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;
		public function get Def():GraphicExportDef { return def; }
		private var callback;
		private var sequences:Array;
		private var frames:Array;
		public function GetFrames():Array
		{
			return frames;			
		}
		public function GetData()
		{
			return {};
		}
		public function AnimatedSkeletonGenerator(src:MovieClip, def:GraphicExportDef)
		{
			this.src = src;
			this.def = def;
		}
		
		private var library:PieceLibrary
		public function Go(callback)
		{
			sequences = new Array();
			library = new PieceLibrary();
			this.callback = callback;
			for (var i = 0; i < src.totalFrames; 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)
					{
						var s:Sequence = new Sequence(this, library, i, c);
						s.GetAnimation();
						sequences.push(s);
						break;
					}
				}
			}
			
			frames = library.GetAllUsedPieces().map(
				function(p:Piece, i, arr)
				{
					return p.Frame;
				}
			);
			
			callback(true);
		}
		
		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;
		}
	}
}

class Sequence
{
	import flash.display.*;
	import flash.geom.*;
	
	private var pieces:Array = new Array();
	private var pieceSequences:Array = new Array();
	private var src:MovieClip;
	private var seq:int;
	private var seqLength:int;
	private var library:PieceLibrary;
	private var generator:AnimatedSkeletonGenerator;
	
	public function Sequence(generator:AnimatedSkeletonGenerator, library:PieceLibrary, seq:int, src:MovieClip)
	{
		this.generator = generator;
		this.library = library;
		this.seq = seq;
		this.seqLength = src.totalFrames;
		this.src = src;
	}
	
	public function GetAnimation()
	{
		for (var i = 0; i < seqLength; i++)
		{
			Utils.WeirdGotoFrame(src, i + 1);
			Utils.RecursivelyStop(src);
			
			RecursePieces(src, i + 1, 0, new Matrix());
		}
	}
	
	protected function RecursePieces(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 (AnimatedSkeletonGenerator.IsFullPiece(src, graphic, allChildren[i]))
				{
					currentPieces.push(allChildren[i]);
				} else {
					depth = GeneratePieceForClips(currentPieces, depth, frame, mat.clone());
					currentPieces.splice(0);
					
					depth = RecursePieces(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, generator.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(generator.Def.scale, generator.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 / generator.Def.scale, 0, 0, 1 / generator.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, seqLength);
			piece.AddUse(seq);
			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 /= generator.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(seq);
					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, seqLength);
				p.AddUse(seq);
				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(seq) == 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)};
	}
}