//------------------ Algorithm for fitting everything into one atlas and scaling down // // 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 // Explore space to find a resonably efficient packing. Grow the atlas gradually until a fit is found // Scale atlas to fit // AtlasPackingResult _GetRectsSingleAtlas(List <Vector2> imgWidthHeights, List <AtlasPadding> paddings, int maxDimensionX, int maxDimensionY, int minImageSizeX, int minImageSizeY, int masterImageSizeX, int masterImageSizeY, int recursionDepth) { if (LOG_LEVEL >= MB2_LogLevel.debug) { Debug.Log(String.Format("_GetRects numImages={0}, maxDimension={1}, minImageSizeX={2}, minImageSizeY={3}, masterImageSizeX={4}, masterImageSizeY={5}, recursionDepth={6}", imgWidthHeights.Count, maxDimensionX, minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY, recursionDepth)); } if (recursionDepth > MAX_RECURSION_DEPTH) { if (LOG_LEVEL >= MB2_LogLevel.error) { Debug.LogError("Maximum recursion depth reached. The baked atlas is likely not very good. " + " This happens when the packed atlases exceeds the maximum" + " atlas size in one or both dimensions so that the atlas needs to be downscaled AND there are some very thin or very small images (only-a-few-pixels)." + " these very thin images can 'vanish' completely when the atlas is downscaled.\n\n" + " Try one or more of the following: using multiple atlases, increase the maximum atlas size, don't use 'force-power-of-two', remove the source materials that are are using very small/thin textures."); } //return null; } float area = 0; int maxW = 0; int maxH = 0; Image[] imgsToAdd = new Image[imgWidthHeights.Count]; for (int i = 0; i < imgsToAdd.Length; i++) { int iw = (int)imgWidthHeights[i].x; int ih = (int)imgWidthHeights[i].y; Image im = imgsToAdd[i] = new Image(i, iw, ih, paddings[i], minImageSizeX, minImageSizeY); area += im.w * im.h; maxW = Mathf.Max(maxW, im.w); maxH = Mathf.Max(maxH, im.h); } if ((float)maxH / (float)maxW > 2) { if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Using height Comparer"); } Array.Sort(imgsToAdd, new ImageHeightComparer()); } else if ((float)maxH / (float)maxW < .5) { if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Using width Comparer"); } Array.Sort(imgsToAdd, new ImageWidthComparer()); } else { if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Using area Comparer"); } Array.Sort(imgsToAdd, new ImageAreaComparer()); } //explore the space to find a resonably efficient packing int sqrtArea = (int)Mathf.Sqrt(area); int idealAtlasW; int idealAtlasH; if (atlasMustBePowerOfTwo) { idealAtlasW = idealAtlasH = RoundToNearestPositivePowerOfTwo(sqrtArea); if (maxW > idealAtlasW) { idealAtlasW = CeilToNearestPowerOfTwo(idealAtlasW); } if (maxH > idealAtlasH) { idealAtlasH = CeilToNearestPowerOfTwo(idealAtlasH); } } else { idealAtlasW = sqrtArea; idealAtlasH = sqrtArea; if (maxW > sqrtArea) { idealAtlasW = maxW; idealAtlasH = Mathf.Max(Mathf.CeilToInt(area / maxW), maxH); } if (maxH > sqrtArea) { idealAtlasW = Mathf.Max(Mathf.CeilToInt(area / maxH), maxW); idealAtlasH = maxH; } } if (idealAtlasW == 0) { idealAtlasW = 4; } if (idealAtlasH == 0) { idealAtlasH = 4; } int stepW = (int)(idealAtlasW * .15f); int stepH = (int)(idealAtlasH * .15f); if (stepW == 0) { stepW = 1; } if (stepH == 0) { stepH = 1; } int numWIterations = 2; int steppedWidth = idealAtlasW; int steppedHeight = idealAtlasH; while (numWIterations >= 1 && steppedHeight < sqrtArea * 1000) { bool successW = false; numWIterations = 0; steppedWidth = idealAtlasW; while (!successW && steppedWidth < sqrtArea * 1000) { ProbeResult pr = new ProbeResult(); if (LOG_LEVEL >= MB2_LogLevel.trace) { Debug.Log("Probing h=" + steppedHeight + " w=" + steppedWidth); } if (ProbeSingleAtlas(imgsToAdd, steppedWidth, steppedHeight, area, maxDimensionX, maxDimensionY, pr)) { successW = true; if (bestRoot == null) { bestRoot = pr; } else if (pr.GetScore(atlasMustBePowerOfTwo) > bestRoot.GetScore(atlasMustBePowerOfTwo)) { bestRoot = pr; } } else { numWIterations++; steppedWidth = StepWidthHeight(steppedWidth, stepW, maxDimensionX); if (LOG_LEVEL >= MB2_LogLevel.trace) { MB2_Log.LogDebug("increasing Width h=" + steppedHeight + " w=" + steppedWidth); } } } steppedHeight = StepWidthHeight(steppedHeight, stepH, maxDimensionY); if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("increasing Height h=" + steppedHeight + " w=" + steppedWidth); } } if (bestRoot == null) { return(null); } int outW = 0; int outH = 0; if (atlasMustBePowerOfTwo) { outW = Mathf.Min(CeilToNearestPowerOfTwo(bestRoot.w), maxDimensionX); outH = Mathf.Min(CeilToNearestPowerOfTwo(bestRoot.h), maxDimensionY); 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 = Mathf.Min(bestRoot.w, maxDimensionX); outH = Mathf.Min(bestRoot.h, maxDimensionY); } bestRoot.outW = outW; bestRoot.outH = outH; if (LOG_LEVEL >= MB2_LogLevel.debug) { Debug.Log("Best fit found: atlasW=" + outW + " atlasH" + outH + " w=" + bestRoot.w + " h=" + bestRoot.h + " efficiency=" + bestRoot.efficiency + " squareness=" + bestRoot.squareness + " fits in max dimension=" + bestRoot.largerOrEqualToMaxDim); } //Debug.Assert(images.Count != imgsToAdd.Length, "Result images not the same lentgh as source")); //the atlas can be larger than the max dimension scale it if this is the case //int newMinSizeX = minImageSizeX; //int newMinSizeY = minImageSizeY; List <Image> images = new List <Image>(); flattenTree(bestRoot.root, images); images.Sort(new ImgIDComparer()); // the atlas may be packed larger than the maxDimension. If so then the atlas needs to be scaled down to fit Vector2 rootWH = new Vector2(bestRoot.w, bestRoot.h); float padX, padY; int newMinSizeX, newMinSizeY; if (!ScaleAtlasToFitMaxDim(rootWH, images, maxDimensionX, maxDimensionY, paddings[0], minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY, ref outW, ref outH, out padX, out padY, out newMinSizeX, out newMinSizeY) || recursionDepth > MAX_RECURSION_DEPTH) { AtlasPackingResult res = new AtlasPackingResult(paddings.ToArray()); res.rects = new Rect[images.Count]; res.srcImgIdxs = new int[images.Count]; res.atlasX = outW; res.atlasY = outH; res.usedW = -1; res.usedH = -1; for (int i = 0; i < images.Count; i++) { Image im = images[i]; Rect r = res.rects[i] = new Rect((float)im.x / (float)outW + padX, (float)im.y / (float)outH + padY, (float)im.w / (float)outW - padX * 2f, (float)im.h / (float)outH - padY * 2f); res.srcImgIdxs[i] = im.imgId; if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Image: " + i + " imgID=" + im.imgId + " x=" + r.x * outW + " y=" + r.y * outH + " w=" + r.width * outW + " h=" + r.height * outH + " padding=" + (paddings[i].leftRight * 2) + "x" + (paddings[i].topBottom * 2)); } } res.CalcUsedWidthAndHeight(); return(res); } else { if (LOG_LEVEL >= MB2_LogLevel.debug) { Debug.Log("==================== REDOING PACKING ================"); } //root = null; return(_GetRectsSingleAtlas(imgWidthHeights, paddings, maxDimensionX, maxDimensionY, newMinSizeX, newMinSizeY, masterImageSizeX, masterImageSizeY, recursionDepth + 1)); } //if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug(String.Format("Done GetRects atlasW={0} atlasH={1}", bestRoot.w, bestRoot.h)); //return res; }
Rect[] _GetRects(List <Vector2> imgWidthHeights, int maxDimension, int padding, int minImageSizeX, int minImageSizeY, int masterImageSizeX, int masterImageSizeY, out int outW, out int outH, int recursionDepth) { if (LOG_LEVEL >= MBLogLevel.debug) { Debug.Log(String.Format("_GetRects numImages={0}, maxDimension={1}, padding={2}, minImageSizeX={3}, minImageSizeY={4}, masterImageSizeX={5}, masterImageSizeY={6}, recursionDepth={7}", imgWidthHeights.Count, maxDimension, padding, minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY, recursionDepth)); } if (recursionDepth > 10) { Debug.LogError("Maximum recursion depth reached. Couldn't find packing for these textures."); outW = 0; outH = 0; return(new Rect[0]); } float area = 0; int maxW = 0; int maxH = 0; Image[] imgsToAdd = new Image[imgWidthHeights.Count]; for (int i = 0; i < imgsToAdd.Length; i++) { Image im = imgsToAdd[i] = new Image(i, (int)imgWidthHeights[i].x, (int)imgWidthHeights[i].y, padding, minImageSizeX, minImageSizeY); area += im.w * im.h; maxW = Mathf.Max(maxW, im.w); maxH = Mathf.Max(maxH, im.h); } if ((float)maxH / (float)maxW > 2) { if (LOG_LEVEL >= MBLogLevel.debug) { MBLog.LogDebug("Using height Comparer"); } Array.Sort(imgsToAdd, new ImageHeightComparer()); } else if ((float)maxH / (float)maxW < .5) { if (LOG_LEVEL >= MBLogLevel.debug) { MBLog.LogDebug("Using width Comparer"); } Array.Sort(imgsToAdd, new ImageWidthComparer()); } else { if (LOG_LEVEL >= MBLogLevel.debug) { MBLog.LogDebug("Using area Comparer"); } Array.Sort(imgsToAdd, new ImageAreaComparer()); } //explore the space to find a resonably efficient packing int sqrtArea = (int)Mathf.Sqrt(area); int idealAtlasW; int idealAtlasH; if (doPowerOfTwoTextures) { idealAtlasW = idealAtlasH = RoundToNearestPositivePowerOfTwo(sqrtArea); if (maxW > idealAtlasW) { idealAtlasW = CeilToNearestPowerOfTwo(idealAtlasW); } if (maxH > idealAtlasH) { idealAtlasH = CeilToNearestPowerOfTwo(idealAtlasH); } } else { idealAtlasW = sqrtArea; idealAtlasH = sqrtArea; if (maxW > sqrtArea) { idealAtlasW = maxW; idealAtlasH = Mathf.Max(Mathf.CeilToInt(area / maxW), maxH); } if (maxH > sqrtArea) { idealAtlasW = Mathf.Max(Mathf.CeilToInt(area / maxH), maxW); idealAtlasH = maxH; } } if (idealAtlasW == 0) { idealAtlasW = 1; } if (idealAtlasH == 0) { idealAtlasH = 1; } int stepW = (int)(idealAtlasW * .15f); int stepH = (int)(idealAtlasH * .15f); if (stepW == 0) { stepW = 1; } if (stepH == 0) { stepH = 1; } int numWIterations = 2; int steppedWidth = idealAtlasW; int steppedHeight = idealAtlasH; while (numWIterations >= 1 && steppedHeight < sqrtArea * 1000) { bool successW = false; numWIterations = 0; steppedWidth = idealAtlasW; while (!successW && steppedWidth < sqrtArea * 1000) { ProbeResult pr = new ProbeResult(); if (LOG_LEVEL >= MBLogLevel.trace) { Debug.Log("Probing h=" + steppedHeight + " w=" + steppedWidth); } if (Probe(imgsToAdd, steppedWidth, steppedHeight, area, maxDimension, pr)) { successW = true; if (bestRoot == null) { bestRoot = pr; } else if (pr.GetScore(doPowerOfTwoTextures) > bestRoot.GetScore(doPowerOfTwoTextures)) { bestRoot = pr; } } else { numWIterations++; steppedWidth = StepWidthHeight(steppedWidth, stepW, maxDimension); if (LOG_LEVEL >= MBLogLevel.debug) { MBLog.LogDebug("increasing Width h=" + steppedHeight + " w=" + steppedWidth); } } } steppedHeight = StepWidthHeight(steppedHeight, stepH, maxDimension); if (LOG_LEVEL >= MBLogLevel.debug) { MBLog.LogDebug("increasing Height h=" + steppedHeight + " w=" + steppedWidth); } } outW = 0; outH = 0; if (doPowerOfTwoTextures) { outW = Mathf.Min(CeilToNearestPowerOfTwo(bestRoot.w), maxDimension); outH = Mathf.Min(CeilToNearestPowerOfTwo(bestRoot.h), maxDimension); 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 = bestRoot.w; outH = bestRoot.h; } if (bestRoot == null) { return(null); } if (LOG_LEVEL >= MBLogLevel.debug) { MBLog.LogDebug("Best fit found: atlasW=" + outW + " atlasH" + outH + " w=" + bestRoot.w + " h=" + bestRoot.h + " efficiency=" + bestRoot.efficiency + " squareness=" + bestRoot.squareness + " fits in max dimension=" + bestRoot.fitsInMaxSize); } List <Image> images = new List <Image>(); flattenTree(bestRoot.root, images); images.Sort(new ImgIDComparer()); if (images.Count != imgsToAdd.Length) { Debug.LogError("Result images not the same lentgh as source"); } //scale images if too large int newMinSizeX = minImageSizeX; int newMinSizeY = minImageSizeY; bool redoPacking = false; float padX = (float)padding / (float)outW; if (bestRoot.w > maxDimension) { //float minSizeX = ((float)minImageSizeX + 1) / maxDimension; padX = (float)padding / (float)maxDimension; float scaleFactor = (float)maxDimension / (float)bestRoot.w; if (LOG_LEVEL >= MBLogLevel.warn) { Debug.LogWarning("Packing exceeded atlas width shrinking to " + scaleFactor); } for (int i = 0; i < images.Count; i++) { Image im = images[i]; if (im.w * scaleFactor < masterImageSizeX) //check if small images will be rounded too small. If so need to redo packing forcing a larger min size { if (LOG_LEVEL >= MBLogLevel.debug) { Debug.Log("Small images are being scaled to zero. Will need to redo packing with larger minTexSizeX."); } redoPacking = true; newMinSizeX = Mathf.CeilToInt(minImageSizeX / scaleFactor); } int right = (int)((im.x + im.w) * scaleFactor); im.x = (int)(scaleFactor * im.x); im.w = right - im.x; } outW = maxDimension; } float padY = (float)padding / (float)outH; if (bestRoot.h > maxDimension) { //float minSizeY = ((float)minImageSizeY + 1) / maxDimension; padY = (float)padding / (float)maxDimension; float scaleFactor = (float)maxDimension / (float)bestRoot.h; if (LOG_LEVEL >= MBLogLevel.warn) { Debug.LogWarning("Packing exceeded atlas height shrinking to " + scaleFactor); } for (int i = 0; i < images.Count; i++) { Image im = images[i]; if (im.h * scaleFactor < masterImageSizeY) //check if small images will be rounded too small. If so need to redo packing forcing a larger min size { if (LOG_LEVEL >= MBLogLevel.debug) { Debug.Log("Small images are being scaled to zero. Will need to redo packing with larger minTexSizeY."); } redoPacking = true; newMinSizeY = Mathf.CeilToInt(minImageSizeY / scaleFactor); } int bottom = (int)((im.y + im.h) * scaleFactor); im.y = (int)(scaleFactor * im.y); im.h = bottom - im.y; } outH = maxDimension; } Rect[] rs; if (!redoPacking) { rs = new Rect[images.Count]; for (int i = 0; i < images.Count; i++) { Image im = images[i]; Rect r = rs[i] = new Rect((float)im.x / (float)outW + padX, (float)im.y / (float)outH + padY, (float)im.w / (float)outW - padX * 2f, (float)im.h / (float)outH - padY * 2f); if (LOG_LEVEL >= MBLogLevel.debug) { MBLog.LogDebug("Image: " + i + " imgID=" + im.imgId + " x=" + r.x * outW + " y=" + r.y * outH + " w=" + r.width * outW + " h=" + r.height * outH + " padding=" + padding); } } } else { if (LOG_LEVEL >= MBLogLevel.debug) { Debug.Log("==================== REDOING PACKING ================"); } bestRoot = null; rs = _GetRects(imgWidthHeights, maxDimension, padding, newMinSizeX, newMinSizeY, masterImageSizeX, masterImageSizeY, out outW, out outH, recursionDepth + 1); } if (LOG_LEVEL >= MBLogLevel.debug) { MBLog.LogDebug("Done GetRects"); } return(rs); }
//------------------ Algorithm for fitting everything into one atlas and scaling down // // 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 // Explore space to find a resonably efficient packing. Grow the atlas gradually until a fit is found // Scale atlas to fit // AtlasPackingResult _GetRectsSingleAtlas(List <Vector2> imgWidthHeights, int maxDimension, int padding, int minImageSizeX, int minImageSizeY, int masterImageSizeX, int masterImageSizeY, int recursionDepth) { if (LOG_LEVEL >= MB2_LogLevel.debug) { Debug.Log(String.Format("_GetRects numImages={0}, maxDimension={1}, padding={2}, minImageSizeX={3}, minImageSizeY={4}, masterImageSizeX={5}, masterImageSizeY={6}, recursionDepth={7}", imgWidthHeights.Count, maxDimension, padding, minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY, recursionDepth)); } if (recursionDepth > 10) { if (LOG_LEVEL >= MB2_LogLevel.error) { Debug.LogError("Maximum recursion depth reached. Couldn't find packing for these textures."); } return(null); } float area = 0; int maxW = 0; int maxH = 0; Image[] imgsToAdd = new Image[imgWidthHeights.Count]; for (int i = 0; i < imgsToAdd.Length; i++) { int iw = (int)imgWidthHeights[i].x; int ih = (int)imgWidthHeights[i].y; Image im = imgsToAdd[i] = new Image(i, iw, ih, padding, minImageSizeX, minImageSizeY); area += im.w * im.h; maxW = Mathf.Max(maxW, im.w); maxH = Mathf.Max(maxH, im.h); } if ((float)maxH / (float)maxW > 2) { if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Using height Comparer"); } Array.Sort(imgsToAdd, new ImageHeightComparer()); } else if ((float)maxH / (float)maxW < .5) { if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Using width Comparer"); } Array.Sort(imgsToAdd, new ImageWidthComparer()); } else { if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Using area Comparer"); } Array.Sort(imgsToAdd, new ImageAreaComparer()); } //explore the space to find a resonably efficient packing int sqrtArea = (int)Mathf.Sqrt(area); int idealAtlasW; int idealAtlasH; if (doPowerOfTwoTextures) { idealAtlasW = idealAtlasH = RoundToNearestPositivePowerOfTwo(sqrtArea); if (maxW > idealAtlasW) { idealAtlasW = CeilToNearestPowerOfTwo(idealAtlasW); } if (maxH > idealAtlasH) { idealAtlasH = CeilToNearestPowerOfTwo(idealAtlasH); } } else { idealAtlasW = sqrtArea; idealAtlasH = sqrtArea; if (maxW > sqrtArea) { idealAtlasW = maxW; idealAtlasH = Mathf.Max(Mathf.CeilToInt(area / maxW), maxH); } if (maxH > sqrtArea) { idealAtlasW = Mathf.Max(Mathf.CeilToInt(area / maxH), maxW); idealAtlasH = maxH; } } if (idealAtlasW == 0) { idealAtlasW = 4; } if (idealAtlasH == 0) { idealAtlasH = 4; } int stepW = (int)(idealAtlasW * .15f); int stepH = (int)(idealAtlasH * .15f); if (stepW == 0) { stepW = 1; } if (stepH == 0) { stepH = 1; } int numWIterations = 2; int steppedWidth = idealAtlasW; int steppedHeight = idealAtlasH; while (numWIterations >= 1 && steppedHeight < sqrtArea * 1000) { bool successW = false; numWIterations = 0; steppedWidth = idealAtlasW; while (!successW && steppedWidth < sqrtArea * 1000) { ProbeResult pr = new ProbeResult(); if (LOG_LEVEL >= MB2_LogLevel.trace) { Debug.Log("Probing h=" + steppedHeight + " w=" + steppedWidth); } if (ProbeSingleAtlas(imgsToAdd, steppedWidth, steppedHeight, area, maxDimension, pr)) { successW = true; if (bestRoot == null) { bestRoot = pr; } else if (pr.GetScore(doPowerOfTwoTextures) > bestRoot.GetScore(doPowerOfTwoTextures)) { bestRoot = pr; } } else { numWIterations++; steppedWidth = StepWidthHeight(steppedWidth, stepW, maxDimension); if (LOG_LEVEL >= MB2_LogLevel.trace) { MB2_Log.LogDebug("increasing Width h=" + steppedHeight + " w=" + steppedWidth); } } } steppedHeight = StepWidthHeight(steppedHeight, stepH, maxDimension); if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("increasing Height h=" + steppedHeight + " w=" + steppedWidth); } } if (bestRoot == null) { return(null); } int outW = 0; int outH = 0; if (doPowerOfTwoTextures) { outW = Mathf.Min(CeilToNearestPowerOfTwo(bestRoot.w), maxDimension); outH = Mathf.Min(CeilToNearestPowerOfTwo(bestRoot.h), maxDimension); 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 = Mathf.Min(bestRoot.w, maxDimension); outH = Mathf.Min(bestRoot.h, maxDimension); } bestRoot.outW = outW; bestRoot.outH = outH; if (LOG_LEVEL >= MB2_LogLevel.debug) { Debug.Log("Best fit found: atlasW=" + outW + " atlasH" + outH + " w=" + bestRoot.w + " h=" + bestRoot.h + " efficiency=" + bestRoot.efficiency + " squareness=" + bestRoot.squareness + " fits in max dimension=" + bestRoot.largerOrEqualToMaxDim); } //Debug.Assert(images.Count != imgsToAdd.Length, "Result images not the same lentgh as source")); //the atlas can be larger than the max dimension scale it if this is the case //int newMinSizeX = minImageSizeX; //int newMinSizeY = minImageSizeY; List <Image> images = new List <Image>(); flattenTree(bestRoot.root, images); images.Sort(new ImgIDComparer()); // the atlas may be packed larger than the maxDimension. If so then the atlas needs to be scaled down to fit AtlasPackingResult res = ScaleAtlasToFitMaxDim(bestRoot, imgWidthHeights, images, maxDimension, padding, minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY, outW, outH, recursionDepth); if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug(String.Format("Done GetRects atlasW={0} atlasH={1}", bestRoot.w, bestRoot.h)); } return(res); }
public Rect[] GetRects(List <Vector2> imgWidthHeights, int maxDimension, int padding, out int outW, out int outH) { float area = 0; int maxW = 0; int maxH = 0; Image[] imgsToAdd = new Image[imgWidthHeights.Count]; for (int i = 0; i < imgsToAdd.Length; i++) { Image im = imgsToAdd[i] = new Image(i, (int)imgWidthHeights[i].x, (int)imgWidthHeights[i].y, padding); area += im.w * im.h; maxW = Mathf.Max(maxW, im.w); maxH = Mathf.Max(maxH, im.h); } if ((float)maxH / (float)maxW > 2) { if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Using height Comparer"); } Array.Sort(imgsToAdd, new ImageHeightComparer()); } else if ((float)maxH / (float)maxW < .5) { if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Using width Comparer"); } Array.Sort(imgsToAdd, new ImageWidthComparer()); } else { if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Using area Comparer"); } Array.Sort(imgsToAdd, new ImageAreaComparer()); } // List<Node> ns = new List<Node>(); //explore the space to find a resonably efficient packing int sqrtArea = (int)Mathf.Sqrt(area); int idealAtlasW = sqrtArea; int idealAtlasH = sqrtArea; if (maxW > sqrtArea) { idealAtlasW = maxW; idealAtlasH = Mathf.Max(Mathf.CeilToInt(area / maxW), maxH); } if (maxH > sqrtArea) { idealAtlasW = Mathf.Max(Mathf.CeilToInt(area / maxH), maxW); idealAtlasH = maxH; } if (idealAtlasW == 0) { idealAtlasW = 1; } if (idealAtlasH == 0) { idealAtlasH = 1; } int stepW = (int)(idealAtlasW * .15f); int stepH = (int)(idealAtlasH * .15f); if (stepW == 0) { stepW = 1; } if (stepH == 0) { stepH = 1; } // bool doStepHeight = true; // bool successH = false; int numWIterations = 2; int steppedHeight = idealAtlasH; while (numWIterations > 1 && steppedHeight < sqrtArea * 1000) { bool successW = false; numWIterations = 0; int steppedWidth = idealAtlasW; while (!successW && steppedWidth < sqrtArea * 1000) { ProbeResult pr = new ProbeResult(); if (Probe(imgsToAdd, steppedWidth, steppedHeight, area, maxDimension, pr)) { successW = true; if (bestRoot == null) { bestRoot = pr; } else if (pr.GetScore() > bestRoot.GetScore()) { bestRoot = pr; } } else { numWIterations++; steppedWidth += stepW; if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("increasing Width h=" + steppedHeight + " w=" + steppedWidth); } } } steppedHeight += stepH; if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("increasing Height h=" + steppedHeight + " w=" + steppedWidth); } } outW = 0; outH = 0; if (bestRoot == null) { return(null); } if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Best fit found: w=" + bestRoot.w + " h=" + bestRoot.h + " efficiency=" + bestRoot.efficiency + " squareness=" + bestRoot.squareness + " fits in max dimension=" + bestRoot.fitsInMaxSize); } outW = bestRoot.w; outH = bestRoot.h; List <Image> images = new List <Image>(); flattenTree(bestRoot.root, images); images.Sort(new ImgIDComparer()); if (images.Count != imgsToAdd.Length) { Debug.LogError("Result images not the same lentgh as source"); } //scale images if too large float padX = (float)padding / (float)bestRoot.w; if (bestRoot.w > maxDimension) { padX = (float)padding / (float)maxDimension; float scaleFactor = (float)maxDimension / (float)bestRoot.w; if (LOG_LEVEL >= MB2_LogLevel.warn) { Debug.LogWarning("Packing exceeded atlas width shrinking to " + scaleFactor); } for (int i = 0; i < images.Count; i++) { Image im = images[i]; int right = (int)((im.x + im.w) * scaleFactor); im.x = (int)(scaleFactor * im.x); im.w = right - im.x; if (im.w == 0) { Debug.LogError("rounding scaled image w to zero"); } } outW = maxDimension; } float padY = (float)padding / (float)bestRoot.h; if (bestRoot.h > maxDimension) { padY = (float)padding / (float)maxDimension; float scaleFactor = (float)maxDimension / (float)bestRoot.h; if (LOG_LEVEL >= MB2_LogLevel.warn) { Debug.LogWarning("Packing exceeded atlas height shrinking to " + scaleFactor); } for (int i = 0; i < images.Count; i++) { Image im = images[i]; int bottom = (int)((im.y + im.h) * scaleFactor); im.y = (int)(scaleFactor * im.y); im.h = bottom - im.y; if (im.h == 0) { Debug.LogError("rounding scaled image h to zero"); } } outH = maxDimension; } Rect[] rs = new Rect[images.Count]; for (int i = 0; i < images.Count; i++) { Image im = images[i]; Rect r = rs[i] = new Rect((float)im.x / (float)outW + padX, (float)im.y / (float)outH + padY, (float)im.w / (float)outW - padX * 2, (float)im.h / (float)outH - padY * 2); if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Image: " + i + " imgID=" + im.imgId + " x=" + r.x * outW + " y=" + r.y * outH + " w=" + r.width * outW + " h=" + r.height * outH + " padding=" + padding); } } if (LOG_LEVEL >= MB2_LogLevel.debug) { MB2_Log.LogDebug("Done GetRects"); } return(rs); }
//------------------ Algorithm for fitting everything into one atlas and scaling down // // 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 // Explore space to find a resonably efficient packing. Grow the atlas gradually until a fit is found // Scale atlas to fit // AtlasPackingResult _GetRectsSingleAtlas(List <Vector2> imgWidthHeights, List <AtlasPadding> paddings, int maxDimensionX, int maxDimensionY, int minImageSizeX, int minImageSizeY, int masterImageSizeX, int masterImageSizeY, int recursionDepth) { Debug.Log(string.Format("_GetRects 图片数量 ={0}, 最大尺寸X ={1}, 最小尺寸 X={2}, 最小尺寸 Y ={3}, masterImageSizeX={4}, masterImageSizeY={5}, 递归深度 = {6}", imgWidthHeights.Count, maxDimensionX, minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY, recursionDepth)); if (recursionDepth > 10) { //最大递归深度设定为 10 Debug.LogError("Maximum recursion depth reached. Couldn't find packing for these textures."); return(null); } float allImageTotalArea = 0; int maxW = 0; int maxH = 0; ImageAreaInAtlas[] imgsToAdd = new ImageAreaInAtlas[imgWidthHeights.Count]; for (int i = 0; i < imgsToAdd.Length; i++) { int iw = (int)imgWidthHeights[i].x; int ih = (int)imgWidthHeights[i].y; ImageAreaInAtlas im = imgsToAdd[i] = new ImageAreaInAtlas(i, iw, ih, paddings[i], minImageSizeX, minImageSizeY); allImageTotalArea += im.w * im.h; maxW = Mathf.Max(maxW, im.w); maxH = Mathf.Max(maxH, im.h); } if ((float)maxH / (float)maxW > 2) { Array.Sort(imgsToAdd, new ImageHeightComparer()); } else if ((float)maxH / (float)maxW < .5) { Array.Sort(imgsToAdd, new ImageWidthComparer()); } else { Array.Sort(imgsToAdd, new ImageAreaComparer()); } //explore the space to find a resonably efficient packing //探索图片空间以找到合理有效的包装 int sqrtArea = (int)Mathf.Sqrt(allImageTotalArea); int idealAtlasW; int idealAtlasH; if (atlasMustBePowerOfTwo) { idealAtlasW = idealAtlasH = RoundToNearestPositivePowerOfTwo(sqrtArea); if (maxW > idealAtlasW) { idealAtlasW = CeilToNearestPowerOfTwo(idealAtlasW); } if (maxH > idealAtlasH) { idealAtlasH = CeilToNearestPowerOfTwo(idealAtlasH); } } else { idealAtlasW = sqrtArea; idealAtlasH = sqrtArea; if (maxW > sqrtArea) { idealAtlasW = maxW; idealAtlasH = Mathf.Max(Mathf.CeilToInt(allImageTotalArea / maxW), maxH); } if (maxH > sqrtArea) { idealAtlasW = Mathf.Max(Mathf.CeilToInt(allImageTotalArea / maxH), maxW); idealAtlasH = maxH; } } if (idealAtlasW == 0) { idealAtlasW = 4; } if (idealAtlasH == 0) { idealAtlasH = 4; } int stepW = (int)(idealAtlasW * .15f); int stepH = (int)(idealAtlasH * .15f); if (stepW == 0) { stepW = 1; } if (stepH == 0) { stepH = 1; } int numWIterations = 2; int steppedWidth = idealAtlasW; int steppedHeight = idealAtlasH; while (numWIterations >= 1 && steppedHeight < sqrtArea * 1000) { bool successW = false; numWIterations = 0; steppedWidth = idealAtlasW; while (!successW && steppedWidth < sqrtArea * 1000) { ProbeResult pr = new ProbeResult(); Debug.Log("Probing h=" + steppedHeight + " w=" + steppedWidth); if (ProbeSingleAtlas(imgsToAdd, steppedWidth, steppedHeight, allImageTotalArea, maxDimensionX, maxDimensionY, pr)) { successW = true; if (bestRoot == null) { bestRoot = pr; } else if (pr.GetScore(atlasMustBePowerOfTwo) > bestRoot.GetScore(atlasMustBePowerOfTwo)) { bestRoot = pr; } } else { numWIterations++; steppedWidth = SetStepWidthHeight(steppedWidth, stepW, maxDimensionX); Debug.Log("增加 Width h=" + steppedHeight + " w=" + steppedWidth); } } steppedHeight = SetStepWidthHeight(steppedHeight, stepH, maxDimensionY); Debug.Log("增加 Height h=" + steppedHeight + " w=" + steppedWidth); } if (bestRoot == null) { return(null); } int outW = 0; int outH = 0; if (atlasMustBePowerOfTwo) { outW = Mathf.Min(CeilToNearestPowerOfTwo(bestRoot.w), maxDimensionX); outH = Mathf.Min(CeilToNearestPowerOfTwo(bestRoot.h), maxDimensionY); 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 = Mathf.Min(bestRoot.w, maxDimensionX); outH = Mathf.Min(bestRoot.h, maxDimensionY); } bestRoot.outW = outW; bestRoot.outH = outH; Debug.Log("Best fit found: atlasW=" + outW + " atlasH" + outH + " w=" + bestRoot.w + " h=" + bestRoot.h + " efficiency=" + bestRoot.efficiency + " squareness=" + bestRoot.squareness + " fits in max dimension=" + bestRoot.largerOrEqualToMaxDim); //Debug.Assert(images.Count != imgsToAdd.Length, "Result images not the same lentgh as source")); //the atlas can be larger than the max dimension scale it if this is the case //int newMinSizeX = minImageSizeX; //int newMinSizeY = minImageSizeY; List <ImageAreaInAtlas> images = new List <ImageAreaInAtlas>(); flattenTree(bestRoot.root, images); images.Sort(new ImgIDComparer()); // the atlas may be packed larger than the maxDimension. If so then the atlas needs to be scaled down to fit Vector2 rootWH = new Vector2(bestRoot.w, bestRoot.h); float padX, padY; int newMinSizeX, newMinSizeY; if (!ScaleAtlasToFitMaxDim(rootWH, images, maxDimensionX, maxDimensionY, paddings[0], minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY, ref outW, ref outH, out padX, out padY, out newMinSizeX, out newMinSizeY)) { AtlasPackingResult res = new AtlasPackingResult(paddings.ToArray()); res.rects = new Rect[images.Count]; res.srcImgIdxs = new int[images.Count]; res.atlasX = outW; res.atlasY = outH; res.usedW = -1; res.usedH = -1; for (int i = 0; i < images.Count; i++) { ImageAreaInAtlas im = images[i]; Rect r = res.rects[i] = new Rect((float)im.x / (float)outW + padX, (float)im.y / (float)outH + padY, (float)im.w / (float)outW - padX * 2f, (float)im.h / (float)outH - padY * 2f); res.srcImgIdxs[i] = im.imgId; Debug.Log("Image: " + i + " imgID=" + im.imgId + " x=" + r.x * outW + " y=" + r.y * outH + " w=" + r.width * outW + " h=" + r.height * outH + " padding=" + paddings[i]); } res.CalcUsedWidthAndHeight(); return(res); } else { Debug.Log("==================== REDOING PACKING ================"); //root = null; return(_GetRectsSingleAtlas(imgWidthHeights, paddings, maxDimensionX, maxDimensionY, newMinSizeX, newMinSizeY, masterImageSizeX, masterImageSizeY, recursionDepth + 1)); } //Debug.Log(String.Format("Done GetRects atlasW={0} atlasH={1}", bestRoot.w, bestRoot.h)); //return res; }