diff --git a/AnimatedSkeletonGenerator.as b/AnimatedSkeletonGenerator.as new file mode 100644 index 0000000..20fec88 --- /dev/null +++ b/AnimatedSkeletonGenerator.as @@ -0,0 +1,580 @@ +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; + } + } +} \ No newline at end of file diff --git a/AnimatedSkeletonGenerator.as b/AnimatedSkeletonGenerator.as new file mode 100644 index 0000000..20fec88 --- /dev/null +++ b/AnimatedSkeletonGenerator.as @@ -0,0 +1,580 @@ +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; + } + } +} \ No newline at end of file diff --git a/CharacterExport/PieceLibrary.as b/CharacterExport/PieceLibrary.as deleted file mode 100644 index d07255c..0000000 --- a/CharacterExport/PieceLibrary.as +++ /dev/null @@ -1,156 +0,0 @@ -package CharacterExport -{ - import flash.display.*; - import djarts.core.*; - import flash.geom.Point; - import flash.utils.*; - - public class PieceLibrary - { - protected var pieces:Array = []; - - public function Reset() - { - pieces.splice(0); - } - - private function GetArtistGivenName(clipName:String):String - { - if (clipName.match(/instance\d+/)) - { - return null; - } - return clipName; - } - - private function GetNextAvailableName(artistName:String):String - { - if (artistName == null) return null; - if (artistName.match("^instance\\d+$")) return null; - - var maxNameCount = 0; - for (var i in pieces) - { - if (pieces[i].Name != null) - { - var m = pieces[i].Name.match(new RegExp("^" + artistName + "(\\d+)?$")); - if (m) - { - maxNameCount = Math.max(maxNameCount, m[1] == undefined ? 1 : (Number(m[1]) + 1)); - } - } - } - return artistName + (maxNameCount == 0 ? "" : maxNameCount); - } - - public function GetPieceFromClip(clip:DisplayObject, outCenterPoint:Point, upscale:Number = 1) - { - var piece:Piece = GetExistingPiece(Piece.GenerateCompareData(clip), clip.name); - if (piece != null) return piece; - - piece = new Piece(); - var center:Point = piece.InitFromClip(clip, upscale); - if (piece.Valid) - { - piece.Name = GetNextAvailableName(clip.name); - - pieces.push(piece); - outCenterPoint.x = center.x; - outCenterPoint.y = center.y; - return piece; - } - - return null; - } - - private function GetExistingPiece(compareData:BitmapData, artistName:String) - { - if (compareData == null) return null; - - for (var i in pieces) - { - if (pieces[i].Matches(compareData, artistName)) - { - if (pieces[i].Name == null) - { - pieces[i].Name = GetNextAvailableName(artistName); - } - return pieces[i]; - } - } - - return null; - } - - public function GetPieceFromBitmapData(bd:BitmapData, pieceName:String, centerPoint:Point = null) - { - if (centerPoint == null) centerPoint = new Point(0, 0); - - var piece:Piece = GetExistingPiece(bd, pieceName); - if (piece != null) return piece; - - piece = new Piece(); - piece.InitFromBitmapData(bd, centerPoint); - if (piece.Valid) - { - piece.Name = GetNextAvailableName(pieceName); - pieces.push(piece); - return piece; - } - - return null; - } - - public function Debug() - { - var nextX = 0; - for (var i = 0; i < pieces.length; i++) - { - if (pieces[i].TotalUses > 0) - { - var b:Bitmap = new Bitmap(pieces[i].FullData); - b.scaleX = b.scaleY = 1; - b.x = nextX; - Globals.Instance().GetStage().addChild(b); - nextX += b.width; - } - } - } - - public function get NumUsedPieces() - { - var count = 0; - for (var i in this.pieces) - { - if (pieces[i].TotalUses > 0) - { - count++; - } - } - return count; - } - - public function GetAllUsedPieces():Array - { - var ret:Array = []; - for (var i in this.pieces) - { - if (pieces[i].TotalUses > 0) - { - ret.push(pieces[i]); - } - } - return ret; - } - - public function EnsureAllUsedPiecesHaveNames() - { - var index = 0; - var used:Array = GetAllUsedPieces(); - for (var i in used) - { - if (used[i].Name == null) used[i].Name = "UnknownPiece" + (index++); - } - } - } -} \ No newline at end of file diff --git a/AnimatedSkeletonGenerator.as b/AnimatedSkeletonGenerator.as new file mode 100644 index 0000000..20fec88 --- /dev/null +++ b/AnimatedSkeletonGenerator.as @@ -0,0 +1,580 @@ +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; + } + } +} \ No newline at end of file diff --git a/CharacterExport/PieceLibrary.as b/CharacterExport/PieceLibrary.as deleted file mode 100644 index d07255c..0000000 --- a/CharacterExport/PieceLibrary.as +++ /dev/null @@ -1,156 +0,0 @@ -package CharacterExport -{ - import flash.display.*; - import djarts.core.*; - import flash.geom.Point; - import flash.utils.*; - - public class PieceLibrary - { - protected var pieces:Array = []; - - public function Reset() - { - pieces.splice(0); - } - - private function GetArtistGivenName(clipName:String):String - { - if (clipName.match(/instance\d+/)) - { - return null; - } - return clipName; - } - - private function GetNextAvailableName(artistName:String):String - { - if (artistName == null) return null; - if (artistName.match("^instance\\d+$")) return null; - - var maxNameCount = 0; - for (var i in pieces) - { - if (pieces[i].Name != null) - { - var m = pieces[i].Name.match(new RegExp("^" + artistName + "(\\d+)?$")); - if (m) - { - maxNameCount = Math.max(maxNameCount, m[1] == undefined ? 1 : (Number(m[1]) + 1)); - } - } - } - return artistName + (maxNameCount == 0 ? "" : maxNameCount); - } - - public function GetPieceFromClip(clip:DisplayObject, outCenterPoint:Point, upscale:Number = 1) - { - var piece:Piece = GetExistingPiece(Piece.GenerateCompareData(clip), clip.name); - if (piece != null) return piece; - - piece = new Piece(); - var center:Point = piece.InitFromClip(clip, upscale); - if (piece.Valid) - { - piece.Name = GetNextAvailableName(clip.name); - - pieces.push(piece); - outCenterPoint.x = center.x; - outCenterPoint.y = center.y; - return piece; - } - - return null; - } - - private function GetExistingPiece(compareData:BitmapData, artistName:String) - { - if (compareData == null) return null; - - for (var i in pieces) - { - if (pieces[i].Matches(compareData, artistName)) - { - if (pieces[i].Name == null) - { - pieces[i].Name = GetNextAvailableName(artistName); - } - return pieces[i]; - } - } - - return null; - } - - public function GetPieceFromBitmapData(bd:BitmapData, pieceName:String, centerPoint:Point = null) - { - if (centerPoint == null) centerPoint = new Point(0, 0); - - var piece:Piece = GetExistingPiece(bd, pieceName); - if (piece != null) return piece; - - piece = new Piece(); - piece.InitFromBitmapData(bd, centerPoint); - if (piece.Valid) - { - piece.Name = GetNextAvailableName(pieceName); - pieces.push(piece); - return piece; - } - - return null; - } - - public function Debug() - { - var nextX = 0; - for (var i = 0; i < pieces.length; i++) - { - if (pieces[i].TotalUses > 0) - { - var b:Bitmap = new Bitmap(pieces[i].FullData); - b.scaleX = b.scaleY = 1; - b.x = nextX; - Globals.Instance().GetStage().addChild(b); - nextX += b.width; - } - } - } - - public function get NumUsedPieces() - { - var count = 0; - for (var i in this.pieces) - { - if (pieces[i].TotalUses > 0) - { - count++; - } - } - return count; - } - - public function GetAllUsedPieces():Array - { - var ret:Array = []; - for (var i in this.pieces) - { - if (pieces[i].TotalUses > 0) - { - ret.push(pieces[i]); - } - } - return ret; - } - - public function EnsureAllUsedPiecesHaveNames() - { - var index = 0; - var used:Array = GetAllUsedPieces(); - for (var i in used) - { - if (used[i].Name == null) used[i].Name = "UnknownPiece" + (index++); - } - } - } -} \ No newline at end of file diff --git a/DebugExporter.swf b/DebugExporter.swf index b19375a..9ecb975 100755 --- a/DebugExporter.swf +++ b/DebugExporter.swf Binary files differ diff --git a/AnimatedSkeletonGenerator.as b/AnimatedSkeletonGenerator.as new file mode 100644 index 0000000..20fec88 --- /dev/null +++ b/AnimatedSkeletonGenerator.as @@ -0,0 +1,580 @@ +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; + } + } +} \ No newline at end of file diff --git a/CharacterExport/PieceLibrary.as b/CharacterExport/PieceLibrary.as deleted file mode 100644 index d07255c..0000000 --- a/CharacterExport/PieceLibrary.as +++ /dev/null @@ -1,156 +0,0 @@ -package CharacterExport -{ - import flash.display.*; - import djarts.core.*; - import flash.geom.Point; - import flash.utils.*; - - public class PieceLibrary - { - protected var pieces:Array = []; - - public function Reset() - { - pieces.splice(0); - } - - private function GetArtistGivenName(clipName:String):String - { - if (clipName.match(/instance\d+/)) - { - return null; - } - return clipName; - } - - private function GetNextAvailableName(artistName:String):String - { - if (artistName == null) return null; - if (artistName.match("^instance\\d+$")) return null; - - var maxNameCount = 0; - for (var i in pieces) - { - if (pieces[i].Name != null) - { - var m = pieces[i].Name.match(new RegExp("^" + artistName + "(\\d+)?$")); - if (m) - { - maxNameCount = Math.max(maxNameCount, m[1] == undefined ? 1 : (Number(m[1]) + 1)); - } - } - } - return artistName + (maxNameCount == 0 ? "" : maxNameCount); - } - - public function GetPieceFromClip(clip:DisplayObject, outCenterPoint:Point, upscale:Number = 1) - { - var piece:Piece = GetExistingPiece(Piece.GenerateCompareData(clip), clip.name); - if (piece != null) return piece; - - piece = new Piece(); - var center:Point = piece.InitFromClip(clip, upscale); - if (piece.Valid) - { - piece.Name = GetNextAvailableName(clip.name); - - pieces.push(piece); - outCenterPoint.x = center.x; - outCenterPoint.y = center.y; - return piece; - } - - return null; - } - - private function GetExistingPiece(compareData:BitmapData, artistName:String) - { - if (compareData == null) return null; - - for (var i in pieces) - { - if (pieces[i].Matches(compareData, artistName)) - { - if (pieces[i].Name == null) - { - pieces[i].Name = GetNextAvailableName(artistName); - } - return pieces[i]; - } - } - - return null; - } - - public function GetPieceFromBitmapData(bd:BitmapData, pieceName:String, centerPoint:Point = null) - { - if (centerPoint == null) centerPoint = new Point(0, 0); - - var piece:Piece = GetExistingPiece(bd, pieceName); - if (piece != null) return piece; - - piece = new Piece(); - piece.InitFromBitmapData(bd, centerPoint); - if (piece.Valid) - { - piece.Name = GetNextAvailableName(pieceName); - pieces.push(piece); - return piece; - } - - return null; - } - - public function Debug() - { - var nextX = 0; - for (var i = 0; i < pieces.length; i++) - { - if (pieces[i].TotalUses > 0) - { - var b:Bitmap = new Bitmap(pieces[i].FullData); - b.scaleX = b.scaleY = 1; - b.x = nextX; - Globals.Instance().GetStage().addChild(b); - nextX += b.width; - } - } - } - - public function get NumUsedPieces() - { - var count = 0; - for (var i in this.pieces) - { - if (pieces[i].TotalUses > 0) - { - count++; - } - } - return count; - } - - public function GetAllUsedPieces():Array - { - var ret:Array = []; - for (var i in this.pieces) - { - if (pieces[i].TotalUses > 0) - { - ret.push(pieces[i]); - } - } - return ret; - } - - public function EnsureAllUsedPiecesHaveNames() - { - var index = 0; - var used:Array = GetAllUsedPieces(); - for (var i in used) - { - if (used[i].Name == null) used[i].Name = "UnknownPiece" + (index++); - } - } - } -} \ No newline at end of file diff --git a/DebugExporter.swf b/DebugExporter.swf index b19375a..9ecb975 100755 --- a/DebugExporter.swf +++ b/DebugExporter.swf Binary files differ diff --git a/GraphicExportDef.as b/GraphicExportDef.as index e4202f3..2e4b25b 100755 --- a/GraphicExportDef.as +++ b/GraphicExportDef.as @@ -100,6 +100,9 @@ case TYPE_SPRITES: frames = new AnimatedSpriteGenerator(clip, this); break; + case TYPE_SKELETAL: + frames = new AnimatedSkeletonGenerator(clip, this); + break; } if (frames == null) diff --git a/AnimatedSkeletonGenerator.as b/AnimatedSkeletonGenerator.as new file mode 100644 index 0000000..20fec88 --- /dev/null +++ b/AnimatedSkeletonGenerator.as @@ -0,0 +1,580 @@ +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; + } + } +} \ No newline at end of file diff --git a/CharacterExport/PieceLibrary.as b/CharacterExport/PieceLibrary.as deleted file mode 100644 index d07255c..0000000 --- a/CharacterExport/PieceLibrary.as +++ /dev/null @@ -1,156 +0,0 @@ -package CharacterExport -{ - import flash.display.*; - import djarts.core.*; - import flash.geom.Point; - import flash.utils.*; - - public class PieceLibrary - { - protected var pieces:Array = []; - - public function Reset() - { - pieces.splice(0); - } - - private function GetArtistGivenName(clipName:String):String - { - if (clipName.match(/instance\d+/)) - { - return null; - } - return clipName; - } - - private function GetNextAvailableName(artistName:String):String - { - if (artistName == null) return null; - if (artistName.match("^instance\\d+$")) return null; - - var maxNameCount = 0; - for (var i in pieces) - { - if (pieces[i].Name != null) - { - var m = pieces[i].Name.match(new RegExp("^" + artistName + "(\\d+)?$")); - if (m) - { - maxNameCount = Math.max(maxNameCount, m[1] == undefined ? 1 : (Number(m[1]) + 1)); - } - } - } - return artistName + (maxNameCount == 0 ? "" : maxNameCount); - } - - public function GetPieceFromClip(clip:DisplayObject, outCenterPoint:Point, upscale:Number = 1) - { - var piece:Piece = GetExistingPiece(Piece.GenerateCompareData(clip), clip.name); - if (piece != null) return piece; - - piece = new Piece(); - var center:Point = piece.InitFromClip(clip, upscale); - if (piece.Valid) - { - piece.Name = GetNextAvailableName(clip.name); - - pieces.push(piece); - outCenterPoint.x = center.x; - outCenterPoint.y = center.y; - return piece; - } - - return null; - } - - private function GetExistingPiece(compareData:BitmapData, artistName:String) - { - if (compareData == null) return null; - - for (var i in pieces) - { - if (pieces[i].Matches(compareData, artistName)) - { - if (pieces[i].Name == null) - { - pieces[i].Name = GetNextAvailableName(artistName); - } - return pieces[i]; - } - } - - return null; - } - - public function GetPieceFromBitmapData(bd:BitmapData, pieceName:String, centerPoint:Point = null) - { - if (centerPoint == null) centerPoint = new Point(0, 0); - - var piece:Piece = GetExistingPiece(bd, pieceName); - if (piece != null) return piece; - - piece = new Piece(); - piece.InitFromBitmapData(bd, centerPoint); - if (piece.Valid) - { - piece.Name = GetNextAvailableName(pieceName); - pieces.push(piece); - return piece; - } - - return null; - } - - public function Debug() - { - var nextX = 0; - for (var i = 0; i < pieces.length; i++) - { - if (pieces[i].TotalUses > 0) - { - var b:Bitmap = new Bitmap(pieces[i].FullData); - b.scaleX = b.scaleY = 1; - b.x = nextX; - Globals.Instance().GetStage().addChild(b); - nextX += b.width; - } - } - } - - public function get NumUsedPieces() - { - var count = 0; - for (var i in this.pieces) - { - if (pieces[i].TotalUses > 0) - { - count++; - } - } - return count; - } - - public function GetAllUsedPieces():Array - { - var ret:Array = []; - for (var i in this.pieces) - { - if (pieces[i].TotalUses > 0) - { - ret.push(pieces[i]); - } - } - return ret; - } - - public function EnsureAllUsedPiecesHaveNames() - { - var index = 0; - var used:Array = GetAllUsedPieces(); - for (var i in used) - { - if (used[i].Name == null) used[i].Name = "UnknownPiece" + (index++); - } - } - } -} \ No newline at end of file diff --git a/DebugExporter.swf b/DebugExporter.swf index b19375a..9ecb975 100755 --- a/DebugExporter.swf +++ b/DebugExporter.swf Binary files differ diff --git a/GraphicExportDef.as b/GraphicExportDef.as index e4202f3..2e4b25b 100755 --- a/GraphicExportDef.as +++ b/GraphicExportDef.as @@ -100,6 +100,9 @@ case TYPE_SPRITES: frames = new AnimatedSpriteGenerator(clip, this); break; + case TYPE_SKELETAL: + frames = new AnimatedSkeletonGenerator(clip, this); + break; } if (frames == null) diff --git a/Piece.as b/Piece.as new file mode 100644 index 0000000..dc07fd2 --- /dev/null +++ b/Piece.as @@ -0,0 +1,171 @@ +package +{ + import flash.display.*; + import flash.geom.*; + + public class Piece + { + protected var size:Point; + protected var fullData:BitmapData; + protected var compareData:BitmapData; + + protected var centerPoint:Point; + public function get CenterPoint():Point { return centerPoint; } + + public function get FullData():BitmapData { return fullData; } + + protected var valid = false; + public function get Valid() { return valid; } + + public var Name:String = null; + + protected var usesBySequence = {}; + public function AddUse(sequence) + { + if (!(sequence in usesBySequence)) + { + usesBySequence[sequence] = 0; + } + usesBySequence[sequence]++; + } + + public function RemoveUse(sequence) + { + if (sequence in usesBySequence && usesBySequence[sequence] > 0) + { + usesBySequence[sequence]--; + } + } + + public function GetUses(sequence) + { + return (sequence in usesBySequence) ? usesBySequence[sequence] : 0; + } + + public function get TotalUses() + { + var count = 0; + for (var s in usesBySequence) + { + count += usesBySequence[s]; + } + return count; + } + + // returns the center point + public function InitFromClip(clip:DisplayObject, upscale:Number = 1):Point + { + var bounds:Rectangle = Utils.GetAccurateBounds(clip); + + size = new Point(bounds.width, bounds.height); + + valid = bounds.width > 0 && bounds.height > 0; + if (valid) + { + var m:Matrix = clip is Shape ? clip.transform.matrix.clone() : new Matrix(); + m.scale(upscale, upscale); + + Utils.TransformRect(bounds, m); + Utils.RoundRect(bounds); + + m.translate(-bounds.left, -bounds.top); + + fullData = new BitmapData(bounds.width, bounds.height, true, 0x0); + fullData.draw(clip, m, clip is Shape && clip.parent ? clip.parent.transform.colorTransform : clip.transform.colorTransform, null, null, true); + + compareData = GenerateCompareData(clip); + + centerPoint = new Point(-bounds.left, -bounds.top); + return centerPoint; + } + return null; + } + + public function InitFromBitmapData(bd:BitmapData, centerPoint:Point = null) + { + if (centerPoint == null) centerPoint = new Point(0, 0); + valid = true; + size = new Point(bd.width, bd.height); + + fullData = bd; + + compareData = bd.clone(); + + this.centerPoint = centerPoint; + } + + public function Matches(otherData:BitmapData, otherName) + { + + if (false && otherName != null && Name != null) + { + //This whole section fails to match things properly... + + var m = Name.match("^([^0-9]+)(\\d*)"); + var tempName = m ? m[1] : Name; + + var mo = otherName.match("^([^0-9]+)(\\d*)"); + var tempOtherName = mo ? mo[1] : otherName; + + return tempName == tempOtherName; + + + if (m) + { + if (otherName != m[1]) return false; + return true; + } + else + { + if (otherName != Name) return false; + return true; + } + } + + var threshold = 3; + var maxPixels = 0; + var result = compareData.compare(otherData); + if (result == 0) return true; + if (result is BitmapData) + { + var histo:Vector.> = (result as BitmapData).histogram(); + for (var i = 0; i < 4; i++) + { + var diff:Vector. = histo[i]; + // for some STUPID reason, if all the colors match but the alphas dont, it returns FFFFFF for the color portion... + // so DON'T compare the histogram for color channels up to 255 + var max = i == 4 ? 255 : 254; + for (var j = threshold; j < Math.min(max, diff.length); j++) + { + if (diff[j] > maxPixels) + { + return false; + } + } + } + return true; + } + return false; + } + + public static function GenerateCompareData(clip:DisplayObject):BitmapData + { + var scale = 1; + + var quickBounds:Rectangle = clip.getBounds(clip); + var orig:Rectangle = quickBounds.clone(); + if (quickBounds.width == 0 || quickBounds.height == 0) return null; + Utils.ScaleRect(quickBounds, scale); + Utils.RoundRect(quickBounds); + + var m:Matrix = clip is Shape ? clip.transform.matrix.clone() : new Matrix(); + m.scale(scale, scale); + m.translate(-quickBounds.left, -quickBounds.top); + + var data:BitmapData = new BitmapData(quickBounds.width, quickBounds.height, true, 0x0); + data.draw(clip, m, clip is Shape && clip.parent ? clip.parent.transform.colorTransform : clip.transform.colorTransform, null, null, true); + + return data; + } + } +} \ No newline at end of file diff --git a/AnimatedSkeletonGenerator.as b/AnimatedSkeletonGenerator.as new file mode 100644 index 0000000..20fec88 --- /dev/null +++ b/AnimatedSkeletonGenerator.as @@ -0,0 +1,580 @@ +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; + } + } +} \ No newline at end of file diff --git a/CharacterExport/PieceLibrary.as b/CharacterExport/PieceLibrary.as deleted file mode 100644 index d07255c..0000000 --- a/CharacterExport/PieceLibrary.as +++ /dev/null @@ -1,156 +0,0 @@ -package CharacterExport -{ - import flash.display.*; - import djarts.core.*; - import flash.geom.Point; - import flash.utils.*; - - public class PieceLibrary - { - protected var pieces:Array = []; - - public function Reset() - { - pieces.splice(0); - } - - private function GetArtistGivenName(clipName:String):String - { - if (clipName.match(/instance\d+/)) - { - return null; - } - return clipName; - } - - private function GetNextAvailableName(artistName:String):String - { - if (artistName == null) return null; - if (artistName.match("^instance\\d+$")) return null; - - var maxNameCount = 0; - for (var i in pieces) - { - if (pieces[i].Name != null) - { - var m = pieces[i].Name.match(new RegExp("^" + artistName + "(\\d+)?$")); - if (m) - { - maxNameCount = Math.max(maxNameCount, m[1] == undefined ? 1 : (Number(m[1]) + 1)); - } - } - } - return artistName + (maxNameCount == 0 ? "" : maxNameCount); - } - - public function GetPieceFromClip(clip:DisplayObject, outCenterPoint:Point, upscale:Number = 1) - { - var piece:Piece = GetExistingPiece(Piece.GenerateCompareData(clip), clip.name); - if (piece != null) return piece; - - piece = new Piece(); - var center:Point = piece.InitFromClip(clip, upscale); - if (piece.Valid) - { - piece.Name = GetNextAvailableName(clip.name); - - pieces.push(piece); - outCenterPoint.x = center.x; - outCenterPoint.y = center.y; - return piece; - } - - return null; - } - - private function GetExistingPiece(compareData:BitmapData, artistName:String) - { - if (compareData == null) return null; - - for (var i in pieces) - { - if (pieces[i].Matches(compareData, artistName)) - { - if (pieces[i].Name == null) - { - pieces[i].Name = GetNextAvailableName(artistName); - } - return pieces[i]; - } - } - - return null; - } - - public function GetPieceFromBitmapData(bd:BitmapData, pieceName:String, centerPoint:Point = null) - { - if (centerPoint == null) centerPoint = new Point(0, 0); - - var piece:Piece = GetExistingPiece(bd, pieceName); - if (piece != null) return piece; - - piece = new Piece(); - piece.InitFromBitmapData(bd, centerPoint); - if (piece.Valid) - { - piece.Name = GetNextAvailableName(pieceName); - pieces.push(piece); - return piece; - } - - return null; - } - - public function Debug() - { - var nextX = 0; - for (var i = 0; i < pieces.length; i++) - { - if (pieces[i].TotalUses > 0) - { - var b:Bitmap = new Bitmap(pieces[i].FullData); - b.scaleX = b.scaleY = 1; - b.x = nextX; - Globals.Instance().GetStage().addChild(b); - nextX += b.width; - } - } - } - - public function get NumUsedPieces() - { - var count = 0; - for (var i in this.pieces) - { - if (pieces[i].TotalUses > 0) - { - count++; - } - } - return count; - } - - public function GetAllUsedPieces():Array - { - var ret:Array = []; - for (var i in this.pieces) - { - if (pieces[i].TotalUses > 0) - { - ret.push(pieces[i]); - } - } - return ret; - } - - public function EnsureAllUsedPiecesHaveNames() - { - var index = 0; - var used:Array = GetAllUsedPieces(); - for (var i in used) - { - if (used[i].Name == null) used[i].Name = "UnknownPiece" + (index++); - } - } - } -} \ No newline at end of file diff --git a/DebugExporter.swf b/DebugExporter.swf index b19375a..9ecb975 100755 --- a/DebugExporter.swf +++ b/DebugExporter.swf Binary files differ diff --git a/GraphicExportDef.as b/GraphicExportDef.as index e4202f3..2e4b25b 100755 --- a/GraphicExportDef.as +++ b/GraphicExportDef.as @@ -100,6 +100,9 @@ case TYPE_SPRITES: frames = new AnimatedSpriteGenerator(clip, this); break; + case TYPE_SKELETAL: + frames = new AnimatedSkeletonGenerator(clip, this); + break; } if (frames == null) diff --git a/Piece.as b/Piece.as new file mode 100644 index 0000000..dc07fd2 --- /dev/null +++ b/Piece.as @@ -0,0 +1,171 @@ +package +{ + import flash.display.*; + import flash.geom.*; + + public class Piece + { + protected var size:Point; + protected var fullData:BitmapData; + protected var compareData:BitmapData; + + protected var centerPoint:Point; + public function get CenterPoint():Point { return centerPoint; } + + public function get FullData():BitmapData { return fullData; } + + protected var valid = false; + public function get Valid() { return valid; } + + public var Name:String = null; + + protected var usesBySequence = {}; + public function AddUse(sequence) + { + if (!(sequence in usesBySequence)) + { + usesBySequence[sequence] = 0; + } + usesBySequence[sequence]++; + } + + public function RemoveUse(sequence) + { + if (sequence in usesBySequence && usesBySequence[sequence] > 0) + { + usesBySequence[sequence]--; + } + } + + public function GetUses(sequence) + { + return (sequence in usesBySequence) ? usesBySequence[sequence] : 0; + } + + public function get TotalUses() + { + var count = 0; + for (var s in usesBySequence) + { + count += usesBySequence[s]; + } + return count; + } + + // returns the center point + public function InitFromClip(clip:DisplayObject, upscale:Number = 1):Point + { + var bounds:Rectangle = Utils.GetAccurateBounds(clip); + + size = new Point(bounds.width, bounds.height); + + valid = bounds.width > 0 && bounds.height > 0; + if (valid) + { + var m:Matrix = clip is Shape ? clip.transform.matrix.clone() : new Matrix(); + m.scale(upscale, upscale); + + Utils.TransformRect(bounds, m); + Utils.RoundRect(bounds); + + m.translate(-bounds.left, -bounds.top); + + fullData = new BitmapData(bounds.width, bounds.height, true, 0x0); + fullData.draw(clip, m, clip is Shape && clip.parent ? clip.parent.transform.colorTransform : clip.transform.colorTransform, null, null, true); + + compareData = GenerateCompareData(clip); + + centerPoint = new Point(-bounds.left, -bounds.top); + return centerPoint; + } + return null; + } + + public function InitFromBitmapData(bd:BitmapData, centerPoint:Point = null) + { + if (centerPoint == null) centerPoint = new Point(0, 0); + valid = true; + size = new Point(bd.width, bd.height); + + fullData = bd; + + compareData = bd.clone(); + + this.centerPoint = centerPoint; + } + + public function Matches(otherData:BitmapData, otherName) + { + + if (false && otherName != null && Name != null) + { + //This whole section fails to match things properly... + + var m = Name.match("^([^0-9]+)(\\d*)"); + var tempName = m ? m[1] : Name; + + var mo = otherName.match("^([^0-9]+)(\\d*)"); + var tempOtherName = mo ? mo[1] : otherName; + + return tempName == tempOtherName; + + + if (m) + { + if (otherName != m[1]) return false; + return true; + } + else + { + if (otherName != Name) return false; + return true; + } + } + + var threshold = 3; + var maxPixels = 0; + var result = compareData.compare(otherData); + if (result == 0) return true; + if (result is BitmapData) + { + var histo:Vector.> = (result as BitmapData).histogram(); + for (var i = 0; i < 4; i++) + { + var diff:Vector. = histo[i]; + // for some STUPID reason, if all the colors match but the alphas dont, it returns FFFFFF for the color portion... + // so DON'T compare the histogram for color channels up to 255 + var max = i == 4 ? 255 : 254; + for (var j = threshold; j < Math.min(max, diff.length); j++) + { + if (diff[j] > maxPixels) + { + return false; + } + } + } + return true; + } + return false; + } + + public static function GenerateCompareData(clip:DisplayObject):BitmapData + { + var scale = 1; + + var quickBounds:Rectangle = clip.getBounds(clip); + var orig:Rectangle = quickBounds.clone(); + if (quickBounds.width == 0 || quickBounds.height == 0) return null; + Utils.ScaleRect(quickBounds, scale); + Utils.RoundRect(quickBounds); + + var m:Matrix = clip is Shape ? clip.transform.matrix.clone() : new Matrix(); + m.scale(scale, scale); + m.translate(-quickBounds.left, -quickBounds.top); + + var data:BitmapData = new BitmapData(quickBounds.width, quickBounds.height, true, 0x0); + data.draw(clip, m, clip is Shape && clip.parent ? clip.parent.transform.colorTransform : clip.transform.colorTransform, null, null, true); + + return data; + } + } +} \ No newline at end of file diff --git a/PieceFrameInfo.as b/PieceFrameInfo.as new file mode 100644 index 0000000..a656f09 --- /dev/null +++ b/PieceFrameInfo.as @@ -0,0 +1,10 @@ +package +{ + import flash.geom.*; + public class PieceFrameInfo + { + public var Present = false; + public var Transform:Matrix = null; + public var Depth = null; + } +} \ No newline at end of file diff --git a/AnimatedSkeletonGenerator.as b/AnimatedSkeletonGenerator.as new file mode 100644 index 0000000..20fec88 --- /dev/null +++ b/AnimatedSkeletonGenerator.as @@ -0,0 +1,580 @@ +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; + } + } +} \ No newline at end of file diff --git a/CharacterExport/PieceLibrary.as b/CharacterExport/PieceLibrary.as deleted file mode 100644 index d07255c..0000000 --- a/CharacterExport/PieceLibrary.as +++ /dev/null @@ -1,156 +0,0 @@ -package CharacterExport -{ - import flash.display.*; - import djarts.core.*; - import flash.geom.Point; - import flash.utils.*; - - public class PieceLibrary - { - protected var pieces:Array = []; - - public function Reset() - { - pieces.splice(0); - } - - private function GetArtistGivenName(clipName:String):String - { - if (clipName.match(/instance\d+/)) - { - return null; - } - return clipName; - } - - private function GetNextAvailableName(artistName:String):String - { - if (artistName == null) return null; - if (artistName.match("^instance\\d+$")) return null; - - var maxNameCount = 0; - for (var i in pieces) - { - if (pieces[i].Name != null) - { - var m = pieces[i].Name.match(new RegExp("^" + artistName + "(\\d+)?$")); - if (m) - { - maxNameCount = Math.max(maxNameCount, m[1] == undefined ? 1 : (Number(m[1]) + 1)); - } - } - } - return artistName + (maxNameCount == 0 ? "" : maxNameCount); - } - - public function GetPieceFromClip(clip:DisplayObject, outCenterPoint:Point, upscale:Number = 1) - { - var piece:Piece = GetExistingPiece(Piece.GenerateCompareData(clip), clip.name); - if (piece != null) return piece; - - piece = new Piece(); - var center:Point = piece.InitFromClip(clip, upscale); - if (piece.Valid) - { - piece.Name = GetNextAvailableName(clip.name); - - pieces.push(piece); - outCenterPoint.x = center.x; - outCenterPoint.y = center.y; - return piece; - } - - return null; - } - - private function GetExistingPiece(compareData:BitmapData, artistName:String) - { - if (compareData == null) return null; - - for (var i in pieces) - { - if (pieces[i].Matches(compareData, artistName)) - { - if (pieces[i].Name == null) - { - pieces[i].Name = GetNextAvailableName(artistName); - } - return pieces[i]; - } - } - - return null; - } - - public function GetPieceFromBitmapData(bd:BitmapData, pieceName:String, centerPoint:Point = null) - { - if (centerPoint == null) centerPoint = new Point(0, 0); - - var piece:Piece = GetExistingPiece(bd, pieceName); - if (piece != null) return piece; - - piece = new Piece(); - piece.InitFromBitmapData(bd, centerPoint); - if (piece.Valid) - { - piece.Name = GetNextAvailableName(pieceName); - pieces.push(piece); - return piece; - } - - return null; - } - - public function Debug() - { - var nextX = 0; - for (var i = 0; i < pieces.length; i++) - { - if (pieces[i].TotalUses > 0) - { - var b:Bitmap = new Bitmap(pieces[i].FullData); - b.scaleX = b.scaleY = 1; - b.x = nextX; - Globals.Instance().GetStage().addChild(b); - nextX += b.width; - } - } - } - - public function get NumUsedPieces() - { - var count = 0; - for (var i in this.pieces) - { - if (pieces[i].TotalUses > 0) - { - count++; - } - } - return count; - } - - public function GetAllUsedPieces():Array - { - var ret:Array = []; - for (var i in this.pieces) - { - if (pieces[i].TotalUses > 0) - { - ret.push(pieces[i]); - } - } - return ret; - } - - public function EnsureAllUsedPiecesHaveNames() - { - var index = 0; - var used:Array = GetAllUsedPieces(); - for (var i in used) - { - if (used[i].Name == null) used[i].Name = "UnknownPiece" + (index++); - } - } - } -} \ No newline at end of file diff --git a/DebugExporter.swf b/DebugExporter.swf index b19375a..9ecb975 100755 --- a/DebugExporter.swf +++ b/DebugExporter.swf Binary files differ diff --git a/GraphicExportDef.as b/GraphicExportDef.as index e4202f3..2e4b25b 100755 --- a/GraphicExportDef.as +++ b/GraphicExportDef.as @@ -100,6 +100,9 @@ case TYPE_SPRITES: frames = new AnimatedSpriteGenerator(clip, this); break; + case TYPE_SKELETAL: + frames = new AnimatedSkeletonGenerator(clip, this); + break; } if (frames == null) diff --git a/Piece.as b/Piece.as new file mode 100644 index 0000000..dc07fd2 --- /dev/null +++ b/Piece.as @@ -0,0 +1,171 @@ +package +{ + import flash.display.*; + import flash.geom.*; + + public class Piece + { + protected var size:Point; + protected var fullData:BitmapData; + protected var compareData:BitmapData; + + protected var centerPoint:Point; + public function get CenterPoint():Point { return centerPoint; } + + public function get FullData():BitmapData { return fullData; } + + protected var valid = false; + public function get Valid() { return valid; } + + public var Name:String = null; + + protected var usesBySequence = {}; + public function AddUse(sequence) + { + if (!(sequence in usesBySequence)) + { + usesBySequence[sequence] = 0; + } + usesBySequence[sequence]++; + } + + public function RemoveUse(sequence) + { + if (sequence in usesBySequence && usesBySequence[sequence] > 0) + { + usesBySequence[sequence]--; + } + } + + public function GetUses(sequence) + { + return (sequence in usesBySequence) ? usesBySequence[sequence] : 0; + } + + public function get TotalUses() + { + var count = 0; + for (var s in usesBySequence) + { + count += usesBySequence[s]; + } + return count; + } + + // returns the center point + public function InitFromClip(clip:DisplayObject, upscale:Number = 1):Point + { + var bounds:Rectangle = Utils.GetAccurateBounds(clip); + + size = new Point(bounds.width, bounds.height); + + valid = bounds.width > 0 && bounds.height > 0; + if (valid) + { + var m:Matrix = clip is Shape ? clip.transform.matrix.clone() : new Matrix(); + m.scale(upscale, upscale); + + Utils.TransformRect(bounds, m); + Utils.RoundRect(bounds); + + m.translate(-bounds.left, -bounds.top); + + fullData = new BitmapData(bounds.width, bounds.height, true, 0x0); + fullData.draw(clip, m, clip is Shape && clip.parent ? clip.parent.transform.colorTransform : clip.transform.colorTransform, null, null, true); + + compareData = GenerateCompareData(clip); + + centerPoint = new Point(-bounds.left, -bounds.top); + return centerPoint; + } + return null; + } + + public function InitFromBitmapData(bd:BitmapData, centerPoint:Point = null) + { + if (centerPoint == null) centerPoint = new Point(0, 0); + valid = true; + size = new Point(bd.width, bd.height); + + fullData = bd; + + compareData = bd.clone(); + + this.centerPoint = centerPoint; + } + + public function Matches(otherData:BitmapData, otherName) + { + + if (false && otherName != null && Name != null) + { + //This whole section fails to match things properly... + + var m = Name.match("^([^0-9]+)(\\d*)"); + var tempName = m ? m[1] : Name; + + var mo = otherName.match("^([^0-9]+)(\\d*)"); + var tempOtherName = mo ? mo[1] : otherName; + + return tempName == tempOtherName; + + + if (m) + { + if (otherName != m[1]) return false; + return true; + } + else + { + if (otherName != Name) return false; + return true; + } + } + + var threshold = 3; + var maxPixels = 0; + var result = compareData.compare(otherData); + if (result == 0) return true; + if (result is BitmapData) + { + var histo:Vector.> = (result as BitmapData).histogram(); + for (var i = 0; i < 4; i++) + { + var diff:Vector. = histo[i]; + // for some STUPID reason, if all the colors match but the alphas dont, it returns FFFFFF for the color portion... + // so DON'T compare the histogram for color channels up to 255 + var max = i == 4 ? 255 : 254; + for (var j = threshold; j < Math.min(max, diff.length); j++) + { + if (diff[j] > maxPixels) + { + return false; + } + } + } + return true; + } + return false; + } + + public static function GenerateCompareData(clip:DisplayObject):BitmapData + { + var scale = 1; + + var quickBounds:Rectangle = clip.getBounds(clip); + var orig:Rectangle = quickBounds.clone(); + if (quickBounds.width == 0 || quickBounds.height == 0) return null; + Utils.ScaleRect(quickBounds, scale); + Utils.RoundRect(quickBounds); + + var m:Matrix = clip is Shape ? clip.transform.matrix.clone() : new Matrix(); + m.scale(scale, scale); + m.translate(-quickBounds.left, -quickBounds.top); + + var data:BitmapData = new BitmapData(quickBounds.width, quickBounds.height, true, 0x0); + data.draw(clip, m, clip is Shape && clip.parent ? clip.parent.transform.colorTransform : clip.transform.colorTransform, null, null, true); + + return data; + } + } +} \ No newline at end of file diff --git a/PieceFrameInfo.as b/PieceFrameInfo.as new file mode 100644 index 0000000..a656f09 --- /dev/null +++ b/PieceFrameInfo.as @@ -0,0 +1,10 @@ +package +{ + import flash.geom.*; + public class PieceFrameInfo + { + public var Present = false; + public var Transform:Matrix = null; + public var Depth = null; + } +} \ No newline at end of file diff --git a/PieceLibrary.as b/PieceLibrary.as new file mode 100644 index 0000000..e5745bd --- /dev/null +++ b/PieceLibrary.as @@ -0,0 +1,154 @@ +package +{ + import flash.display.*; + import flash.geom.Point; + import flash.utils.*; + + public class PieceLibrary + { + protected var pieces:Array = []; + + public function Reset() + { + pieces.splice(0); + } + + private function GetArtistGivenName(clipName:String):String + { + if (clipName.match(/instance\d+/)) + { + return null; + } + return clipName; + } + + private function GetNextAvailableName(artistName:String):String + { + if (artistName == null) return null; + if (artistName.match("^instance\\d+$")) return null; + + var maxNameCount = 0; + for (var i in pieces) + { + if (pieces[i].Name != null) + { + var m = pieces[i].Name.match(new RegExp("^" + artistName + "(\\d+)?$")); + if (m) + { + maxNameCount = Math.max(maxNameCount, m[1] == undefined ? 1 : (Number(m[1]) + 1)); + } + } + } + return artistName + (maxNameCount == 0 ? "" : maxNameCount); + } + + public function GetPieceFromClip(clip:DisplayObject, outCenterPoint:Point, upscale:Number = 1) + { + var piece:Piece = GetExistingPiece(Piece.GenerateCompareData(clip), clip.name); + if (piece != null) return piece; + + piece = new Piece(); + var center:Point = piece.InitFromClip(clip, upscale); + if (piece.Valid) + { + piece.Name = GetNextAvailableName(clip.name); + + pieces.push(piece); + outCenterPoint.x = center.x; + outCenterPoint.y = center.y; + return piece; + } + + return null; + } + + private function GetExistingPiece(compareData:BitmapData, artistName:String) + { + if (compareData == null) return null; + + for (var i in pieces) + { + if (pieces[i].Matches(compareData, artistName)) + { + if (pieces[i].Name == null) + { + pieces[i].Name = GetNextAvailableName(artistName); + } + return pieces[i]; + } + } + + return null; + } + + public function GetPieceFromBitmapData(bd:BitmapData, pieceName:String, centerPoint:Point = null) + { + if (centerPoint == null) centerPoint = new Point(0, 0); + + var piece:Piece = GetExistingPiece(bd, pieceName); + if (piece != null) return piece; + + piece = new Piece(); + piece.InitFromBitmapData(bd, centerPoint); + if (piece.Valid) + { + piece.Name = GetNextAvailableName(pieceName); + pieces.push(piece); + return piece; + } + + return null; + } + + public function Debug() + { + var nextX = 0; + for (var i = 0; i < pieces.length; i++) + { + if (pieces[i].TotalUses > 0) + { + var b:Bitmap = new Bitmap(pieces[i].FullData); + b.scaleX = b.scaleY = 1; + b.x = nextX; + nextX += b.width; + } + } + } + + public function get NumUsedPieces() + { + var count = 0; + for (var i in this.pieces) + { + if (pieces[i].TotalUses > 0) + { + count++; + } + } + return count; + } + + public function GetAllUsedPieces():Array + { + var ret:Array = []; + for (var i in this.pieces) + { + if (pieces[i].TotalUses > 0) + { + ret.push(pieces[i]); + } + } + return ret; + } + + public function EnsureAllUsedPiecesHaveNames() + { + var index = 0; + var used:Array = GetAllUsedPieces(); + for (var i in used) + { + if (used[i].Name == null) used[i].Name = "UnknownPiece" + (index++); + } + } + } +} \ No newline at end of file diff --git a/AnimatedSkeletonGenerator.as b/AnimatedSkeletonGenerator.as new file mode 100644 index 0000000..20fec88 --- /dev/null +++ b/AnimatedSkeletonGenerator.as @@ -0,0 +1,580 @@ +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; + } + } +} \ No newline at end of file diff --git a/CharacterExport/PieceLibrary.as b/CharacterExport/PieceLibrary.as deleted file mode 100644 index d07255c..0000000 --- a/CharacterExport/PieceLibrary.as +++ /dev/null @@ -1,156 +0,0 @@ -package CharacterExport -{ - import flash.display.*; - import djarts.core.*; - import flash.geom.Point; - import flash.utils.*; - - public class PieceLibrary - { - protected var pieces:Array = []; - - public function Reset() - { - pieces.splice(0); - } - - private function GetArtistGivenName(clipName:String):String - { - if (clipName.match(/instance\d+/)) - { - return null; - } - return clipName; - } - - private function GetNextAvailableName(artistName:String):String - { - if (artistName == null) return null; - if (artistName.match("^instance\\d+$")) return null; - - var maxNameCount = 0; - for (var i in pieces) - { - if (pieces[i].Name != null) - { - var m = pieces[i].Name.match(new RegExp("^" + artistName + "(\\d+)?$")); - if (m) - { - maxNameCount = Math.max(maxNameCount, m[1] == undefined ? 1 : (Number(m[1]) + 1)); - } - } - } - return artistName + (maxNameCount == 0 ? "" : maxNameCount); - } - - public function GetPieceFromClip(clip:DisplayObject, outCenterPoint:Point, upscale:Number = 1) - { - var piece:Piece = GetExistingPiece(Piece.GenerateCompareData(clip), clip.name); - if (piece != null) return piece; - - piece = new Piece(); - var center:Point = piece.InitFromClip(clip, upscale); - if (piece.Valid) - { - piece.Name = GetNextAvailableName(clip.name); - - pieces.push(piece); - outCenterPoint.x = center.x; - outCenterPoint.y = center.y; - return piece; - } - - return null; - } - - private function GetExistingPiece(compareData:BitmapData, artistName:String) - { - if (compareData == null) return null; - - for (var i in pieces) - { - if (pieces[i].Matches(compareData, artistName)) - { - if (pieces[i].Name == null) - { - pieces[i].Name = GetNextAvailableName(artistName); - } - return pieces[i]; - } - } - - return null; - } - - public function GetPieceFromBitmapData(bd:BitmapData, pieceName:String, centerPoint:Point = null) - { - if (centerPoint == null) centerPoint = new Point(0, 0); - - var piece:Piece = GetExistingPiece(bd, pieceName); - if (piece != null) return piece; - - piece = new Piece(); - piece.InitFromBitmapData(bd, centerPoint); - if (piece.Valid) - { - piece.Name = GetNextAvailableName(pieceName); - pieces.push(piece); - return piece; - } - - return null; - } - - public function Debug() - { - var nextX = 0; - for (var i = 0; i < pieces.length; i++) - { - if (pieces[i].TotalUses > 0) - { - var b:Bitmap = new Bitmap(pieces[i].FullData); - b.scaleX = b.scaleY = 1; - b.x = nextX; - Globals.Instance().GetStage().addChild(b); - nextX += b.width; - } - } - } - - public function get NumUsedPieces() - { - var count = 0; - for (var i in this.pieces) - { - if (pieces[i].TotalUses > 0) - { - count++; - } - } - return count; - } - - public function GetAllUsedPieces():Array - { - var ret:Array = []; - for (var i in this.pieces) - { - if (pieces[i].TotalUses > 0) - { - ret.push(pieces[i]); - } - } - return ret; - } - - public function EnsureAllUsedPiecesHaveNames() - { - var index = 0; - var used:Array = GetAllUsedPieces(); - for (var i in used) - { - if (used[i].Name == null) used[i].Name = "UnknownPiece" + (index++); - } - } - } -} \ No newline at end of file diff --git a/DebugExporter.swf b/DebugExporter.swf index b19375a..9ecb975 100755 --- a/DebugExporter.swf +++ b/DebugExporter.swf Binary files differ diff --git a/GraphicExportDef.as b/GraphicExportDef.as index e4202f3..2e4b25b 100755 --- a/GraphicExportDef.as +++ b/GraphicExportDef.as @@ -100,6 +100,9 @@ case TYPE_SPRITES: frames = new AnimatedSpriteGenerator(clip, this); break; + case TYPE_SKELETAL: + frames = new AnimatedSkeletonGenerator(clip, this); + break; } if (frames == null) diff --git a/Piece.as b/Piece.as new file mode 100644 index 0000000..dc07fd2 --- /dev/null +++ b/Piece.as @@ -0,0 +1,171 @@ +package +{ + import flash.display.*; + import flash.geom.*; + + public class Piece + { + protected var size:Point; + protected var fullData:BitmapData; + protected var compareData:BitmapData; + + protected var centerPoint:Point; + public function get CenterPoint():Point { return centerPoint; } + + public function get FullData():BitmapData { return fullData; } + + protected var valid = false; + public function get Valid() { return valid; } + + public var Name:String = null; + + protected var usesBySequence = {}; + public function AddUse(sequence) + { + if (!(sequence in usesBySequence)) + { + usesBySequence[sequence] = 0; + } + usesBySequence[sequence]++; + } + + public function RemoveUse(sequence) + { + if (sequence in usesBySequence && usesBySequence[sequence] > 0) + { + usesBySequence[sequence]--; + } + } + + public function GetUses(sequence) + { + return (sequence in usesBySequence) ? usesBySequence[sequence] : 0; + } + + public function get TotalUses() + { + var count = 0; + for (var s in usesBySequence) + { + count += usesBySequence[s]; + } + return count; + } + + // returns the center point + public function InitFromClip(clip:DisplayObject, upscale:Number = 1):Point + { + var bounds:Rectangle = Utils.GetAccurateBounds(clip); + + size = new Point(bounds.width, bounds.height); + + valid = bounds.width > 0 && bounds.height > 0; + if (valid) + { + var m:Matrix = clip is Shape ? clip.transform.matrix.clone() : new Matrix(); + m.scale(upscale, upscale); + + Utils.TransformRect(bounds, m); + Utils.RoundRect(bounds); + + m.translate(-bounds.left, -bounds.top); + + fullData = new BitmapData(bounds.width, bounds.height, true, 0x0); + fullData.draw(clip, m, clip is Shape && clip.parent ? clip.parent.transform.colorTransform : clip.transform.colorTransform, null, null, true); + + compareData = GenerateCompareData(clip); + + centerPoint = new Point(-bounds.left, -bounds.top); + return centerPoint; + } + return null; + } + + public function InitFromBitmapData(bd:BitmapData, centerPoint:Point = null) + { + if (centerPoint == null) centerPoint = new Point(0, 0); + valid = true; + size = new Point(bd.width, bd.height); + + fullData = bd; + + compareData = bd.clone(); + + this.centerPoint = centerPoint; + } + + public function Matches(otherData:BitmapData, otherName) + { + + if (false && otherName != null && Name != null) + { + //This whole section fails to match things properly... + + var m = Name.match("^([^0-9]+)(\\d*)"); + var tempName = m ? m[1] : Name; + + var mo = otherName.match("^([^0-9]+)(\\d*)"); + var tempOtherName = mo ? mo[1] : otherName; + + return tempName == tempOtherName; + + + if (m) + { + if (otherName != m[1]) return false; + return true; + } + else + { + if (otherName != Name) return false; + return true; + } + } + + var threshold = 3; + var maxPixels = 0; + var result = compareData.compare(otherData); + if (result == 0) return true; + if (result is BitmapData) + { + var histo:Vector.> = (result as BitmapData).histogram(); + for (var i = 0; i < 4; i++) + { + var diff:Vector. = histo[i]; + // for some STUPID reason, if all the colors match but the alphas dont, it returns FFFFFF for the color portion... + // so DON'T compare the histogram for color channels up to 255 + var max = i == 4 ? 255 : 254; + for (var j = threshold; j < Math.min(max, diff.length); j++) + { + if (diff[j] > maxPixels) + { + return false; + } + } + } + return true; + } + return false; + } + + public static function GenerateCompareData(clip:DisplayObject):BitmapData + { + var scale = 1; + + var quickBounds:Rectangle = clip.getBounds(clip); + var orig:Rectangle = quickBounds.clone(); + if (quickBounds.width == 0 || quickBounds.height == 0) return null; + Utils.ScaleRect(quickBounds, scale); + Utils.RoundRect(quickBounds); + + var m:Matrix = clip is Shape ? clip.transform.matrix.clone() : new Matrix(); + m.scale(scale, scale); + m.translate(-quickBounds.left, -quickBounds.top); + + var data:BitmapData = new BitmapData(quickBounds.width, quickBounds.height, true, 0x0); + data.draw(clip, m, clip is Shape && clip.parent ? clip.parent.transform.colorTransform : clip.transform.colorTransform, null, null, true); + + return data; + } + } +} \ No newline at end of file diff --git a/PieceFrameInfo.as b/PieceFrameInfo.as new file mode 100644 index 0000000..a656f09 --- /dev/null +++ b/PieceFrameInfo.as @@ -0,0 +1,10 @@ +package +{ + import flash.geom.*; + public class PieceFrameInfo + { + public var Present = false; + public var Transform:Matrix = null; + public var Depth = null; + } +} \ No newline at end of file diff --git a/PieceLibrary.as b/PieceLibrary.as new file mode 100644 index 0000000..e5745bd --- /dev/null +++ b/PieceLibrary.as @@ -0,0 +1,154 @@ +package +{ + import flash.display.*; + import flash.geom.Point; + import flash.utils.*; + + public class PieceLibrary + { + protected var pieces:Array = []; + + public function Reset() + { + pieces.splice(0); + } + + private function GetArtistGivenName(clipName:String):String + { + if (clipName.match(/instance\d+/)) + { + return null; + } + return clipName; + } + + private function GetNextAvailableName(artistName:String):String + { + if (artistName == null) return null; + if (artistName.match("^instance\\d+$")) return null; + + var maxNameCount = 0; + for (var i in pieces) + { + if (pieces[i].Name != null) + { + var m = pieces[i].Name.match(new RegExp("^" + artistName + "(\\d+)?$")); + if (m) + { + maxNameCount = Math.max(maxNameCount, m[1] == undefined ? 1 : (Number(m[1]) + 1)); + } + } + } + return artistName + (maxNameCount == 0 ? "" : maxNameCount); + } + + public function GetPieceFromClip(clip:DisplayObject, outCenterPoint:Point, upscale:Number = 1) + { + var piece:Piece = GetExistingPiece(Piece.GenerateCompareData(clip), clip.name); + if (piece != null) return piece; + + piece = new Piece(); + var center:Point = piece.InitFromClip(clip, upscale); + if (piece.Valid) + { + piece.Name = GetNextAvailableName(clip.name); + + pieces.push(piece); + outCenterPoint.x = center.x; + outCenterPoint.y = center.y; + return piece; + } + + return null; + } + + private function GetExistingPiece(compareData:BitmapData, artistName:String) + { + if (compareData == null) return null; + + for (var i in pieces) + { + if (pieces[i].Matches(compareData, artistName)) + { + if (pieces[i].Name == null) + { + pieces[i].Name = GetNextAvailableName(artistName); + } + return pieces[i]; + } + } + + return null; + } + + public function GetPieceFromBitmapData(bd:BitmapData, pieceName:String, centerPoint:Point = null) + { + if (centerPoint == null) centerPoint = new Point(0, 0); + + var piece:Piece = GetExistingPiece(bd, pieceName); + if (piece != null) return piece; + + piece = new Piece(); + piece.InitFromBitmapData(bd, centerPoint); + if (piece.Valid) + { + piece.Name = GetNextAvailableName(pieceName); + pieces.push(piece); + return piece; + } + + return null; + } + + public function Debug() + { + var nextX = 0; + for (var i = 0; i < pieces.length; i++) + { + if (pieces[i].TotalUses > 0) + { + var b:Bitmap = new Bitmap(pieces[i].FullData); + b.scaleX = b.scaleY = 1; + b.x = nextX; + nextX += b.width; + } + } + } + + public function get NumUsedPieces() + { + var count = 0; + for (var i in this.pieces) + { + if (pieces[i].TotalUses > 0) + { + count++; + } + } + return count; + } + + public function GetAllUsedPieces():Array + { + var ret:Array = []; + for (var i in this.pieces) + { + if (pieces[i].TotalUses > 0) + { + ret.push(pieces[i]); + } + } + return ret; + } + + public function EnsureAllUsedPiecesHaveNames() + { + var index = 0; + var used:Array = GetAllUsedPieces(); + for (var i in used) + { + if (used[i].Name == null) used[i].Name = "UnknownPiece" + (index++); + } + } + } +} \ No newline at end of file diff --git a/PieceSequence.as b/PieceSequence.as new file mode 100644 index 0000000..64fde8e --- /dev/null +++ b/PieceSequence.as @@ -0,0 +1,62 @@ +package +{ + import flash.display.*; + import flash.geom.*; + public class PieceSequence + { + protected var piece:Piece; + protected var overridePiece:Piece; + public function GetOriginalPiece():Piece { return piece; } + public function GetPiece():Piece { return overridePiece ? overridePiece : piece; } + public function SetOverridePiece(p:Piece) { this.overridePiece = p; } + + public var Export:Boolean = true; + + //protected var centerPoint:Point; + //public function get CenterPoint():Point { return centerPoint; } + + public function GetFrame(frame):PieceFrameInfo + { + if (frame < 1 || frame > frames.length) return null; + return frames[frame - 1]; + } + + public function CopyTransformsFrom(other:PieceSequence, atDepth = null) + { + if (other.frames.length == this.frames.length) + { + for (var i = 0; i < frames.length; i++) + { + this.frames[i].Present = other.frames[i].Present; + if (other.frames[i].Present) + { + this.frames[i].Transform = other.frames[i].Transform.clone(); + this.frames[i].Depth = (atDepth == null) ? other.frames[i].Depth : atDepth; + } + } + } + } + + protected var frames:Array = []; + public function PieceSequence(piece:Piece, length) //, centerPoint:Point) + { + this.piece = piece; + //this.centerPoint = centerPoint; + for (var i = 0; i < length; i++) + { + frames.push(new PieceFrameInfo()); + } + } + + // clip matching info + public var BaseClip:DisplayObject = null; + public var Name = null; + + public function CheckMatches(clip:DisplayObject) + { + if (BaseClip != null && clip == BaseClip) return true; + if (Name != null && clip.name == null) return true; + return false; + } + } +} \ No newline at end of file diff --git a/AnimatedSkeletonGenerator.as b/AnimatedSkeletonGenerator.as new file mode 100644 index 0000000..20fec88 --- /dev/null +++ b/AnimatedSkeletonGenerator.as @@ -0,0 +1,580 @@ +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; + } + } +} \ No newline at end of file diff --git a/CharacterExport/PieceLibrary.as b/CharacterExport/PieceLibrary.as deleted file mode 100644 index d07255c..0000000 --- a/CharacterExport/PieceLibrary.as +++ /dev/null @@ -1,156 +0,0 @@ -package CharacterExport -{ - import flash.display.*; - import djarts.core.*; - import flash.geom.Point; - import flash.utils.*; - - public class PieceLibrary - { - protected var pieces:Array = []; - - public function Reset() - { - pieces.splice(0); - } - - private function GetArtistGivenName(clipName:String):String - { - if (clipName.match(/instance\d+/)) - { - return null; - } - return clipName; - } - - private function GetNextAvailableName(artistName:String):String - { - if (artistName == null) return null; - if (artistName.match("^instance\\d+$")) return null; - - var maxNameCount = 0; - for (var i in pieces) - { - if (pieces[i].Name != null) - { - var m = pieces[i].Name.match(new RegExp("^" + artistName + "(\\d+)?$")); - if (m) - { - maxNameCount = Math.max(maxNameCount, m[1] == undefined ? 1 : (Number(m[1]) + 1)); - } - } - } - return artistName + (maxNameCount == 0 ? "" : maxNameCount); - } - - public function GetPieceFromClip(clip:DisplayObject, outCenterPoint:Point, upscale:Number = 1) - { - var piece:Piece = GetExistingPiece(Piece.GenerateCompareData(clip), clip.name); - if (piece != null) return piece; - - piece = new Piece(); - var center:Point = piece.InitFromClip(clip, upscale); - if (piece.Valid) - { - piece.Name = GetNextAvailableName(clip.name); - - pieces.push(piece); - outCenterPoint.x = center.x; - outCenterPoint.y = center.y; - return piece; - } - - return null; - } - - private function GetExistingPiece(compareData:BitmapData, artistName:String) - { - if (compareData == null) return null; - - for (var i in pieces) - { - if (pieces[i].Matches(compareData, artistName)) - { - if (pieces[i].Name == null) - { - pieces[i].Name = GetNextAvailableName(artistName); - } - return pieces[i]; - } - } - - return null; - } - - public function GetPieceFromBitmapData(bd:BitmapData, pieceName:String, centerPoint:Point = null) - { - if (centerPoint == null) centerPoint = new Point(0, 0); - - var piece:Piece = GetExistingPiece(bd, pieceName); - if (piece != null) return piece; - - piece = new Piece(); - piece.InitFromBitmapData(bd, centerPoint); - if (piece.Valid) - { - piece.Name = GetNextAvailableName(pieceName); - pieces.push(piece); - return piece; - } - - return null; - } - - public function Debug() - { - var nextX = 0; - for (var i = 0; i < pieces.length; i++) - { - if (pieces[i].TotalUses > 0) - { - var b:Bitmap = new Bitmap(pieces[i].FullData); - b.scaleX = b.scaleY = 1; - b.x = nextX; - Globals.Instance().GetStage().addChild(b); - nextX += b.width; - } - } - } - - public function get NumUsedPieces() - { - var count = 0; - for (var i in this.pieces) - { - if (pieces[i].TotalUses > 0) - { - count++; - } - } - return count; - } - - public function GetAllUsedPieces():Array - { - var ret:Array = []; - for (var i in this.pieces) - { - if (pieces[i].TotalUses > 0) - { - ret.push(pieces[i]); - } - } - return ret; - } - - public function EnsureAllUsedPiecesHaveNames() - { - var index = 0; - var used:Array = GetAllUsedPieces(); - for (var i in used) - { - if (used[i].Name == null) used[i].Name = "UnknownPiece" + (index++); - } - } - } -} \ No newline at end of file diff --git a/DebugExporter.swf b/DebugExporter.swf index b19375a..9ecb975 100755 --- a/DebugExporter.swf +++ b/DebugExporter.swf Binary files differ diff --git a/GraphicExportDef.as b/GraphicExportDef.as index e4202f3..2e4b25b 100755 --- a/GraphicExportDef.as +++ b/GraphicExportDef.as @@ -100,6 +100,9 @@ case TYPE_SPRITES: frames = new AnimatedSpriteGenerator(clip, this); break; + case TYPE_SKELETAL: + frames = new AnimatedSkeletonGenerator(clip, this); + break; } if (frames == null) diff --git a/Piece.as b/Piece.as new file mode 100644 index 0000000..dc07fd2 --- /dev/null +++ b/Piece.as @@ -0,0 +1,171 @@ +package +{ + import flash.display.*; + import flash.geom.*; + + public class Piece + { + protected var size:Point; + protected var fullData:BitmapData; + protected var compareData:BitmapData; + + protected var centerPoint:Point; + public function get CenterPoint():Point { return centerPoint; } + + public function get FullData():BitmapData { return fullData; } + + protected var valid = false; + public function get Valid() { return valid; } + + public var Name:String = null; + + protected var usesBySequence = {}; + public function AddUse(sequence) + { + if (!(sequence in usesBySequence)) + { + usesBySequence[sequence] = 0; + } + usesBySequence[sequence]++; + } + + public function RemoveUse(sequence) + { + if (sequence in usesBySequence && usesBySequence[sequence] > 0) + { + usesBySequence[sequence]--; + } + } + + public function GetUses(sequence) + { + return (sequence in usesBySequence) ? usesBySequence[sequence] : 0; + } + + public function get TotalUses() + { + var count = 0; + for (var s in usesBySequence) + { + count += usesBySequence[s]; + } + return count; + } + + // returns the center point + public function InitFromClip(clip:DisplayObject, upscale:Number = 1):Point + { + var bounds:Rectangle = Utils.GetAccurateBounds(clip); + + size = new Point(bounds.width, bounds.height); + + valid = bounds.width > 0 && bounds.height > 0; + if (valid) + { + var m:Matrix = clip is Shape ? clip.transform.matrix.clone() : new Matrix(); + m.scale(upscale, upscale); + + Utils.TransformRect(bounds, m); + Utils.RoundRect(bounds); + + m.translate(-bounds.left, -bounds.top); + + fullData = new BitmapData(bounds.width, bounds.height, true, 0x0); + fullData.draw(clip, m, clip is Shape && clip.parent ? clip.parent.transform.colorTransform : clip.transform.colorTransform, null, null, true); + + compareData = GenerateCompareData(clip); + + centerPoint = new Point(-bounds.left, -bounds.top); + return centerPoint; + } + return null; + } + + public function InitFromBitmapData(bd:BitmapData, centerPoint:Point = null) + { + if (centerPoint == null) centerPoint = new Point(0, 0); + valid = true; + size = new Point(bd.width, bd.height); + + fullData = bd; + + compareData = bd.clone(); + + this.centerPoint = centerPoint; + } + + public function Matches(otherData:BitmapData, otherName) + { + + if (false && otherName != null && Name != null) + { + //This whole section fails to match things properly... + + var m = Name.match("^([^0-9]+)(\\d*)"); + var tempName = m ? m[1] : Name; + + var mo = otherName.match("^([^0-9]+)(\\d*)"); + var tempOtherName = mo ? mo[1] : otherName; + + return tempName == tempOtherName; + + + if (m) + { + if (otherName != m[1]) return false; + return true; + } + else + { + if (otherName != Name) return false; + return true; + } + } + + var threshold = 3; + var maxPixels = 0; + var result = compareData.compare(otherData); + if (result == 0) return true; + if (result is BitmapData) + { + var histo:Vector.> = (result as BitmapData).histogram(); + for (var i = 0; i < 4; i++) + { + var diff:Vector. = histo[i]; + // for some STUPID reason, if all the colors match but the alphas dont, it returns FFFFFF for the color portion... + // so DON'T compare the histogram for color channels up to 255 + var max = i == 4 ? 255 : 254; + for (var j = threshold; j < Math.min(max, diff.length); j++) + { + if (diff[j] > maxPixels) + { + return false; + } + } + } + return true; + } + return false; + } + + public static function GenerateCompareData(clip:DisplayObject):BitmapData + { + var scale = 1; + + var quickBounds:Rectangle = clip.getBounds(clip); + var orig:Rectangle = quickBounds.clone(); + if (quickBounds.width == 0 || quickBounds.height == 0) return null; + Utils.ScaleRect(quickBounds, scale); + Utils.RoundRect(quickBounds); + + var m:Matrix = clip is Shape ? clip.transform.matrix.clone() : new Matrix(); + m.scale(scale, scale); + m.translate(-quickBounds.left, -quickBounds.top); + + var data:BitmapData = new BitmapData(quickBounds.width, quickBounds.height, true, 0x0); + data.draw(clip, m, clip is Shape && clip.parent ? clip.parent.transform.colorTransform : clip.transform.colorTransform, null, null, true); + + return data; + } + } +} \ No newline at end of file diff --git a/PieceFrameInfo.as b/PieceFrameInfo.as new file mode 100644 index 0000000..a656f09 --- /dev/null +++ b/PieceFrameInfo.as @@ -0,0 +1,10 @@ +package +{ + import flash.geom.*; + public class PieceFrameInfo + { + public var Present = false; + public var Transform:Matrix = null; + public var Depth = null; + } +} \ No newline at end of file diff --git a/PieceLibrary.as b/PieceLibrary.as new file mode 100644 index 0000000..e5745bd --- /dev/null +++ b/PieceLibrary.as @@ -0,0 +1,154 @@ +package +{ + import flash.display.*; + import flash.geom.Point; + import flash.utils.*; + + public class PieceLibrary + { + protected var pieces:Array = []; + + public function Reset() + { + pieces.splice(0); + } + + private function GetArtistGivenName(clipName:String):String + { + if (clipName.match(/instance\d+/)) + { + return null; + } + return clipName; + } + + private function GetNextAvailableName(artistName:String):String + { + if (artistName == null) return null; + if (artistName.match("^instance\\d+$")) return null; + + var maxNameCount = 0; + for (var i in pieces) + { + if (pieces[i].Name != null) + { + var m = pieces[i].Name.match(new RegExp("^" + artistName + "(\\d+)?$")); + if (m) + { + maxNameCount = Math.max(maxNameCount, m[1] == undefined ? 1 : (Number(m[1]) + 1)); + } + } + } + return artistName + (maxNameCount == 0 ? "" : maxNameCount); + } + + public function GetPieceFromClip(clip:DisplayObject, outCenterPoint:Point, upscale:Number = 1) + { + var piece:Piece = GetExistingPiece(Piece.GenerateCompareData(clip), clip.name); + if (piece != null) return piece; + + piece = new Piece(); + var center:Point = piece.InitFromClip(clip, upscale); + if (piece.Valid) + { + piece.Name = GetNextAvailableName(clip.name); + + pieces.push(piece); + outCenterPoint.x = center.x; + outCenterPoint.y = center.y; + return piece; + } + + return null; + } + + private function GetExistingPiece(compareData:BitmapData, artistName:String) + { + if (compareData == null) return null; + + for (var i in pieces) + { + if (pieces[i].Matches(compareData, artistName)) + { + if (pieces[i].Name == null) + { + pieces[i].Name = GetNextAvailableName(artistName); + } + return pieces[i]; + } + } + + return null; + } + + public function GetPieceFromBitmapData(bd:BitmapData, pieceName:String, centerPoint:Point = null) + { + if (centerPoint == null) centerPoint = new Point(0, 0); + + var piece:Piece = GetExistingPiece(bd, pieceName); + if (piece != null) return piece; + + piece = new Piece(); + piece.InitFromBitmapData(bd, centerPoint); + if (piece.Valid) + { + piece.Name = GetNextAvailableName(pieceName); + pieces.push(piece); + return piece; + } + + return null; + } + + public function Debug() + { + var nextX = 0; + for (var i = 0; i < pieces.length; i++) + { + if (pieces[i].TotalUses > 0) + { + var b:Bitmap = new Bitmap(pieces[i].FullData); + b.scaleX = b.scaleY = 1; + b.x = nextX; + nextX += b.width; + } + } + } + + public function get NumUsedPieces() + { + var count = 0; + for (var i in this.pieces) + { + if (pieces[i].TotalUses > 0) + { + count++; + } + } + return count; + } + + public function GetAllUsedPieces():Array + { + var ret:Array = []; + for (var i in this.pieces) + { + if (pieces[i].TotalUses > 0) + { + ret.push(pieces[i]); + } + } + return ret; + } + + public function EnsureAllUsedPiecesHaveNames() + { + var index = 0; + var used:Array = GetAllUsedPieces(); + for (var i in used) + { + if (used[i].Name == null) used[i].Name = "UnknownPiece" + (index++); + } + } + } +} \ No newline at end of file diff --git a/PieceSequence.as b/PieceSequence.as new file mode 100644 index 0000000..64fde8e --- /dev/null +++ b/PieceSequence.as @@ -0,0 +1,62 @@ +package +{ + import flash.display.*; + import flash.geom.*; + public class PieceSequence + { + protected var piece:Piece; + protected var overridePiece:Piece; + public function GetOriginalPiece():Piece { return piece; } + public function GetPiece():Piece { return overridePiece ? overridePiece : piece; } + public function SetOverridePiece(p:Piece) { this.overridePiece = p; } + + public var Export:Boolean = true; + + //protected var centerPoint:Point; + //public function get CenterPoint():Point { return centerPoint; } + + public function GetFrame(frame):PieceFrameInfo + { + if (frame < 1 || frame > frames.length) return null; + return frames[frame - 1]; + } + + public function CopyTransformsFrom(other:PieceSequence, atDepth = null) + { + if (other.frames.length == this.frames.length) + { + for (var i = 0; i < frames.length; i++) + { + this.frames[i].Present = other.frames[i].Present; + if (other.frames[i].Present) + { + this.frames[i].Transform = other.frames[i].Transform.clone(); + this.frames[i].Depth = (atDepth == null) ? other.frames[i].Depth : atDepth; + } + } + } + } + + protected var frames:Array = []; + public function PieceSequence(piece:Piece, length) //, centerPoint:Point) + { + this.piece = piece; + //this.centerPoint = centerPoint; + for (var i = 0; i < length; i++) + { + frames.push(new PieceFrameInfo()); + } + } + + // clip matching info + public var BaseClip:DisplayObject = null; + public var Name = null; + + public function CheckMatches(clip:DisplayObject) + { + if (BaseClip != null && clip == BaseClip) return true; + if (Name != null && clip.name == null) return true; + return false; + } + } +} \ No newline at end of file diff --git a/Utils.as b/Utils.as index c76edbe..45db712 100644 --- a/Utils.as +++ b/Utils.as @@ -4,6 +4,136 @@ import flash.geom.*; public class Utils { + public static function GetChildren(clip:MovieClip):Array + { + var ret:Array = []; + for (var i = 0; i < clip.numChildren; i++) + { + ret.push(clip.getChildAt(i)); + } + return ret; + } + + /* + * angle difference + * (range -Math.PI to Math.PI) + */ + public static function GetAngleDiff(a, b) + { + var angleDiff = b - a; + + while (angleDiff > Math.PI) + { + angleDiff -= Math.PI * 2; + } + while (angleDiff < -Math.PI) + { + angleDiff += Math.PI * 2; + } + return angleDiff; + } + + /* + * angle difference + * (range 0 to Math.PI) + */ + public static function GetAngleDiffAbs(a, b) + { + var angleDiff = GetAngleDiff(a, b); + while (angleDiff < 0) + { + angleDiff += Math.PI * 2; + } + + if (angleDiff > Math.PI) + { + angleDiff = Math.PI * 2 - angleDiff; + } + + return angleDiff; + } + + public static function GetTransform(from:Matrix, to:Matrix) + { + var i:Matrix = from.clone(); + i.invert(); + var m:Matrix = to.clone(); + m.concat(i); + return m; + } + + public static function Decompose(m:Matrix) + { + var s:Point = ScaleFromMat(m); + m = m.clone(); + + var sxUnit = s.x >= 0 ? 1 : -1; + var syUnit = s.y >= 0 ? 1 : -1; + + m.scale(sxUnit, syUnit); + + //m.a /= sxUnit; + //m.d /= syUnit; + var rot = RotFromMat(m); + return {sx:s.x, sy:s.y, rot:rot}; + } + + public static function RotFromMat(m:Matrix) + { + return Math.atan2(-m.c, m.a); + } + + public static function ScaleFromMat(m:Matrix):Point + { + var xAxisX = m.a; + var xAxisY = m.c; + + var yAxisX = m.b; + var yAxisY = m.d; + + var sx = Math.sqrt(xAxisX * xAxisX + xAxisY * xAxisY); + if (m.a < 0) sx *= -1; + + var sy = Math.sqrt(yAxisX * yAxisX + yAxisY * yAxisY); + if (m.d < 0) sy *= -1; + + return new Point(sx, sy); + } + + public static function TransformRect(r:Rectangle, m:Matrix) + { + var p1:Point = new Point(r.left, r.top); + var p2:Point = new Point(r.right, r.top); + var p3:Point = new Point(r.left, r.bottom); + var p4:Point = new Point(r.right, r.bottom); + + p1 = m.transformPoint(p1); + p2 = m.transformPoint(p2); + p3 = m.transformPoint(p3); + p4 = m.transformPoint(p4); + + r.left = Math.min(p1.x, p2.x, p3.x, p4.x); + r.top = Math.min(p1.y, p2.y, p3.y, p4.y); + r.right = Math.max(p1.x, p2.x, p3.x, p4.x); + r.bottom = Math.max(p1.y, p2.y, p3.y, p4.y); + } + + public static function RoundRect(r:Rectangle) + { + r.left = Math.floor(r.left); + r.top = Math.floor(r.top); + r.right = Math.ceil(r.right); + r.bottom = Math.ceil(r.bottom); + } + + public static function ScaleRect(r:Rectangle, s) + { + r.left *= s; + r.top *= s; + r.right *= s; + r.bottom *= s; + } + public static function RecursivelyStop(clip) { if (clip is MovieClip)