您的位置:首页 > 其它

图集合并扩展工具

2016-09-07 12:05 183 查看



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;

}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: