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)}; } }