http://www.cppblog.com/shly/archive/2015/12/09/212443.html
[Unity3D] 5.0 图集合并扩展工具,用于解决UGUI与AssetBundle打包造成资源包过大的问题
UGUI Image已Sprite为主,而简单的合并图集可以使用自带的SpritePacker。
而当在使用AssetBundle的时候情况有些不同,会造成每个AB都会引入完整的Sprite图集,显然就增加了AB的大小,重复资源。
为了解决这个问题我们可以手动合并图集,那么方案也有多种,这里我们可以编辑器自带的API来实现一个小的图集合并工具。
private
static
bool CombineSpritesHelper(
string path,
string dpath,
string name,
int padding)
{ string[] paths = AssetDatabase.FindAssets("t:sprite", new string[] { path }); List<Sprite> spriteList = new List<Sprite>(); List<Texture2D> texList = new List<Texture2D>(); foreach (var o in paths) { Sprite s = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(o), typeof(Sprite)) as Sprite; if (null != s) { spriteList.Add(s); texList.Add(s.texture); } } if (texList.Count > 0) { Texture2D tex = new Texture2D(1024, 1024, TextureFormat.ARGB32, true); Rect[] uvs = UITexturePacker.PackTextures(tex, texList.ToArray(), 4, 4, padding, 1024); if (null == uvs) { EditorUtility.DisplayDialog(path, "图集超过1024,需要分组成多张图集", "点击退出"); Object.DestroyImmediate(tex); tex = null; return false; } else { List<SpriteMetaData> metaList = new List<SpriteMetaData>(); for (int i = 0; i < uvs.Length; ++i) { SpriteMetaData data = new SpriteMetaData(); data.alignment = (int)SpriteAlignment.Center; data.border = spriteList[i].border; data.name = spriteList[i].name; data.pivot = spriteList[i].pivot; data.rect = new Rect(uvs[i].x * tex.width, uvs[i].y * tex.height, uvs[i].width * tex.width, uvs[i].height * tex.height); metaList.Add(data); } //string dpath = path.Substring(0, path.Length - obj.name.Length) + "SpriteSet"; if (!System.IO.Directory.Exists(dpath)) { System.IO.Directory.CreateDirectory(dpath); } string file = dpath + "/" + name + ".png"; if (System.IO.File.Exists(file)) { System.IO.File.Delete(file); } System.IO.File.WriteAllBytes(file, tex.EncodeToPNG()); AssetDatabase.ImportAsset(file, ImportAssetOptions.ForceUpdate); TextureImporter importer = AssetImporter.GetAtPath(file) as TextureImporter; importer.spritesheet = metaList.ToArray(); importer.spriteImportMode = SpriteImportMode.Multiple; importer.textureType = TextureImporterType.Sprite; importer.textureFormat = TextureImporterFormat.ARGB32; importer.mipmapEnabled = true; importer.mipmapFilter = TextureImporterMipFilter.BoxFilter; importer.assetBundleName = "ui_image/" + name.ToLower(); AssetDatabase.ImportAsset(file); AssetDatabase.Refresh(); } } return true; }
[MenuItem("Tool/Combine Sprites")]
[MenuItem("Assets/Tool/Combine Sprites")]
public
static
void CombineSprites()
{ EditorUtility.DisplayProgressBar("Combine Sprites", "Initializing ", 0); try { Object obj = Selection.activeObject; string path = AssetDatabase.GetAssetPath(obj.GetInstanceID()); string dpath = path.Substring(0, path.Length - obj.name.Length) + "SpriteSet"; if (System.IO.Directory.Exists(path)) { string[] directories = System.IO.Directory.GetDirectories(path); int count = 0; if (directories.Length > 0) { foreach (var directory in directories) { count++; EditorUtility.DisplayProgressBar("Combine Sprites", string.Format("combing {0}", count), (float)(count) / (directories.Length)); if (!CombineSpritesHelper(directory, dpath, string.Concat(obj.name, "_", count.ToString()), 1)) { break; } } } else { EditorUtility.DisplayProgressBar("Combine Sprites", "combing 0", 1); CombineSpritesHelper(path, dpath, obj.name, 1); } } } catch (System.Exception e) { Debug.LogError(e); } EditorUtility.ClearProgressBar(); }
使用方法很简单,可以修改源码中的路径,可以将多个Single Sprite合成一个Multi Sprite。然后在供,UGUI使用,注意最好保存好合并前的源文件,不然可能造成新增图片打图集后对应不上从而造成引用丢失。
补充UITextPacker.cs :引用自NGUI
/*
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;
}
}