static void drawGizmosNode(AtalsAreaNode r) { Vector3 extents = new Vector3(r.nodeAreaRect.w, r.nodeAreaRect.h, 0); Vector3 pos = new Vector3(r.nodeAreaRect.x + extents.x / 2, -r.nodeAreaRect.y - extents.y / 2, 0f); Gizmos.color = Color.yellow; Gizmos.DrawWireCube(pos, extents); if (r.img != null) { Gizmos.color = new Color(UnityEngine.Random.value, UnityEngine.Random.value, UnityEngine.Random.value); extents = new Vector3(r.img.w, r.img.h, 0); pos = new Vector3(r.nodeAreaRect.x + extents.x / 2, -r.nodeAreaRect.y - extents.y / 2, 0f); Gizmos.DrawCube(pos, extents); } if (r.child[0] != null) { Gizmos.color = Color.red; drawGizmosNode(r.child[0]); } if (r.child[1] != null) { Gizmos.color = Color.green; drawGizmosNode(r.child[1]); } }
public void Set(int ww, int hh, int outw, int outh, AtalsAreaNode r, bool fits, float e, float sq) { w = ww; h = hh; outW = outw; outH = outh; root = r; largerOrEqualToMaxDim = fits; efficiency = e; squareness = sq; }
static void printTree(AtalsAreaNode r, string spc) { Debug.Log(spc + "Nd img=" + (r.img != null) + " r=" + r.nodeAreaRect); if (r.child[0] != null) { printTree(r.child[0], spc + " "); } if (r.child[1] != null) { printTree(r.child[1], spc + " "); } }
////public int atlasY; /// <summary> /// 展开图集区域树,将图片区域保存在 putHere 列表 /// </summary> /// <param name="r"></param> /// <param name="putHere"></param> static void flattenTree(AtalsAreaNode r, List <ImageAreaInAtlas> putHere) { if (r.img != null) { r.img.x = r.nodeAreaRect.x; r.img.y = r.nodeAreaRect.y; putHere.Add(r.img); } if (r.child[0] != null) { flattenTree(r.child[0], putHere); } if (r.child[1] != null) { flattenTree(r.child[1], putHere); } }
internal void GetExtent(AtalsAreaNode r, ref int x, ref int y) { if (r.img != null) { if (r.nodeAreaRect.x + r.img.w > x) { x = r.nodeAreaRect.x + r.img.w; } if (r.nodeAreaRect.y + r.img.h > y) { y = r.nodeAreaRect.y + r.img.h; } } if (r.child[0] != null) { GetExtent(r.child[0], ref x, ref y); } if (r.child[1] != null) { GetExtent(r.child[1], ref x, ref y); } }
bool ProbeMultiAtlas(ImageAreaInAtlas[] imgsToAdd, int idealAtlasW, int idealAtlasH, float imgArea, int maxAtlasDimX, int maxAtlasDimY, ProbeResult pr) { int numAtlases = 0; AtalsAreaNode root = new AtalsAreaNode(NodeType.maxDim); root.nodeAreaRect = new AtalsAreaPixRect(0, 0, idealAtlasW, idealAtlasH); for (int i = 0; i < imgsToAdd.Length; i++) { AtalsAreaNode n = root.Insert(imgsToAdd[i], false); if (n == null) { if (imgsToAdd[i].x > idealAtlasW && imgsToAdd[i].y > idealAtlasH) { return(false); } else { // create a new root node wider than previous atlas AtalsAreaNode newRoot = new AtalsAreaNode(NodeType.Container); newRoot.nodeAreaRect = new AtalsAreaPixRect(0, 0, root.nodeAreaRect.w + idealAtlasW, idealAtlasH); // create a new right child AtalsAreaNode newRight = new AtalsAreaNode(NodeType.maxDim); newRight.nodeAreaRect = new AtalsAreaPixRect(root.nodeAreaRect.w, 0, idealAtlasW, idealAtlasH); newRoot.child[1] = newRight; // insert root as a new left child newRoot.child[0] = root; root = newRoot; n = root.Insert(imgsToAdd[i], false); numAtlases++; } } } pr.numAtlases = numAtlases; pr.root = root; //todo atlas may not be maxDim * maxDim. Do some checking to see what actual needed sizes are and update pr.totalArea pr.totalAtlasArea = numAtlases * maxAtlasDimX * maxAtlasDimY; Debug.Log("Probe success efficiency numAtlases=" + numAtlases + " totalArea=" + pr.totalAtlasArea); return(true); }
internal AtalsAreaNode Insert(ImageAreaInAtlas im, bool handed) { int a, b; if (handed) { a = 0; b = 1; } else { a = 1; b = 0; } if (!isLeaf()) { //不是叶 //try insert into first child AtalsAreaNode newNode = child[a].Insert(im, handed); if (newNode != null) { return(newNode); } //no room insert into second return(child[b].Insert(im, handed)); } else { //(if there's already a img here, return) if (img != null) { return(null); } //(if space too small, return) if (nodeAreaRect.w < im.w || nodeAreaRect.h < im.h) { return(null); } //(if space just right, accept) if (nodeAreaRect.w == im.w && nodeAreaRect.h == im.h) { img = im; return(this); } //拆分此节点并创建两个分支 child[a] = new AtalsAreaNode(NodeType.regular); child[b] = new AtalsAreaNode(NodeType.regular); //(decide which way to split) int dw = nodeAreaRect.w - im.w; int dh = nodeAreaRect.h - im.h; if (dw > dh) { child[a].nodeAreaRect = new AtalsAreaPixRect(nodeAreaRect.x, nodeAreaRect.y, im.w, nodeAreaRect.h); child[b].nodeAreaRect = new AtalsAreaPixRect(nodeAreaRect.x + im.w, nodeAreaRect.y, nodeAreaRect.w - im.w, nodeAreaRect.h); } else { child[a].nodeAreaRect = new AtalsAreaPixRect(nodeAreaRect.x, nodeAreaRect.y, nodeAreaRect.w, im.h); child[b].nodeAreaRect = new AtalsAreaPixRect(nodeAreaRect.x, nodeAreaRect.y + im.h, nodeAreaRect.w, nodeAreaRect.h - im.h); } return(child[a].Insert(im, handed)); } }
//----------------- Algorithm for fitting everything into multiple Atlases // // for images being added calc area, maxW, maxH. A perfectly packed atlas will match area exactly. atlas must be at least maxH and maxW in size. // Sort images from big to small using either height, width or area comparer // // If an image is bigger than maxDim, then shrink it to max size on the largest dimension // distribute images using the new algorithm, should never have to expand the atlas instead create new atlases as needed // should not need to scale atlases // AtlasPackingResult[] _GetRectsMultiAtlas(List <Vector2> imgWidthHeights, List <AtlasPadding> paddings, int maxDimensionPassedX, int maxDimensionPassedY, int minImageSizeX, int minImageSizeY, int masterImageSizeX, int masterImageSizeY) { Debug.Log(String.Format("_GetRects numImages={0}, maxDimensionX={1}, maxDimensionY={2} minImageSizeX={3}, minImageSizeY={4}, masterImageSizeX={5}, masterImageSizeY={6}", imgWidthHeights.Count, maxDimensionPassedX, maxDimensionPassedY, minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY)); float area = 0; int maxW = 0; int maxH = 0; ImageAreaInAtlas[] imgsToAdd = new ImageAreaInAtlas[imgWidthHeights.Count]; int maxDimensionX = maxDimensionPassedX; int maxDimensionY = maxDimensionPassedY; if (atlasMustBePowerOfTwo) { maxDimensionX = RoundToNearestPositivePowerOfTwo(maxDimensionX); maxDimensionY = RoundToNearestPositivePowerOfTwo(maxDimensionY); } for (int i = 0; i < imgsToAdd.Length; i++) { int iw = (int)imgWidthHeights[i].x; int ih = (int)imgWidthHeights[i].y; //shrink the image so that it fits in maxDimenion if it is larger than maxDimension if atlas exceeds maxDim x maxDim then new alas will be created iw = Mathf.Min(iw, maxDimensionX - paddings[i].leftRight * 2); ih = Mathf.Min(ih, maxDimensionY - paddings[i].topBottom * 2); ImageAreaInAtlas im = imgsToAdd[i] = new ImageAreaInAtlas(i, iw, ih, paddings[i], minImageSizeX, minImageSizeY); area += im.w * im.h; maxW = Mathf.Max(maxW, im.w); maxH = Mathf.Max(maxH, im.h); } //explore the space to find a resonably efficient packing //int sqrtArea = (int)Mathf.Sqrt(area); int idealAtlasW; int idealAtlasH; if (atlasMustBePowerOfTwo) { idealAtlasH = RoundToNearestPositivePowerOfTwo(maxDimensionY); idealAtlasW = RoundToNearestPositivePowerOfTwo(maxDimensionX); } else { idealAtlasH = maxDimensionY; idealAtlasW = maxDimensionX; } if (idealAtlasW == 0) { idealAtlasW = 4; } if (idealAtlasH == 0) { idealAtlasH = 4; } ProbeResult pr = new ProbeResult(); Array.Sort(imgsToAdd, new ImageHeightComparer()); if (ProbeMultiAtlas(imgsToAdd, idealAtlasW, idealAtlasH, area, maxDimensionX, maxDimensionY, pr)) { bestRoot = pr; } Array.Sort(imgsToAdd, new ImageWidthComparer()); if (ProbeMultiAtlas(imgsToAdd, idealAtlasW, idealAtlasH, area, maxDimensionX, maxDimensionY, pr)) { if (pr.totalAtlasArea < bestRoot.totalAtlasArea) { bestRoot = pr; } } Array.Sort(imgsToAdd, new ImageAreaComparer()); if (ProbeMultiAtlas(imgsToAdd, idealAtlasW, idealAtlasH, area, maxDimensionX, maxDimensionY, pr)) { if (pr.totalAtlasArea < bestRoot.totalAtlasArea) { bestRoot = pr; } } if (bestRoot == null) { return(null); } Debug.Log("Best fit found: w=" + bestRoot.w + " h=" + bestRoot.h + " efficiency=" + bestRoot.efficiency + " squareness=" + bestRoot.squareness + " fits in max dimension=" + bestRoot.largerOrEqualToMaxDim); //the atlas can be larger than the max dimension scale it if this is the case //int newMinSizeX = minImageSizeX; //int newMinSizeY = minImageSizeY; List <AtlasPackingResult> rs = new List <AtlasPackingResult>(); // find all Nodes that are an individual atlas List <AtalsAreaNode> atlasNodes = new List <AtalsAreaNode>(); Stack <AtalsAreaNode> stack = new Stack <AtalsAreaNode>(); AtalsAreaNode node = bestRoot.root; while (node != null) { stack.Push(node); node = node.child[0]; } // traverse the tree collecting atlasNodes while (stack.Count > 0) { node = stack.Pop(); if (node.isFullAtlas == NodeType.maxDim) { atlasNodes.Add(node); } if (node.child[1] != null) { node = node.child[1]; while (node != null) { stack.Push(node); node = node.child[0]; } } } //pack atlases so they all fit for (int i = 0; i < atlasNodes.Count; i++) { List <ImageAreaInAtlas> images = new List <ImageAreaInAtlas>(); flattenTree(atlasNodes[i], images); Rect[] rss = new Rect[images.Count]; int[] srcImgIdx = new int[images.Count]; for (int j = 0; j < images.Count; j++) { rss[j] = (new Rect(images[j].x - atlasNodes[i].nodeAreaRect.x, images[j].y, images[j].w, images[j].h)); srcImgIdx[j] = images[j].imgId; } AtlasPackingResult res = new AtlasPackingResult(paddings.ToArray()); GetExtent(atlasNodes[i], ref res.usedW, ref res.usedH); res.usedW -= atlasNodes[i].nodeAreaRect.x; int outW = atlasNodes[i].nodeAreaRect.w; int outH = atlasNodes[i].nodeAreaRect.h; if (atlasMustBePowerOfTwo) { outW = Mathf.Min(CeilToNearestPowerOfTwo(res.usedW), atlasNodes[i].nodeAreaRect.w); outH = Mathf.Min(CeilToNearestPowerOfTwo(res.usedH), atlasNodes[i].nodeAreaRect.h); if (outH < outW / 2) { outH = outW / 2; //smaller dim can't be less than half larger } if (outW < outH / 2) { outW = outH / 2; } } else { outW = res.usedW; outH = res.usedH; } res.atlasY = outH; res.atlasX = outW; res.rects = rss; res.srcImgIdxs = srcImgIdx; res.CalcUsedWidthAndHeight(); rs.Add(res); normalizeRects(res, paddings[i]); Debug.Log(String.Format("Done GetRects ")); } return(rs.ToArray()); }
bool ProbeSingleAtlas(ImageAreaInAtlas[] imgsToAdd, int idealAtlasW, int idealAtlasH, float imgArea, int maxAtlasDimX, int maxAtlasDimY, ProbeResult pr) { AtalsAreaNode root = new AtalsAreaNode(NodeType.maxDim); root.nodeAreaRect = new AtalsAreaPixRect(0, 0, idealAtlasW, idealAtlasH); for (int i = 0; i < imgsToAdd.Length; i++) { AtalsAreaNode n = root.Insert(imgsToAdd[i], false); if (n == null) { return(false); } else if (i == imgsToAdd.Length - 1) { int usedW = 0; int usedH = 0; GetExtent(root, ref usedW, ref usedH); float efficiency, squareness; bool fitsInMaxDim; int atlasW = usedW; int atlasH = usedH; if (atlasMustBePowerOfTwo) { atlasW = Mathf.Min(CeilToNearestPowerOfTwo(usedW), maxAtlasDimX); atlasH = Mathf.Min(CeilToNearestPowerOfTwo(usedH), maxAtlasDimY); if (atlasH < atlasW / 2) { atlasH = atlasW / 2; } if (atlasW < atlasH / 2) { atlasW = atlasH / 2; } fitsInMaxDim = usedW <= maxAtlasDimX && usedH <= maxAtlasDimY; float scaleW = Mathf.Max(1f, ((float)usedW) / maxAtlasDimX); float scaleH = Mathf.Max(1f, ((float)usedH) / maxAtlasDimY); float atlasArea = atlasW * scaleW * atlasH * scaleH; //area if we scaled it up to something large enough to contain images efficiency = 1f - (atlasArea - imgArea) / atlasArea; squareness = 1f; //don't care about squareness in power of two case } else { efficiency = 1f - (usedW * usedH - imgArea) / (usedW * usedH); if (usedW < usedH) { squareness = (float)usedW / (float)usedH; } else { squareness = (float)usedH / (float)usedW; } fitsInMaxDim = usedW <= maxAtlasDimX && usedH <= maxAtlasDimY; } pr.Set(usedW, usedH, atlasW, atlasH, root, fitsInMaxDim, efficiency, squareness); Debug.Log("Probe success efficiency w=" + usedW + " h=" + usedH + " e=" + efficiency + " sq=" + squareness + " fits=" + fitsInMaxDim); return(true); } } Debug.LogError("Should never get here."); return(false); }