604 lines
19 KiB
C#
604 lines
19 KiB
C#
/*
|
|
Based on the Public Domain MaxRectsBinPack.cpp source by Jukka Jylänki
|
|
https://github.com/juj/RectangleBinPack/
|
|
|
|
Ported to C# by Sven Magnus
|
|
This version is also public domain - do whatever you want with it.
|
|
*/
|
|
using UnityEngine;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
|
|
public class UITexturePacker
|
|
{
|
|
public int binWidth = 0;
|
|
public int binHeight = 0;
|
|
public bool allowRotations;
|
|
|
|
public List<Rect> usedRectangles = new List<Rect>();
|
|
public List<Rect> freeRectangles = new List<Rect>();
|
|
|
|
public enum FreeRectChoiceHeuristic
|
|
{
|
|
RectBestShortSideFit, ///< -BSSF: Positions the rectangle against the short side of a free rectangle into which it fits the best.
|
|
RectBestLongSideFit, ///< -BLSF: Positions the rectangle against the long side of a free rectangle into which it fits the best.
|
|
RectBestAreaFit, ///< -BAF: Positions the rectangle into the smallest free rect into which it fits.
|
|
RectBottomLeftRule, ///< -BL: Does the Tetris placement.
|
|
RectContactPointRule ///< -CP: Choosest the placement where the rectangle touches other rects as much as possible.
|
|
};
|
|
|
|
public UITexturePacker (int width, int height, bool rotations)
|
|
{
|
|
Init(width, height, rotations);
|
|
}
|
|
|
|
public void Init (int width, int height, bool rotations)
|
|
{
|
|
binWidth = width;
|
|
binHeight = height;
|
|
allowRotations = rotations;
|
|
|
|
Rect n = new Rect();
|
|
n.x = 0;
|
|
n.y = 0;
|
|
n.width = width;
|
|
n.height = height;
|
|
|
|
usedRectangles.Clear();
|
|
|
|
freeRectangles.Clear();
|
|
freeRectangles.Add(n);
|
|
}
|
|
|
|
private struct Storage
|
|
{
|
|
public Rect rect;
|
|
public bool paddingX;
|
|
public bool paddingY;
|
|
}
|
|
|
|
public static Rect[] PackTextures (Texture2D texture, Texture2D[] textures, int width, int height, int padding, int maxSize)
|
|
{
|
|
if (width > maxSize && height > maxSize) return null;
|
|
if (width > maxSize || height > maxSize) { int temp = width; width = height; height = temp; }
|
|
|
|
// Force square by sizing up
|
|
if (NGUISettings.forceSquareAtlas)
|
|
{
|
|
if (width > height)
|
|
height = width;
|
|
else if (height > width)
|
|
width = height;
|
|
}
|
|
UITexturePacker bp = new UITexturePacker(width, height, false);
|
|
Storage[] storage = new Storage[textures.Length];
|
|
|
|
for (int i = 0; i < textures.Length; i++)
|
|
{
|
|
Texture2D tex = textures[i];
|
|
if (!tex) continue;
|
|
|
|
Rect rect = new Rect();
|
|
|
|
int xPadding = 1;
|
|
int yPadding = 1;
|
|
|
|
for (xPadding = 1; xPadding >= 0; --xPadding)
|
|
{
|
|
for (yPadding = 1; yPadding >= 0; --yPadding)
|
|
{
|
|
rect = bp.Insert(tex.width + (xPadding * padding), tex.height + (yPadding * padding),
|
|
UITexturePacker.FreeRectChoiceHeuristic.RectBestAreaFit);
|
|
if (rect.width != 0 && rect.height != 0) break;
|
|
|
|
// After having no padding if it still doesn't fit -- increase texture size.
|
|
else if (xPadding == 0 && yPadding == 0)
|
|
{
|
|
return PackTextures(texture, textures, width * (width <= height ? 2 : 1),
|
|
height * (height < width ? 2 : 1), padding, maxSize);
|
|
}
|
|
}
|
|
if (rect.width != 0 && rect.height != 0) break;
|
|
}
|
|
|
|
storage[i] = new Storage();
|
|
storage[i].rect = rect;
|
|
storage[i].paddingX = (xPadding != 0);
|
|
storage[i].paddingY = (yPadding != 0);
|
|
}
|
|
|
|
texture.Resize(width, height);
|
|
texture.SetPixels(new Color[width * height]);
|
|
|
|
// The returned rects
|
|
Rect[] rects = new Rect[textures.Length];
|
|
|
|
for (int i = 0; i < textures.Length; i++)
|
|
{
|
|
Texture2D tex = textures[i];
|
|
if (!tex) continue;
|
|
|
|
Rect rect = storage[i].rect;
|
|
int xPadding = (storage[i].paddingX ? padding : 0);
|
|
int yPadding = (storage[i].paddingY ? padding : 0);
|
|
Color[] colors = tex.GetPixels();
|
|
|
|
// Would be used to rotate the texture if need be.
|
|
if (rect.width != tex.width + xPadding)
|
|
{
|
|
Color[] newColors = tex.GetPixels();
|
|
|
|
for (int x = 0; x < rect.width; x++)
|
|
{
|
|
for (int y = 0; y < rect.height; y++)
|
|
{
|
|
int prevIndex = ((int)rect.height - (y + 1)) + x * (int)tex.width;
|
|
newColors[x + y * (int)rect.width] = colors[prevIndex];
|
|
}
|
|
}
|
|
|
|
colors = newColors;
|
|
}
|
|
|
|
texture.SetPixels((int)rect.x, (int)rect.y, (int)rect.width - xPadding, (int)rect.height - yPadding, colors);
|
|
rect.x /= width;
|
|
rect.y /= height;
|
|
rect.width = (rect.width - xPadding) / width;
|
|
rect.height = (rect.height - yPadding) / height;
|
|
rects[i] = rect;
|
|
}
|
|
texture.Apply();
|
|
return rects;
|
|
}
|
|
|
|
public Rect Insert (int width, int height, FreeRectChoiceHeuristic method)
|
|
{
|
|
Rect newNode = new Rect();
|
|
int score1 = 0; // Unused in this function. We don't need to know the score after finding the position.
|
|
int score2 = 0;
|
|
switch (method)
|
|
{
|
|
case FreeRectChoiceHeuristic.RectBestShortSideFit: newNode = FindPositionForNewNodeBestShortSideFit(width, height, ref score1, ref score2); break;
|
|
case FreeRectChoiceHeuristic.RectBottomLeftRule: newNode = FindPositionForNewNodeBottomLeft(width, height, ref score1, ref score2); break;
|
|
case FreeRectChoiceHeuristic.RectContactPointRule: newNode = FindPositionForNewNodeContactPoint(width, height, ref score1); break;
|
|
case FreeRectChoiceHeuristic.RectBestLongSideFit: newNode = FindPositionForNewNodeBestLongSideFit(width, height, ref score2, ref score1); break;
|
|
case FreeRectChoiceHeuristic.RectBestAreaFit: newNode = FindPositionForNewNodeBestAreaFit(width, height, ref score1, ref score2); break;
|
|
}
|
|
|
|
if (newNode.height == 0)
|
|
return newNode;
|
|
|
|
int numRectanglesToProcess = freeRectangles.Count;
|
|
for (int i = 0; i < numRectanglesToProcess; ++i)
|
|
{
|
|
if (SplitFreeNode(freeRectangles[i], ref newNode))
|
|
{
|
|
freeRectangles.RemoveAt(i);
|
|
--i;
|
|
--numRectanglesToProcess;
|
|
}
|
|
}
|
|
|
|
PruneFreeList();
|
|
|
|
usedRectangles.Add(newNode);
|
|
return newNode;
|
|
}
|
|
|
|
public void Insert (List<Rect> rects, List<Rect> dst, FreeRectChoiceHeuristic method)
|
|
{
|
|
dst.Clear();
|
|
|
|
while (rects.Count > 0)
|
|
{
|
|
int bestScore1 = int.MaxValue;
|
|
int bestScore2 = int.MaxValue;
|
|
int bestRectIndex = -1;
|
|
Rect bestNode = new Rect();
|
|
|
|
for (int i = 0; i < rects.Count; ++i)
|
|
{
|
|
int score1 = 0;
|
|
int score2 = 0;
|
|
Rect newNode = ScoreRect((int)rects[i].width, (int)rects[i].height, method, ref score1, ref score2);
|
|
|
|
if (score1 < bestScore1 || (score1 == bestScore1 && score2 < bestScore2))
|
|
{
|
|
bestScore1 = score1;
|
|
bestScore2 = score2;
|
|
bestNode = newNode;
|
|
bestRectIndex = i;
|
|
}
|
|
}
|
|
|
|
if (bestRectIndex == -1)
|
|
return;
|
|
|
|
PlaceRect(bestNode);
|
|
rects.RemoveAt(bestRectIndex);
|
|
}
|
|
}
|
|
|
|
void PlaceRect (Rect node)
|
|
{
|
|
int numRectanglesToProcess = freeRectangles.Count;
|
|
for (int i = 0; i < numRectanglesToProcess; ++i)
|
|
{
|
|
if (SplitFreeNode(freeRectangles[i], ref node))
|
|
{
|
|
freeRectangles.RemoveAt(i);
|
|
--i;
|
|
--numRectanglesToProcess;
|
|
}
|
|
}
|
|
|
|
PruneFreeList();
|
|
|
|
usedRectangles.Add(node);
|
|
}
|
|
|
|
Rect ScoreRect (int width, int height, FreeRectChoiceHeuristic method, ref int score1, ref int score2)
|
|
{
|
|
Rect newNode = new Rect();
|
|
score1 = int.MaxValue;
|
|
score2 = int.MaxValue;
|
|
switch (method)
|
|
{
|
|
case FreeRectChoiceHeuristic.RectBestShortSideFit: newNode = FindPositionForNewNodeBestShortSideFit(width, height, ref score1, ref score2); break;
|
|
case FreeRectChoiceHeuristic.RectBottomLeftRule: newNode = FindPositionForNewNodeBottomLeft(width, height, ref score1, ref score2); break;
|
|
case FreeRectChoiceHeuristic.RectContactPointRule: newNode = FindPositionForNewNodeContactPoint(width, height, ref score1);
|
|
score1 = -score1; // Reverse since we are minimizing, but for contact point score bigger is better.
|
|
break;
|
|
case FreeRectChoiceHeuristic.RectBestLongSideFit: newNode = FindPositionForNewNodeBestLongSideFit(width, height, ref score2, ref score1); break;
|
|
case FreeRectChoiceHeuristic.RectBestAreaFit: newNode = FindPositionForNewNodeBestAreaFit(width, height, ref score1, ref score2); break;
|
|
}
|
|
|
|
// Cannot fit the current rectangle.
|
|
if (newNode.height == 0)
|
|
{
|
|
score1 = int.MaxValue;
|
|
score2 = int.MaxValue;
|
|
}
|
|
|
|
return newNode;
|
|
}
|
|
|
|
/// Computes the ratio of used surface area.
|
|
public float Occupancy ()
|
|
{
|
|
ulong usedSurfaceArea = 0;
|
|
for (int i = 0; i < usedRectangles.Count; ++i)
|
|
usedSurfaceArea += (uint)usedRectangles[i].width * (uint)usedRectangles[i].height;
|
|
|
|
return (float)usedSurfaceArea / (binWidth * binHeight);
|
|
}
|
|
|
|
Rect FindPositionForNewNodeBottomLeft (int width, int height, ref int bestY, ref int bestX)
|
|
{
|
|
Rect bestNode = new Rect();
|
|
//memset(bestNode, 0, sizeof(Rect));
|
|
|
|
bestY = int.MaxValue;
|
|
|
|
for (int i = 0; i < freeRectangles.Count; ++i)
|
|
{
|
|
// Try to place the rectangle in upright (non-flipped) orientation.
|
|
if (freeRectangles[i].width >= width && freeRectangles[i].height >= height)
|
|
{
|
|
int topSideY = (int)freeRectangles[i].y + height;
|
|
if (topSideY < bestY || (topSideY == bestY && freeRectangles[i].x < bestX))
|
|
{
|
|
bestNode.x = freeRectangles[i].x;
|
|
bestNode.y = freeRectangles[i].y;
|
|
bestNode.width = width;
|
|
bestNode.height = height;
|
|
bestY = topSideY;
|
|
bestX = (int)freeRectangles[i].x;
|
|
}
|
|
}
|
|
if (allowRotations && freeRectangles[i].width >= height && freeRectangles[i].height >= width)
|
|
{
|
|
int topSideY = (int)freeRectangles[i].y + width;
|
|
if (topSideY < bestY || (topSideY == bestY && freeRectangles[i].x < bestX))
|
|
{
|
|
bestNode.x = freeRectangles[i].x;
|
|
bestNode.y = freeRectangles[i].y;
|
|
bestNode.width = height;
|
|
bestNode.height = width;
|
|
bestY = topSideY;
|
|
bestX = (int)freeRectangles[i].x;
|
|
}
|
|
}
|
|
}
|
|
return bestNode;
|
|
}
|
|
|
|
Rect FindPositionForNewNodeBestShortSideFit (int width, int height, ref int bestShortSideFit, ref int bestLongSideFit)
|
|
{
|
|
Rect bestNode = new Rect();
|
|
//memset(&bestNode, 0, sizeof(Rect));
|
|
|
|
bestShortSideFit = int.MaxValue;
|
|
|
|
for (int i = 0; i < freeRectangles.Count; ++i)
|
|
{
|
|
// Try to place the rectangle in upright (non-flipped) orientation.
|
|
if (freeRectangles[i].width >= width && freeRectangles[i].height >= height)
|
|
{
|
|
int leftoverHoriz = Mathf.Abs((int)freeRectangles[i].width - width);
|
|
int leftoverVert = Mathf.Abs((int)freeRectangles[i].height - height);
|
|
int shortSideFit = Mathf.Min(leftoverHoriz, leftoverVert);
|
|
int longSideFit = Mathf.Max(leftoverHoriz, leftoverVert);
|
|
|
|
if (shortSideFit < bestShortSideFit || (shortSideFit == bestShortSideFit && longSideFit < bestLongSideFit))
|
|
{
|
|
bestNode.x = freeRectangles[i].x;
|
|
bestNode.y = freeRectangles[i].y;
|
|
bestNode.width = width;
|
|
bestNode.height = height;
|
|
bestShortSideFit = shortSideFit;
|
|
bestLongSideFit = longSideFit;
|
|
}
|
|
}
|
|
|
|
if (allowRotations && freeRectangles[i].width >= height && freeRectangles[i].height >= width)
|
|
{
|
|
int flippedLeftoverHoriz = Mathf.Abs((int)freeRectangles[i].width - height);
|
|
int flippedLeftoverVert = Mathf.Abs((int)freeRectangles[i].height - width);
|
|
int flippedShortSideFit = Mathf.Min(flippedLeftoverHoriz, flippedLeftoverVert);
|
|
int flippedLongSideFit = Mathf.Max(flippedLeftoverHoriz, flippedLeftoverVert);
|
|
|
|
if (flippedShortSideFit < bestShortSideFit || (flippedShortSideFit == bestShortSideFit && flippedLongSideFit < bestLongSideFit))
|
|
{
|
|
bestNode.x = freeRectangles[i].x;
|
|
bestNode.y = freeRectangles[i].y;
|
|
bestNode.width = height;
|
|
bestNode.height = width;
|
|
bestShortSideFit = flippedShortSideFit;
|
|
bestLongSideFit = flippedLongSideFit;
|
|
}
|
|
}
|
|
}
|
|
return bestNode;
|
|
}
|
|
|
|
Rect FindPositionForNewNodeBestLongSideFit (int width, int height, ref int bestShortSideFit, ref int bestLongSideFit)
|
|
{
|
|
Rect bestNode = new Rect();
|
|
//memset(&bestNode, 0, sizeof(Rect));
|
|
|
|
bestLongSideFit = int.MaxValue;
|
|
|
|
for (int i = 0; i < freeRectangles.Count; ++i)
|
|
{
|
|
// Try to place the rectangle in upright (non-flipped) orientation.
|
|
if (freeRectangles[i].width >= width && freeRectangles[i].height >= height)
|
|
{
|
|
int leftoverHoriz = Mathf.Abs((int)freeRectangles[i].width - width);
|
|
int leftoverVert = Mathf.Abs((int)freeRectangles[i].height - height);
|
|
int shortSideFit = Mathf.Min(leftoverHoriz, leftoverVert);
|
|
int longSideFit = Mathf.Max(leftoverHoriz, leftoverVert);
|
|
|
|
if (longSideFit < bestLongSideFit || (longSideFit == bestLongSideFit && shortSideFit < bestShortSideFit))
|
|
{
|
|
bestNode.x = freeRectangles[i].x;
|
|
bestNode.y = freeRectangles[i].y;
|
|
bestNode.width = width;
|
|
bestNode.height = height;
|
|
bestShortSideFit = shortSideFit;
|
|
bestLongSideFit = longSideFit;
|
|
}
|
|
}
|
|
|
|
if (allowRotations && freeRectangles[i].width >= height && freeRectangles[i].height >= width)
|
|
{
|
|
int leftoverHoriz = Mathf.Abs((int)freeRectangles[i].width - height);
|
|
int leftoverVert = Mathf.Abs((int)freeRectangles[i].height - width);
|
|
int shortSideFit = Mathf.Min(leftoverHoriz, leftoverVert);
|
|
int longSideFit = Mathf.Max(leftoverHoriz, leftoverVert);
|
|
|
|
if (longSideFit < bestLongSideFit || (longSideFit == bestLongSideFit && shortSideFit < bestShortSideFit))
|
|
{
|
|
bestNode.x = freeRectangles[i].x;
|
|
bestNode.y = freeRectangles[i].y;
|
|
bestNode.width = height;
|
|
bestNode.height = width;
|
|
bestShortSideFit = shortSideFit;
|
|
bestLongSideFit = longSideFit;
|
|
}
|
|
}
|
|
}
|
|
return bestNode;
|
|
}
|
|
|
|
Rect FindPositionForNewNodeBestAreaFit (int width, int height, ref int bestAreaFit, ref int bestShortSideFit)
|
|
{
|
|
Rect bestNode = new Rect();
|
|
//memset(&bestNode, 0, sizeof(Rect));
|
|
|
|
bestAreaFit = int.MaxValue;
|
|
|
|
for (int i = 0; i < freeRectangles.Count; ++i)
|
|
{
|
|
int areaFit = (int)freeRectangles[i].width * (int)freeRectangles[i].height - width * height;
|
|
|
|
// Try to place the rectangle in upright (non-flipped) orientation.
|
|
if (freeRectangles[i].width >= width && freeRectangles[i].height >= height)
|
|
{
|
|
int leftoverHoriz = Mathf.Abs((int)freeRectangles[i].width - width);
|
|
int leftoverVert = Mathf.Abs((int)freeRectangles[i].height - height);
|
|
int shortSideFit = Mathf.Min(leftoverHoriz, leftoverVert);
|
|
|
|
if (areaFit < bestAreaFit || (areaFit == bestAreaFit && shortSideFit < bestShortSideFit))
|
|
{
|
|
bestNode.x = freeRectangles[i].x;
|
|
bestNode.y = freeRectangles[i].y;
|
|
bestNode.width = width;
|
|
bestNode.height = height;
|
|
bestShortSideFit = shortSideFit;
|
|
bestAreaFit = areaFit;
|
|
}
|
|
}
|
|
|
|
if (allowRotations && freeRectangles[i].width >= height && freeRectangles[i].height >= width)
|
|
{
|
|
int leftoverHoriz = Mathf.Abs((int)freeRectangles[i].width - height);
|
|
int leftoverVert = Mathf.Abs((int)freeRectangles[i].height - width);
|
|
int shortSideFit = Mathf.Min(leftoverHoriz, leftoverVert);
|
|
|
|
if (areaFit < bestAreaFit || (areaFit == bestAreaFit && shortSideFit < bestShortSideFit))
|
|
{
|
|
bestNode.x = freeRectangles[i].x;
|
|
bestNode.y = freeRectangles[i].y;
|
|
bestNode.width = height;
|
|
bestNode.height = width;
|
|
bestShortSideFit = shortSideFit;
|
|
bestAreaFit = areaFit;
|
|
}
|
|
}
|
|
}
|
|
return bestNode;
|
|
}
|
|
|
|
/// Returns 0 if the two intervals i1 and i2 are disjoint, or the length of their overlap otherwise.
|
|
int CommonIntervalLength (int i1start, int i1end, int i2start, int i2end)
|
|
{
|
|
if (i1end < i2start || i2end < i1start)
|
|
return 0;
|
|
return Mathf.Min(i1end, i2end) - Mathf.Max(i1start, i2start);
|
|
}
|
|
|
|
int ContactPointScoreNode (int x, int y, int width, int height)
|
|
{
|
|
int score = 0;
|
|
|
|
if (x == 0 || x + width == binWidth)
|
|
score += height;
|
|
if (y == 0 || y + height == binHeight)
|
|
score += width;
|
|
|
|
for (int i = 0; i < usedRectangles.Count; ++i)
|
|
{
|
|
if (usedRectangles[i].x == x + width || usedRectangles[i].x + usedRectangles[i].width == x)
|
|
score += CommonIntervalLength((int)usedRectangles[i].y, (int)usedRectangles[i].y + (int)usedRectangles[i].height, y, y + height);
|
|
if (usedRectangles[i].y == y + height || usedRectangles[i].y + usedRectangles[i].height == y)
|
|
score += CommonIntervalLength((int)usedRectangles[i].x, (int)usedRectangles[i].x + (int)usedRectangles[i].width, x, x + width);
|
|
}
|
|
return score;
|
|
}
|
|
|
|
Rect FindPositionForNewNodeContactPoint (int width, int height, ref int bestContactScore)
|
|
{
|
|
Rect bestNode = new Rect();
|
|
//memset(&bestNode, 0, sizeof(Rect));
|
|
|
|
bestContactScore = -1;
|
|
|
|
for (int i = 0; i < freeRectangles.Count; ++i)
|
|
{
|
|
// Try to place the rectangle in upright (non-flipped) orientation.
|
|
if (freeRectangles[i].width >= width && freeRectangles[i].height >= height)
|
|
{
|
|
int score = ContactPointScoreNode((int)freeRectangles[i].x, (int)freeRectangles[i].y, width, height);
|
|
if (score > bestContactScore)
|
|
{
|
|
bestNode.x = (int)freeRectangles[i].x;
|
|
bestNode.y = (int)freeRectangles[i].y;
|
|
bestNode.width = width;
|
|
bestNode.height = height;
|
|
bestContactScore = score;
|
|
}
|
|
}
|
|
if (allowRotations && freeRectangles[i].width >= height && freeRectangles[i].height >= width)
|
|
{
|
|
int score = ContactPointScoreNode((int)freeRectangles[i].x, (int)freeRectangles[i].y, height, width);
|
|
if (score > bestContactScore)
|
|
{
|
|
bestNode.x = (int)freeRectangles[i].x;
|
|
bestNode.y = (int)freeRectangles[i].y;
|
|
bestNode.width = height;
|
|
bestNode.height = width;
|
|
bestContactScore = score;
|
|
}
|
|
}
|
|
}
|
|
return bestNode;
|
|
}
|
|
|
|
bool SplitFreeNode (Rect freeNode, ref Rect usedNode)
|
|
{
|
|
// Test with SAT if the rectangles even intersect.
|
|
if (usedNode.x >= freeNode.x + freeNode.width || usedNode.x + usedNode.width <= freeNode.x ||
|
|
usedNode.y >= freeNode.y + freeNode.height || usedNode.y + usedNode.height <= freeNode.y)
|
|
return false;
|
|
|
|
if (usedNode.x < freeNode.x + freeNode.width && usedNode.x + usedNode.width > freeNode.x)
|
|
{
|
|
// New node at the top side of the used node.
|
|
if (usedNode.y > freeNode.y && usedNode.y < freeNode.y + freeNode.height)
|
|
{
|
|
Rect newNode = freeNode;
|
|
newNode.height = usedNode.y - newNode.y;
|
|
freeRectangles.Add(newNode);
|
|
}
|
|
|
|
// New node at the bottom side of the used node.
|
|
if (usedNode.y + usedNode.height < freeNode.y + freeNode.height)
|
|
{
|
|
Rect newNode = freeNode;
|
|
newNode.y = usedNode.y + usedNode.height;
|
|
newNode.height = freeNode.y + freeNode.height - (usedNode.y + usedNode.height);
|
|
freeRectangles.Add(newNode);
|
|
}
|
|
}
|
|
|
|
if (usedNode.y < freeNode.y + freeNode.height && usedNode.y + usedNode.height > freeNode.y)
|
|
{
|
|
// New node at the left side of the used node.
|
|
if (usedNode.x > freeNode.x && usedNode.x < freeNode.x + freeNode.width)
|
|
{
|
|
Rect newNode = freeNode;
|
|
newNode.width = usedNode.x - newNode.x;
|
|
freeRectangles.Add(newNode);
|
|
}
|
|
|
|
// New node at the right side of the used node.
|
|
if (usedNode.x + usedNode.width < freeNode.x + freeNode.width)
|
|
{
|
|
Rect newNode = freeNode;
|
|
newNode.x = usedNode.x + usedNode.width;
|
|
newNode.width = freeNode.x + freeNode.width - (usedNode.x + usedNode.width);
|
|
freeRectangles.Add(newNode);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void PruneFreeList ()
|
|
{
|
|
for (int i = 0; i < freeRectangles.Count; ++i)
|
|
for (int j = i + 1; j < freeRectangles.Count; ++j)
|
|
{
|
|
if (IsContainedIn(freeRectangles[i], freeRectangles[j]))
|
|
{
|
|
freeRectangles.RemoveAt(i);
|
|
--i;
|
|
break;
|
|
}
|
|
if (IsContainedIn(freeRectangles[j], freeRectangles[i]))
|
|
{
|
|
freeRectangles.RemoveAt(j);
|
|
--j;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool IsContainedIn (Rect a, Rect b)
|
|
{
|
|
return a.x >= b.x && a.y >= b.y
|
|
&& a.x + a.width <= b.x + b.width
|
|
&& a.y + a.height <= b.y + b.height;
|
|
}
|
|
}
|