using System.Collections.Generic;
namespace FontBuilder
{
internal static class BspSubdivision
{
///
/// Attempt to place textures into the space defined by Dimensions until no more can be fit.
/// Subdivide the space after each insertion. Assume textures are sorted by size.
///
///
///
internal static void TryPlaceGlyphs(Rectangle Dimensions, List Textures)
{
Glyph tex = null;
//Find largest entry that fits within the dimensions.
for (int i = 0; i < Textures.Count; ++i)
{
var Entry = Textures[i];
if (Entry.Width > Dimensions.Width || Entry.Height > Dimensions.Height)
continue;
Textures.RemoveAt(i);
tex = Entry;
break;
}
// Quit if nothing fit.
if (tex == null) return;
tex.X = Dimensions.X;
tex.Y = Dimensions.Y;
//Subdivide remaining space.
int HorizontalDifference = Dimensions.Width - tex.Width;
int VerticalDifference = Dimensions.Height - tex.Height;
if (HorizontalDifference == 0 && VerticalDifference == 0) //Perfect fit!
return;
Rectangle? ASpace = null;
Rectangle? BSpace = null;
// Subdivide the space on the shortest axis of the texture we just placed.
if (HorizontalDifference >= VerticalDifference)
{
ASpace = new Rectangle(Dimensions.X + tex.Width, Dimensions.Y, HorizontalDifference, Dimensions.Height);
// Remember that this isn't a perfect split - a chunk belongs to the placed texture.
if (VerticalDifference > 0)
BSpace = new Rectangle(Dimensions.X, Dimensions.Y + tex.Height, tex.Width, VerticalDifference);
}
else
{
ASpace = new Rectangle(Dimensions.X, Dimensions.Y + tex.Height, Dimensions.Width, VerticalDifference);
if (HorizontalDifference > 0)
BSpace = new Rectangle(Dimensions.X + tex.Width, Dimensions.Y, HorizontalDifference, tex.Height);
}
TryPlaceGlyphs(ASpace.Value, Textures);
if (BSpace.HasValue) TryPlaceGlyphs(BSpace.Value, Textures);
}
///
/// Attempt to fit textures into working space, and if any are left, double vertical size of working space.
///
///
///
///
///
internal static Rectangle ExpandVertical(Rectangle TotalArea, Rectangle WorkingArea, List Textures)
{
TryPlaceGlyphs(WorkingArea, Textures);
if (Textures.Count > 0)
{
WorkingArea = new Rectangle(0, TotalArea.Height, TotalArea.Width, TotalArea.Height);
TotalArea.Height *= 2;
return ExpandHorizontal(TotalArea, WorkingArea, Textures);
}
else
return TotalArea;
}
///
/// Attempt to fit textures into working space, and if any are left, double horizontal size of working space.
///
///
///
///
///
internal static Rectangle ExpandHorizontal(Rectangle TotalArea, Rectangle WorkingArea, List Textures)
{
TryPlaceGlyphs(WorkingArea, Textures);
if (Textures.Count > 0)
{
WorkingArea = new Rectangle(TotalArea.Width, 0, TotalArea.Width, TotalArea.Height);
TotalArea.Width *= 2;
return ExpandVertical(TotalArea, WorkingArea, Textures);
}
else
return TotalArea;
}
}
public class AtlasCompiler
{
public static Atlas Compile(List Entries)
{
Entries.Sort((A, B) =>
{
return (B.Width * B.Height) - (A.Width * A.Height);
});
// Find smallest power of 2 sized texture that can hold the largest entry.
var largestEntry = Entries[0];
var texSize = new Rectangle(0, 0, 1, 1);
while (texSize.Width < largestEntry.Width)
texSize.Width *= 2;
while (texSize.Height < largestEntry.Height)
texSize.Height *= 2;
// Be sure to pass a copy of the list since the algorithm modifies it.
texSize = BspSubdivision.ExpandHorizontal(texSize, texSize, new List(Entries));
return new Atlas { Dimensions = texSize, Glyphs = Entries };
}
}
}