/// <summary> /// packs the supplied RectItems into an atlas. Modifies the RectItems with x/y values of location in new atlas. /// </summary> public static PackedAtlasResults PackAtlas(IEnumerable <RectItem> items) { PackedAtlasResults ret = new PackedAtlasResults(); ret.Atlases.Add(new PackedAtlasResults.SingleAtlas()); //initially, we'll try all the items; none remain List <RectItem> currentItems = new List <RectItem>(items); List <RectItem> remainItems = new List <RectItem>(); RETRY: //this is where the texture size range is determined. //we run this every time we make an atlas, in case we want to variably control the maximum texture output size. //ALSO - we accumulate data in there, so we need to refresh it each time. ... lame. List <TryFitParam> todoSizes = new List <TryFitParam>(); for (int i = 3; i <= MaxSizeBits; i++) { for (int j = 3; j <= MaxSizeBits; j++) { int w = 1 << i; int h = 1 << j; TryFitParam tfp = new TryFitParam(w, h); todoSizes.Add(tfp); } } //run the packing algorithm on each potential size Parallel.ForEach(todoSizes, (param) => { var rbp = new RectangleBinPack(); rbp.Init(16384, 16384); param.rbp.Init(param.w, param.h); foreach (var ri in currentItems) { RectangleBinPack.Node node = param.rbp.Insert(ri.Width, ri.Height); if (node == null) { param.ok = false; } else { node.ri = ri; param.nodes.Add(node); } } }); //find the best fit among the potential sizes that worked long best = long.MaxValue; TryFitParam tfpFinal = null; foreach (TryFitParam tfp in todoSizes) { if (tfp.ok) { long area = (long)tfp.w * (long)tfp.h; long perimeter = (long)tfp.w + (long)tfp.h; if (area < best) { best = area; tfpFinal = tfp; } else if (area == best) { //try to minimize perimeter (to create squares, which are nicer to look at) if (tfpFinal == null) { } else if (perimeter < tfpFinal.w + tfpFinal.h) { best = area; tfpFinal = tfp; } } } } //did we find any fit? if (best == long.MaxValue) { //nope - move an item to the remaining list and try again remainItems.Add(currentItems[currentItems.Count - 1]); currentItems.RemoveAt(currentItems.Count - 1); goto RETRY; } //we found a fit. setup this atlas in the result and drop the items into it var atlas = ret.Atlases[ret.Atlases.Count - 1]; atlas.Size.Width = tfpFinal.w; atlas.Size.Height = tfpFinal.h; atlas.Items = new List <RectItem>(items); foreach (var item in currentItems) { object o = item.Item; var node = tfpFinal.nodes.Find((x) => x.ri == item); item.X = node.x; item.Y = node.y; item.TexIndex = ret.Atlases.Count - 1; } //if we have any items left, we've got to run this again if (remainItems.Count > 0) { //move all remaining items into the clear list currentItems.Clear(); currentItems.AddRange(remainItems); remainItems.Clear(); ret.Atlases.Add(new PackedAtlasResults.SingleAtlas()); goto RETRY; } if (ret.Atlases.Count > 1) { Console.WriteLine("Created animset with >1 texture ({0} textures)", ret.Atlases.Count); } return(ret); }
/// <summary> /// packs the supplied RectItems into an atlas. Modifies the RectItems with x/y values of location in new atlas. /// </summary> public static PackedAtlasResults PackAtlas(IEnumerable<RectItem> items) { PackedAtlasResults ret = new PackedAtlasResults(); ret.Atlases.Add(new PackedAtlasResults.SingleAtlas()); //initially, we'll try all the items; none remain List<RectItem> currentItems = new List<RectItem>(items); List<RectItem> remainItems = new List<RectItem>(); RETRY: //this is where the texture size range is determined. //we run this every time we make an atlas, in case we want to variably control the maximum texture output size. //ALSO - we accumulate data in there, so we need to refresh it each time. ... lame. List<TryFitParam> todoSizes = new List<TryFitParam>(); for (int i = 3; i <= MaxSizeBits; i++) { for (int j = 3; j <= MaxSizeBits; j++) { int w = 1 << i; int h = 1 << j; TryFitParam tfp = new TryFitParam(w, h); todoSizes.Add(tfp); } } //run the packing algorithm on each potential size Parallel.ForEach(todoSizes, (param) => { var rbp = new RectangleBinPack(); rbp.Init(16384, 16384); param.rbp.Init(param.w, param.h); foreach (var ri in currentItems) { RectangleBinPack.Node node = param.rbp.Insert(ri.Width, ri.Height); if (node == null) { param.ok = false; } else { node.ri = ri; param.nodes.Add(node); } } }); //find the best fit among the potential sizes that worked long best = long.MaxValue; TryFitParam tfpFinal = null; foreach (TryFitParam tfp in todoSizes) { if (tfp.ok) { long area = (long)tfp.w * (long)tfp.h; long perimeter = (long)tfp.w + (long)tfp.h; if (area < best) { best = area; tfpFinal = tfp; } else if (area == best) { //try to minimize perimeter (to create squares, which are nicer to look at) if (tfpFinal == null) { } else if (perimeter < tfpFinal.w + tfpFinal.h) { best = area; tfpFinal = tfp; } } } } //did we find any fit? if (best == long.MaxValue) { //nope - move an item to the remaining list and try again remainItems.Add(currentItems[currentItems.Count - 1]); currentItems.RemoveAt(currentItems.Count - 1); goto RETRY; } //we found a fit. setup this atlas in the result and drop the items into it var atlas = ret.Atlases[ret.Atlases.Count - 1]; atlas.Size.Width = tfpFinal.w; atlas.Size.Height = tfpFinal.h; atlas.Items = new List<RectItem>(items); foreach (var item in currentItems) { object o = item.Item; var node = tfpFinal.nodes.Find((x) => x.ri == item); item.X = node.x; item.Y = node.y; item.TexIndex = ret.Atlases.Count - 1; } //if we have any items left, we've got to run this again if (remainItems.Count > 0) { //move all remaining items into the clear list currentItems.Clear(); currentItems.AddRange(remainItems); remainItems.Clear(); ret.Atlases.Add(new PackedAtlasResults.SingleAtlas()); goto RETRY; } if (ret.Atlases.Count > 1) Console.WriteLine("Created animset with >1 texture ({0} textures)", ret.Atlases.Count); return ret; }