Newer
Older
exporter / flash / frame / FramePacker.as
package frame
{
	import flash.geom.*;
	import flash.display.*;
	import util.*;
	public class FramePacker
	{
		public const MaxSize:int = 2048;
		private var sheets:Array;	// [RectanglePacker]
		private var frames:Array;	// [FrameInfo]
		
		public function GetSheets():Array
		{
			return sheets;
		}
		
		public function FramePacker(frames:Array)
		{
			this.frames = frames.filter(function(i:FrameInfo, index, arr) { return i.frameData != null; }, this);
			this.frames.sortOn("frameData", Array.DESCENDING, function(a:BitmapData, b:BitmapData) { return (a.width * a.height) - (b.width - b.height); });
		}
		
		private function GetMaxSize(frames:Array):Point
		{
			if (frames.length == 0) return new Point();
			return new Point(Math.max.apply(Math, frames.map(function(i:FrameInfo, index, arr) { return i.frameData.width; }, this)),
							 Math.max.apply(Math, frames.map(function(i:FrameInfo, index, arr) { return i.frameData.height; }, this)));
		}
		
		private function GetArea(frames:Array):int
		{
			var area = 0;
			for (var i in frames)
			{
				if (frames[i] is FrameInfo)
				{
					area += frames[i].frameData.width * frames[i].frameData.height;
				}
				if (frames[i] is BitmapData || frames[i] is Rectangle)
				{
					area += frames[i].width * frames[i].height;
				}
			}
			return area;
		}
		
		public const AreaUsedMultiplier = 1.3;		// guess how much more sheet space we use than actual space taken up by frames
		
		private var callback;
		public function Pack(callback)
		{
			this.callback = callback;
			sheets = new Array();
			var index = 0;
			while (index < frames.length)
			{
				var now:Array = frames.slice(index);
				
				var max:Point = GetMaxSize(now);
				var area:int = GetArea(now);
				
				var exponent = Math.floor(Math.log(Math.sqrt(area * AreaUsedMultiplier)) / Math.log(2));
				var sizeX:int = Math.min(MaxSize, Math.pow(2, exponent));
				var sizeY:int = sizeX;
				
				var i;
				
				var startIndex = index;
				var nodes:Array = new Array();
				while (startIndex == index)
				{
					nodes.length = 0;
					var sheet:RectanglePacker = new RectanglePacker(sizeX, sizeY);
					//var sorted:Array = now.sortOn("frameData", Array.RETURNINDEXEDARRAY | Array.DESCENDING, function(a:BitmapData, b:BitmapData) { return (a.width * a.height) - (b.width - b.height); });
					
					//for (var sortedI = 0; sortedI < now.length; sortedI++)
					for (i = 0; i < now.length; i++)
					{
						//i = sorted[sortedI];
						var node:RectangleNode = sheet.Insert(now[i].frameData.width, now[i].frameData.height);
						if (node == null)
						{
							break;
						}
						nodes.push(node);
					}
					if (nodes.length == now.length)
					{
						// stop the loop, attempt the 75% check here
						index += nodes.length;
					}
					else
					{
						if (sizeX == sizeY)
						{
							sizeX *= 2;
						}
						else
						{
							sizeY *= 2;
						}
						if (sizeX > MaxSize || sizeY > MaxSize)
						{
							if (nodes.length == 0)
							{
								Exporter.Instance.Print("Failure: item too large to pack on sheet");
								callback(false);
								return;
							}
							index += nodes.length;
						}
					}
				}

				for (i = startIndex; i < index; i++)
				{
					frames[i].sheetIndex = sheets.length;
					frames[i].rect = nodes[i - startIndex].Rect;
				}
				sheets.push(sheet);
			}
			
			callback(true);
		}
	}
}