// Returns number of remaining rects
public int Build()
{
// Initialize
atlases = new List <Data>();
remainingRectIndices = new List <int>();
bool[] usedRect = new bool[sourceRects.Count];
int atlasWidth = this.atlasWidth >> alignShift;
int atlasHeight = this.atlasHeight >> alignShift;
// Sanity check, can't build with textures larger than the actual max atlas size
int align = (1 << alignShift) - 1;
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) + align) >> alignShift;
int minDim = (Math.Min(rs.width, rs.height) + align) >> alignShift;
// 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>();
foreach (RectSize rs in sourceRects)
{
RectSize t = new RectSize();
t.width = (rs.width + align) >> alignShift;
t.height = (rs.height + align) >> alignShift;
rects.Add(t);
}
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 || !allowOptimizeSize)
{
List <Entry> atlasEntries = new List <Entry>();
foreach (var t in binPacker.GetMapped())
{
int matchedWidth = 0;
int matchedHeight = 0;
int matchedId = -1;
bool flipped = false;
for (int i = 0; i < sourceRects.Count; ++i)
{
int width = (sourceRects[i].width + align) >> alignShift;
int height = (sourceRects[i].height + align) >> alignShift;
if (!usedRect[i] && width == t.width && height == t.height)
{
matchedId = i;
matchedWidth = sourceRects[i].width;
matchedHeight = sourceRects[i].height;
break;
}
}
// Not matched anything yet, so look for the same rects rotated
if (matchedId == -1)
{
for (int i = 0; i < sourceRects.Count; ++i)
{
int width = (sourceRects[i].width + align) >> alignShift;
int height = (sourceRects[i].height + align) >> alignShift;
if (!usedRect[i] && width == t.height && height == t.width)
{
matchedId = i;
flipped = true;
matchedWidth = sourceRects[i].height;
matchedHeight = sourceRects[i].width;
break;
}
}
}
// If this fails its a catastrophic error
usedRect[matchedId] = true;
Entry newEntry = new Entry();
newEntry.flipped = flipped;
newEntry.x = t.x << alignShift;
newEntry.y = t.y << alignShift;
newEntry.w = matchedWidth;
newEntry.h = matchedHeight;
newEntry.index = matchedId;
atlasEntries.Add(newEntry);
}
Data currAtlas = new Data();
currAtlas.width = thisCellW << alignShift;
currAtlas.height = thisCellH << alignShift;
currAtlas.occupancy = binPacker.Occupancy();
currAtlas.entries = atlasEntries.ToArray();
atlases.Add(currAtlas);
rects = currRects;
break; // done
}
else
{
if (!allUsed)
{
if (forceSquare)
{
thisCellW *= 2;
thisCellH *= 2;
}
else
{
// 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
{
if (forceSquare)
{
thisCellH /= 2;
thisCellW /= 2;
}
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);
}