MaxRectsBinPack FindBestBinPacker(int width, int height, ref List <RectSize> currRects, ref bool allUsed) { List <MaxRectsBinPack> binPackers = new List <MaxRectsBinPack>(); List <List <RectSize> > binPackerRects = new List <List <RectSize> >(); List <bool> binPackerAllUsed = new List <bool>(); //MaxRectsBinPack.FreeRectChoiceHeuristic[] heuristics = { MaxRectsBinPack.FreeRectChoiceHeuristic.RectBestAreaFit, // MaxRectsBinPack.FreeRectChoiceHeuristic.RectBestLongSideFit, // MaxRectsBinPack.FreeRectChoiceHeuristic.RectBestShortSideFit, // MaxRectsBinPack.FreeRectChoiceHeuristic.RectBottomLeftRule, // MaxRectsBinPack.FreeRectChoiceHeuristic.RectContactPointRule }; MaxRectsBinPack.FreeRectChoiceHeuristic[] heuristics = { MaxRectsBinPack.FreeRectChoiceHeuristic.RectBestAreaFit, MaxRectsBinPack.FreeRectChoiceHeuristic.RectBestLongSideFit, MaxRectsBinPack.FreeRectChoiceHeuristic.RectBestShortSideFit, MaxRectsBinPack.FreeRectChoiceHeuristic.RectBottomLeftRule, }; foreach (var heuristic in heuristics) { MaxRectsBinPack binPacker = new MaxRectsBinPack(width, height); List <RectSize> activeRects = new List <RectSize>(currRects); bool activeAllUsed = binPacker.Insert(activeRects, heuristic); binPackers.Add(binPacker); binPackerRects.Add(activeRects); binPackerAllUsed.Add(activeAllUsed); } int leastWastedPixels = Int32.MaxValue; int leastWastedIndex = -1; for (int i = 0; i < binPackers.Count; ++i) { int wastedPixels = binPackers[i].WastedBinArea(); if (wastedPixels < leastWastedPixels) { leastWastedPixels = wastedPixels; leastWastedIndex = i; oversizeTextures = true; } } currRects = binPackerRects[leastWastedIndex]; allUsed = binPackerAllUsed[leastWastedIndex]; return(binPackers[leastWastedIndex]); }
// Returns number of remaining rects public int Build() { // Initialize atlases = new List <AtlasData>(); remainingRectIndices = new List <int>(); bool[] usedRect = new bool[sourceRects.Count]; // Sanity check, can't build with textures larger than the actual max atlas size int minSize = Math.Min(atlasWidth, atlasHeight); int maxSize = Math.Max(atlasWidth, atlasHeight); foreach (RectSize rs in sourceRects) { int maxDim = Math.Max(rs.width, rs.height); int minDim = Math.Min(rs.width, rs.height); // largest texture needs to fit in an atlas if (maxDim > maxSize || (maxDim <= maxSize && minDim > minSize)) { remainingRectIndices = new List <int>(); for (int i = 0; i < sourceRects.Count; ++i) { remainingRectIndices.Add(i); } return(remainingRectIndices.Count); } } // Start with all source rects, this list will get reduced over time List <RectSize> rects = new List <RectSize>(sourceRects); bool allUsed = false; while (allUsed == false && atlases.Count < maxAllowedAtlasCount) { int numPasses = 1; int thisCellW = atlasWidth, thisCellH = atlasHeight; bool reverted = false; while (numPasses > 0) { // Create copy to make sure we can scale textures down when necessary List <RectSize> currRects = new List <RectSize>(rects); // MaxRectsBinPack binPacker = new MaxRectsBinPack(thisCellW, thisCellH); // allUsed = binPacker.Insert(currRects, MaxRectsBinPack.FreeRectChoiceHeuristic.RectBestAreaFit); MaxRectsBinPack binPacker = FindBestBinPacker(thisCellW, thisCellH, ref currRects, ref allUsed); float occupancy = binPacker.Occupancy(); // Consider the atlas resolved when after the first pass, all textures are used, and the occupancy > 0.5f, scaling // down by half to maintain PO2 requirements means this is as good as it gets bool firstPassFull = numPasses == 1 && occupancy > 0.5f; // Reverted copes with the case when halving the atlas size when occupancy < 0.5f, the textures don't fit in the // atlas anymore. At this point, size is reverted to the previous value, and the loop should accept this as the final value if (firstPassFull || (numPasses > 1 && occupancy > 0.5f && allUsed) || reverted) { List <AtlasEntry> atlasEntries = new List <AtlasEntry>(); foreach (var t in binPacker.GetMapped()) { int matchedId = -1; bool flipped = false; for (int i = 0; i < sourceRects.Count; ++i) { if (!usedRect[i] && sourceRects[i].width == t.width && sourceRects[i].height == t.height) { matchedId = i; break; } } // Not matched anything yet, so look for the same rects rotated if (matchedId == -1) { for (int i = 0; i < sourceRects.Count; ++i) { if (!usedRect[i] && sourceRects[i].width == t.height && sourceRects[i].height == t.width) { matchedId = i; flipped = true; break; } } } // If this fails its a catastrophic error usedRect[matchedId] = true; AtlasEntry newEntry = new AtlasEntry(); newEntry.flipped = flipped; newEntry.x = t.x; newEntry.y = t.y; newEntry.w = t.width; newEntry.h = t.height; newEntry.index = matchedId; atlasEntries.Add(newEntry); } AtlasData currAtlas = new AtlasData(); currAtlas.width = thisCellW; currAtlas.height = thisCellH; currAtlas.occupancy = binPacker.Occupancy(); currAtlas.entries = atlasEntries.ToArray(); atlases.Add(currAtlas); rects = currRects; break; // done } else { if (!allUsed) { // Can only try another size when it already has been scaled down for the first time if (thisCellW < atlasWidth && thisCellH < atlasHeight) { // Tried to scale down, but the texture doesn't fit, so revert previous change, and // iterate over the data again forcing a pass even though there is wastage if (thisCellW < thisCellH) { thisCellW *= 2; } else { thisCellH *= 2; } } reverted = true; } else { // More than half the texture was unused, scale down by one of the dimensions if (thisCellW < thisCellH) { thisCellH /= 2; } else { thisCellW /= 2; } } numPasses++; } } } remainingRectIndices = new List <int>(); for (int i = 0; i < usedRect.Length; ++i) { if (!usedRect[i]) { remainingRectIndices.Add(i); } } return(remainingRectIndices.Count); }
MaxRectsBinPack FindBestBinPacker(int width, int height, ref List<RectSize> currRects, ref bool allUsed) { List<MaxRectsBinPack> binPackers = new List<MaxRectsBinPack>(); List<List<RectSize>> binPackerRects = new List<List<RectSize>>(); List<bool> binPackerAllUsed = new List<bool>(); //MaxRectsBinPack.FreeRectChoiceHeuristic[] heuristics = { MaxRectsBinPack.FreeRectChoiceHeuristic.RectBestAreaFit, // MaxRectsBinPack.FreeRectChoiceHeuristic.RectBestLongSideFit, // MaxRectsBinPack.FreeRectChoiceHeuristic.RectBestShortSideFit, // MaxRectsBinPack.FreeRectChoiceHeuristic.RectBottomLeftRule, // MaxRectsBinPack.FreeRectChoiceHeuristic.RectContactPointRule }; MaxRectsBinPack.FreeRectChoiceHeuristic[] heuristics = { MaxRectsBinPack.FreeRectChoiceHeuristic.RectBestAreaFit, MaxRectsBinPack.FreeRectChoiceHeuristic.RectBestLongSideFit, MaxRectsBinPack.FreeRectChoiceHeuristic.RectBestShortSideFit, MaxRectsBinPack.FreeRectChoiceHeuristic.RectBottomLeftRule, }; foreach (var heuristic in heuristics) { MaxRectsBinPack binPacker = new MaxRectsBinPack(width, height); List<RectSize> activeRects = new List<RectSize>(currRects); bool activeAllUsed = binPacker.Insert(activeRects, heuristic); binPackers.Add(binPacker); binPackerRects.Add(activeRects); binPackerAllUsed.Add(activeAllUsed); } int leastWastedPixels = Int32.MaxValue; int leastWastedIndex = -1; for (int i = 0; i < binPackers.Count; ++i) { int wastedPixels = binPackers[i].WastedBinArea(); if (wastedPixels < leastWastedPixels) { leastWastedPixels = wastedPixels; leastWastedIndex = i; oversizeTextures = true; } } currRects = binPackerRects[leastWastedIndex]; allUsed = binPackerAllUsed[leastWastedIndex]; return binPackers[leastWastedIndex]; }