예제 #1
0
        /// <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);
        }
예제 #2
0
		/// <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;
		}