Newer
Older
exporter / AnimatedSkeletonGenerator.as
package
{
	import flash.display.*;
	import flash.geom.*;
	import CharacterExport.Piece;
	import CharacterExport.PieceFrameInfo;
	import CharacterExport.PieceSequence;

	public class AnimatedSkeletonGenerator implements IFramesSource
	{
		private var src:MovieClip;
		private var def:GraphicExportDef;
		private var callback;
		private var pieces:Array;
		private var pieceSequences:Array;
		public function GetFrames():Array
		{
			return [];
		}
		public function GetData()
		{
			return {};
		}
		public function AnimatedSkeletonGenerator(src:MovieClip, def:GraphicExportDef)
		{
			this.src = src;
			this.def = def;
		}
		
		private var library:PieceLibrary
		private var currentSeq:int;
		public function Go(callback)
		{
			library = new PieceLibrary();
			pieces = new Array();
			pieceSequences = new Array();
			this.callback = callback;
			for (var i = 0; i < src.totalFrames; i++)
			{
				currentSeq = i;
				Utils.WeirdGotoFrame(src, i + 1);
				Utils.RecursivelyStop(src);
				
				for (var j = 0; j < src.numChildren; j++)
				{
					var c = src.getChildAt(j);
					if (c is MovieClip)
					{
						GetAnimation(c, i);
						break;
					}
				}
			}
			
			callback(true);
		}
		
		private var currentSeqLength:int;
		private function GetAnimation(m:MovieClip, seq:int)
		{
			currentSeqLength = m.totalFrames;
			for (var i = 0; i < m.totalFrames; i++)
			{
				Utils.WeirdGotoFrame(m, i + 1);
				Utils.RecursivelyStop(m);
				
				RecursePieces(m, m, i + 1, 0, new Matrix());
			}
		}
		
		protected function RecursePieces(base:MovieClip, graphic:MovieClip, frame, depth, parentMatrix:Matrix)
		{
			//_strace(depth+" "+graphic.name+" frame:"+frame);
			
			var mat:Matrix = graphic.transform.matrix.clone();
			mat.concat(parentMatrix);
			
			var currentPieces:Array = [];
			
			var allChildren:Array = Utils.GetChildren(graphic);
			for (var i = 0; i < allChildren.length; i++)
			{
				if (!allChildren[i].visible) continue;
				if (allChildren[i] is MovieClip)
				{
					if (IsFullPiece(base, graphic, allChildren[i]))
					{
						currentPieces.push(allChildren[i]);
					} else {
						depth = GeneratePieceForClips(currentPieces, depth, frame, mat.clone());
						currentPieces.splice(0);
						
						depth = RecursePieces(base, allChildren[i], frame, depth, mat.clone());
					}
				} else {
					currentPieces.push(allChildren[i]);
				}
			}
			
			depth = GeneratePieceForClips(currentPieces, depth, frame, mat.clone());
			return depth;			
		}
		
		protected function GeneratePieceForClips(clips:Array, depth, frame, parentMatrix:Matrix)
		{
			if (clips.length > 0)
			{				
				var l = clips.length;
				var bounds:Rectangle = null;
				
				var invertMatrix:Matrix = clips[0].transform.matrix.clone();
				invertMatrix.invert();
				var i;
				var m:Matrix;
				var useMatrix:Array = [];
				for (i = 0; i < clips.length; i++)
				{
					if (i == 0)
					{
						m = new Matrix();
					} else {
						m = clips[i].transform.matrix.clone();
						m.concat(invertMatrix);
					}
					
					var r:Rectangle = Utils.GetAccurateBounds(clips[i]);
					Utils.TransformRect(r, m);
					if (bounds == null)
					{
						bounds = r.clone();
					} else {
						if (r.left < bounds.left) bounds.left = r.left;
						if (r.top < bounds.top) bounds.top = r.top;
						if (r.right > bounds.right) bounds.right = r.right;
						if (r.bottom > bounds.bottom) bounds.bottom = r.bottom;
					}
					
					useMatrix.push(m);
				}
				
				Utils.ScaleRect(bounds, def.scale);
				Utils.RoundRect(bounds);
				var bd:BitmapData = new BitmapData(Math.max(1, bounds.width), Math.max(1,bounds.height), true, 0x0);
				
				for (i = 0; i < clips.length; i++)
				{
					m = useMatrix[i];
					m.scale(def.scale, def.scale);
					m.translate(-bounds.left, -bounds.top);
					var ct:ColorTransform = clips[i] is Shape ? clips[i].parent.transform.colorTransform : clips[i].transform.colorTransform;
					bd.draw(clips[i], m, ct, null, null, true);
				}
				
				var center:Point = new Point(-bounds.left, -bounds.top);

				var piece:Piece = library.GetPieceFromBitmapData(bd, clips[0].parent.name, center);
				if (piece)
				{
					var mat:Matrix = new Matrix(1/def.scale, 0, 0, 1/def.scale, center.x - piece.CenterPoint.x, center.y - piece.CenterPoint.y);
					mat.concat(clips[0].transform.matrix);
					mat.concat(parentMatrix);
					
					//mat.a /= exporter.Upscale;
					//mat.d /= exporter.Upscale;
					
					var pieceSequence:PieceSequence = GetPieceSequence(piece, frame, clips[0], mat, depth);
					var info:PieceFrameInfo = pieceSequence.GetFrame(frame);
					if (info)
					{
						info.Present = true;

						info.Transform = mat;
						
						
						
						info.Depth = depth;
						depth++;					
					}
				}
			}
			return depth;
		}
		
		protected function GetPieceSequence(piece:Piece, frame, clip:DisplayObject, newTransform:Matrix, newDepth)
		{
			if (this.pieces.indexOf(piece) == -1) this.pieces.push(piece);
			var options:Array = [];
			var i;
			for (i in pieceSequences)
			{
				if (pieceSequences[i].GetPiece() == piece)
				{
					var frameInfo:PieceFrameInfo = pieceSequences[i].GetFrame(frame);
					if (frameInfo && !frameInfo.Present)
					{
						if (pieceSequences[i].CheckMatches(clip))
						{
							return pieceSequences[i];
						} else {
							options.push(pieceSequences[i]);
						}
					}
				}
			}
			
			if (options.length == 0)
			{
				var s:PieceSequence = new PieceSequence(piece, this.currentSeqLength);
				piece.AddUse(currentSeq);
				pieceSequences.push(s);
				return s;
			}
			
			var scores:Array = [];
			for (i = 0; i < options.length; i++)
			{
				var score = 0;
				if (frame > 1)
				{
					var prevInfo:PieceFrameInfo = options[i].GetFrame(frame - 1);
					if (prevInfo && prevInfo.Present)
					{
						score = ComputeTransformScore(prevInfo.Transform, prevInfo.Depth, newTransform, newDepth);
					}
				}
				scores.push(score);
			}
			var sorted:Array = scores.sort(Array.NUMERIC | Array.RETURNINDEXEDARRAY);
			return options[sorted[0]];
		}
		
		protected function ComputeTransformScore(transformA:Matrix, depthA, transformB:Matrix, depthB, posVal = 1, scaleVal = 15, rotVal = 20, depthVal = 10)
		{
			var deltaPos = Point.distance(new Point(transformA.tx, transformA.ty), new Point(transformB.tx, transformB.ty));
			deltaPos /= def.scale;
			
			var partsA = Utils.Decompose(transformA);
			var partsB = Utils.Decompose(transformB);
			
			var rotA = partsA.rot;
			var rotB = partsB.rot;
			var deltaRot = Utils.GetAngleDiffAbs(rotA, rotB);
			
			var deltaScaleX = partsA.sx / partsB.sx;
			if (deltaScaleX < 1) deltaScaleX = 1 / deltaScaleX;
			var deltaScaleY = partsA.sy / partsB.sy;
			if (deltaScaleY < 1) deltaScaleY = 1 / deltaScaleY;
			var deltaScale = Math.max(deltaScaleX, deltaScaleY);
			
			var deltaDepth = Math.abs(depthA - depthB);
			
			return deltaPos * posVal + (deltaScale - 1) * scaleVal + deltaRot * rotVal + deltaDepth * depthVal;
		}
		
		public static var ONLYCOMBINEADJACENT = true;
		
		protected function CombinePieces()
		{
			var i, j, info:PieceFrameInfo, index;
			var sequenceByPresent = {};
			for (i in pieceSequences)
			{
				var str = "";
				for (j = 0; j < length; j++)
				{
					info = pieceSequences[i].GetFrame(j + 1);
					str += (info && info.Present) ? "1" : "0";
				}
				if (!(str in sequenceByPresent))
				{
					sequenceByPresent[str] = [];
				}
				sequenceByPresent[str].push(pieceSequences[i]);
			}
			
			for (var presentStr in sequenceByPresent)
			{
				var all:Array = sequenceByPresent[presentStr];
				
				var firstFrame = ("" + presentStr).indexOf("1") + 1;
				
				var depths:Array = [];
				for (i = 0; i < all.length; i++)
				{
					depths.push(all[i].GetFrame(firstFrame).Depth);
				}
				var sorted:Array = depths.sort(Array.NUMERIC | Array.RETURNINDEXEDARRAY);
				
				var groups:Array = [];
				var lastGroup:Array = null;
				for (i = 0; i < all.length; i++)
				{
					index = sorted[i];
					if (ONLYCOMBINEADJACENT)
					{
						if (lastGroup != null && SequenceMatches(lastGroup[0], all[index]))
						{
							lastGroup.push(all[index]);
						} else {
							lastGroup = [];
							groups.push(lastGroup);
							lastGroup.push(all[index]);
						}
					} else {
						var foundGroup = false;
						for (j in groups)
						{
							if (SequenceMatches(groups[j][0], all[index]))
							{
								groups[j].push(all[index]);
								foundGroup = true;
								break;
							}
						}
						if (!foundGroup)
						{
							var g:Array = [];
							g.push(all[index]);
							groups.push(g);
						}
					}
				}
			}
			
			var p:Piece;
			for (i in groups)
			{
				// recovering the original pieces...
				// for now: render the bitmaps? (one will be transformed... but it would be transformed anyways, right?)
				var group:Array = groups[i];
				if (group.length > 1)
				{
					var origSeq:PieceSequence = group[0];
					var invert:Matrix;
					var firstPresent = null;
					for (j = 0; j < length; j++)
					{
						info = origSeq.GetFrame(j + 1);
						if (info && info.Present)
						{
							firstPresent = j + 1;
							invert = info.Transform.clone();
							invert.invert();
							break;
						}
					}
					var bounds:Rectangle = null;
					var transforms:Array = [];
					for (j = 0; j < group.length; j++)
					{
						p = group[j].GetPiece();
						p.RemoveUse(currentSeq);
						var m:Matrix = new Matrix();
						if (j > 0)
						{
							//m.translate(-group[j].CenterPoint.x, -group[j].CenterPoint.y);
							m.concat(invert);
							m.concat(group[j].GetFrame(firstPresent).Transform);
							//m.translate(origSeq.CenterPoint.x, origSeq.CenterPoint.y);
						}
						var r:Rectangle = p.FullData.rect.clone();
						Utils.TransformRect(r, m);
						
						if (bounds == null)
						{
							bounds = r;
						} else {
							if (r.left < bounds.left) bounds.left = r.left;
							if (r.right > bounds.right) bounds.right = r.right;
							if (r.top < bounds.top) bounds.top = r.top;
							if (r.bottom > bounds.bottom) bounds.bottom = r.bottom;							
						}
						transforms.push(m);
					}
					Utils.RoundRect(bounds);
					var bd:BitmapData = new BitmapData(bounds.width, bounds.height, true, 0x0);
					for (j = 0; j < group.length; j++)
					{
						transforms[j].translate(-bounds.left, -bounds.top);
						bd.draw(group[j].GetPiece().FullData, transforms[j], null, null, null, true);
					}

					p = library.GetPieceFromBitmapData(bd, "CombinedPiece");
					var seq:PieceSequence = new PieceSequence(p, currentSeqLength);
					p.AddUse(currentSeq);
					seq.CopyTransformsFrom(origSeq, group[group.length - 1].GetFrame(firstPresent).Depth);
					
					this.pieceSequences.push(seq);
					
					this.pieces.push(p);
				}
			}
			
			for (i in groups)
			{
				if (groups[i].length > 1)
				{
					for (j in groups[i])
					{
						index = this.pieceSequences.indexOf(groups[i][j]);
						if (index == -1)
						{
							throw new Error("huh?");
						} else {
							pieceSequences.splice(index, 1);
						}
					}
				}
			}
			
			
			for (i = 0; i < pieces.length; i++)
			{
				if (pieces[i].GetUses(currentSeq) == 0)
				{
					pieces.splice(i, 1);
					i--;
				}
			}
			
		}
		
		protected function SequenceMatches(a:PieceSequence, b:PieceSequence)
		{
			var relative:Matrix = null;
			for (var i = 0; i < length; i++)
			{
				var infoA:PieceFrameInfo = a.GetFrame(i + 1);
				var infoB:PieceFrameInfo = b.GetFrame(i + 1);
				if (infoA && infoB && infoA.Present && infoB.Present)
				{
					var m:Matrix = Utils.GetTransform(infoA.Transform, infoB.Transform);
					if (relative == null)
					{
						relative = m;
					} else {
						if (ComputeTransformScore(relative, 0, m, 0, 4, 15, 5, 0) > 4) return false;
					}
				}			
			}
			return true;
		}
		
		public function ProduceFrame(frame, excludeNames:Array = null):MovieClip
		{
			var i;
			var depths:Array = [];
			for (i = 0; i < pieceSequences.length; i++)
			{
				depths.push(pieceSequences[i].GetFrame(frame).Depth);
			}
			
			var sorts:Array = depths.sort(Array.NUMERIC | Array.RETURNINDEXEDARRAY);
			
			var m:MovieClip = new MovieClip();
			for (i = 0; i < sorts.length; i++)
			{
				var ps:PieceSequence = pieceSequences[sorts[i]];
				var info:PieceFrameInfo = ps.GetFrame(frame);
				if (info.Present && (excludeNames == null || excludeNames.indexOf(ps.GetPiece().Name) == -1))
				{
					var p:Piece = ps.GetPiece();
					var b:Bitmap = new Bitmap(p.FullData);
					
					var mat:Matrix = new Matrix();
					mat.translate(-p.CenterPoint.x, -p.CenterPoint.y);
					mat.concat(info.Transform);
					
					//mat.scale(1 / exporter.Upscale, 1 / exporter.Upscale);
					
					//b.smoothing = true;
					b.transform.matrix = mat;
					
					m.addChild(b);
				}
			}
			return m;
		}
		
		public function ProduceFrameBitmapData(frame, excludeNames:Array = null)
		{
			var ps:PieceSequence;
			var r:Rectangle = null;
			var i;
			var mat:Matrix;
			for (i in pieceSequences)
			{
				ps = pieceSequences[i];
				var frameInfo:PieceFrameInfo = ps.GetFrame(frame);
				if (frameInfo && frameInfo.Present && (excludeNames == null || excludeNames.indexOf(ps.GetPiece().Name) == -1))
				{
					var piece:Piece = ps.GetPiece();
					var bounds:Rectangle = piece.FullData.rect;
					bounds.x -= piece.CenterPoint.x;
					bounds.y -= piece.CenterPoint.y;
					mat = frameInfo.Transform.clone();
					//mat.scale(1 / exporter.Upscale, 1 / exporter.Upscale);
					Utils.TransformRect(bounds, mat);
					
					if (r == null)
					{
						r = bounds;
					} else {
						if (bounds.left < r.left) r.left = bounds.left;
						if (bounds.top < r.top) r.top = bounds.top;
						if (bounds.right > r.right) r.right = bounds.right;
						if (bounds.bottom > r.bottom) r.bottom = bounds.bottom;
					}
						
				}
			}
			
			if (r == null) {
				return {data:new BitmapData(1, 1, true, 0x0),center_point:new Point(0, 0)};
			}
			
			Utils.RoundRect(r);
			var bd:BitmapData;
			try {
				bd = new BitmapData(Math.min(512, r.width), Math.min(512, r.height), true, 0x0);
			} catch (e){
				//_strace("failed to create bitmap data");
			}
			
			var depths:Array = [];
			for (i = 0; i < pieceSequences.length; i++)
			{
				depths.push(pieceSequences[i].GetFrame(frame).Depth);
			}
			
			var sorts:Array = depths.sort(Array.NUMERIC | Array.RETURNINDEXEDARRAY);
			
			for (i = 0; i < sorts.length; i++)
			{
				ps = pieceSequences[sorts[i]];
				var info:PieceFrameInfo = ps.GetFrame(frame);
				if (info && info.Present && (excludeNames == null || excludeNames.indexOf(ps.GetPiece().Name) == -1))
				{
					var p:Piece = ps.GetPiece();
					
					mat = new Matrix();
					mat.translate(-ps.GetPiece().CenterPoint.x, -ps.GetPiece().CenterPoint.y);
					mat.concat(info.Transform);
					
					//mat.scale(1 / exporter.Upscale, 1 / exporter.Upscale);
					
					mat.translate(-r.left, -r.top);
					
					bd.draw(p.FullData, mat, null, null, null, true);
				}
			}
			return {data:bd,center_point:new Point(-r.left, -r.top)};
		}
		
		public static function IsFullPiece(baseClip:DisplayObject, parentClip:MovieClip, clip:DisplayObject)
		{
			//if (HasAnyNamedChildren(clip)) return false;
			
			//if (!clip.name.match("^instance\\d+$")) return true;
				
			if (parentClip.totalFrames != 1) return false;
			
			//if (clip == baseClip || (clip.parent && clip.parent == baseClip)) return false;
			if (clip is MovieClip)
			{
				var m:MovieClip = clip as MovieClip;				
				if (m.totalFrames > 1) return false;
				
				if (!m.name.match("^instance\\d+$")) return false;
				
				for (var i = 0; i < m.numChildren; i++)
				{
					var c = m.getChildAt(i);
					if (!IsFullPiece(baseClip, m, c)) return false;
				}
			}
			return true;
		}
	}
}