From 890602c7d0535abca034cb4953c114efb889bd76 Mon Sep 17 00:00:00 2001 From: Yoshman29 <81191621+YoshiCrafter29@users.noreply.github.com> Date: Mon, 7 Nov 2022 13:34:39 +0100 Subject: [PATCH] animate atlas stuff todo --- Project.xml | 1 + source/animateatlas/AtlasFrameMaker.hx | 149 +++++ source/animateatlas/HelperEnums.hx | 21 + source/animateatlas/JSONData.hx | 157 +++++ source/animateatlas/Main.hx | 109 ++++ .../displayobject/SpriteAnimationLibrary.hx | 225 ++++++++ .../displayobject/SpriteMovieClip.hx | 117 ++++ .../displayobject/SpriteSymbol.hx | 540 ++++++++++++++++++ .../tilecontainer/TileAnimationLibrary.hx | 226 ++++++++ .../tilecontainer/TileContainerMovieClip.hx | 135 +++++ .../tilecontainer/TileContainerSymbol.hx | 475 +++++++++++++++ source/funkin/system/CoolUtil.hx | 6 + source/funkin/system/Main.hx | 2 + update.bat | 1 + 14 files changed, 2164 insertions(+) create mode 100644 source/animateatlas/AtlasFrameMaker.hx create mode 100644 source/animateatlas/HelperEnums.hx create mode 100644 source/animateatlas/JSONData.hx create mode 100644 source/animateatlas/Main.hx create mode 100644 source/animateatlas/displayobject/SpriteAnimationLibrary.hx create mode 100644 source/animateatlas/displayobject/SpriteMovieClip.hx create mode 100644 source/animateatlas/displayobject/SpriteSymbol.hx create mode 100644 source/animateatlas/tilecontainer/TileAnimationLibrary.hx create mode 100644 source/animateatlas/tilecontainer/TileContainerMovieClip.hx create mode 100644 source/animateatlas/tilecontainer/TileContainerSymbol.hx diff --git a/Project.xml b/Project.xml index f8495d3a..91d8510e 100644 --- a/Project.xml +++ b/Project.xml @@ -74,6 +74,7 @@ + diff --git a/source/animateatlas/AtlasFrameMaker.hx b/source/animateatlas/AtlasFrameMaker.hx new file mode 100644 index 00000000..d553a5c1 --- /dev/null +++ b/source/animateatlas/AtlasFrameMaker.hx @@ -0,0 +1,149 @@ +package animateatlas; +import flixel.util.FlxDestroyUtil; +import openfl.geom.Rectangle; +import flixel.math.FlxPoint; +import flixel.math.FlxRect; +import openfl.Assets; +import haxe.Json; +import openfl.display.BitmapData; +import animateatlas.JSONData.AtlasData; +import animateatlas.JSONData.AnimationData; +import animateatlas.displayobject.SpriteAnimationLibrary; +import animateatlas.displayobject.SpriteMovieClip; +import flixel.graphics.FlxGraphic; +import flixel.graphics.frames.FlxFramesCollection; +import flixel.graphics.frames.FlxFrame; +using StringTools; +class AtlasFrameMaker extends FlxFramesCollection{ + + + //public static var widthoffset:Int = 0; + //public static var heightoffset:Int = 0; + //public static var excludeArray:Array; + /** + + * Creates Frames from TextureAtlas(very early and broken ok) Originally made for FNF HD by Smokey and Rozebud + * + * @param key The file path. + * @param _excludeArray Use this to only create selected animations. Keep null to create all of them. + * + */ + + public static function construct(key:String,?_excludeArray:Array = null):FlxFramesCollection{ + + // widthoffset = _widthoffset; + // heightoffset = _heightoffset; + + + + var frameCollection:FlxFramesCollection; + var frameArray:Array> = []; + var animationData:AnimationData = Json.parse(Assets.getText(key + "/Animation.json")); + var atlasData:AtlasData = Json.parse(Assets.getText(key + "/spritemap.json").replace("\uFEFF", ""));//FIXED UTF8 w/ BOM error + var bitmapData:BitmapData = Assets.getBitmapData(key + "/spritemap.png"); + var ss = new SpriteAnimationLibrary(animationData, atlasData, bitmapData); + var t = ss.createAnimation(); + if(_excludeArray == null){ + _excludeArray = t.getFrameLabels(); + } + frameCollection = new FlxFramesCollection(FlxGraphic.fromBitmapData(bitmapData),FlxFrameCollectionType.IMAGE); + + + for(x in t.getFrameLabels()){ + + frameArray.push(getFramesArray(t, x,_excludeArray)); + + + } + + for(x in frameArray){ + for(y in x){ + frameCollection.pushFrame(y); + } + } + + return frameCollection; + + } + + @:noCompletion static function getFramesArray(t:SpriteMovieClip,animation:String,_excludeArray:Array):Array + { + var sizeInfo:Rectangle = new Rectangle(0,0); + t.currentLabel = animation; + var bitMapArray:Array = []; + var daFramez:Array = []; + var firstPass = true; + var frameSize:FlxPoint = new FlxPoint(0,0); + + for (i in t.getFrame(animation)...t.numFrames){ + t.currentFrame = i; + if (t.currentLabel == animation){ + if (_excludeArray.contains(animation)){ + sizeInfo = t.getBounds(t); + + var bitmapShit:BitmapData = new BitmapData( + Std.int(sizeInfo.width + Math.abs(sizeInfo.x)),Std.int(sizeInfo.height +Math.abs(sizeInfo.y)),true,0); + + bitmapShit.draw(t,null,null,null,null,true); + bitMapArray.push(bitmapShit); + + if (firstPass){ + frameSize.set(bitmapShit.width,bitmapShit.height); + firstPass = false; + } + } + } + else break; + } + + for (i in 0...bitMapArray.length){ + var b = FlxGraphic.fromBitmapData(bitMapArray[i]); + var theFrame = new FlxFrame(b); + theFrame.parent = b; + theFrame.name = animation + i; + theFrame.sourceSize.set(frameSize.x,frameSize.y); + theFrame.frame = new FlxRect(0, 0, bitMapArray[i].width, bitMapArray[i].height); + daFramez.push(theFrame); + + //trace(daFramez); + } + return daFramez; + } + + /*public static function renderTest(key:String, animation:String, ?frame:Int = 0):Void + { + var animationData:AnimationData = Json.parse(Assets.getText(key + "/Animation.json")); + var atlasData:AtlasData = Json.parse(Assets.getText(key + "/spritemap.json")); + var bitmapData:BitmapData = Assets.getBitmapData(key + "/spritemap.png"); + var ss = new SpriteAnimationLibrary(animationData, atlasData, bitmapData); + var t = ss.createAnimation(); + var bitMapArray:Array = []; + + for (i in 0...t.numFrames){ + t.currentFrame = i; + if (t.currentLabel == animation){ + //trace(t.currentFrame); + var bitmapShit:BitmapData = new BitmapData(Std.int(t.width),Std.int(t.height),true,0); + //bitmapShit.drawWithQuality(t,null,null,null,null,true); + bitmapShit.draw(t, new Matrix(1, 0, 0, 1, t.width/2, t.height/2), null, null, null, false); + bitMapArray.push(bitmapShit); + } + } + + saveImage(bitMapArray[frame]); + + } + + public static function saveImage(bitmapData:BitmapData) + { + var b:ByteArray = new ByteArray(); + b = bitmapData.encode(bitmapData.rect, new PNGEncoderOptions(true), b); + new FileDialog().save(b, "png", null, "file"); + } + */ + + + + + +} diff --git a/source/animateatlas/HelperEnums.hx b/source/animateatlas/HelperEnums.hx new file mode 100644 index 00000000..23f05870 --- /dev/null +++ b/source/animateatlas/HelperEnums.hx @@ -0,0 +1,21 @@ +package animateatlas; + +enum abstract LoopMode(String) from String to String { + public static inline var LOOP:String = "loop"; + public static inline var PLAY_ONCE:String = "playonce"; + public static inline var SINGLE_FRAME:String = "singleframe"; + + public static function isValid(value:String):Bool { + return value == LOOP || value == PLAY_ONCE || value == SINGLE_FRAME; + } +} + +enum abstract SymbolType(String) from String to String { + public static inline var GRAPHIC:String = "graphic"; + public static inline var MOVIE_CLIP:String = "movieclip"; + public static inline var BUTTON:String = "button"; + + public static function isValid(value:String):Bool { + return value == GRAPHIC || value == MOVIE_CLIP || value == BUTTON; + } +} diff --git a/source/animateatlas/JSONData.hx b/source/animateatlas/JSONData.hx new file mode 100644 index 00000000..fbb44e85 --- /dev/null +++ b/source/animateatlas/JSONData.hx @@ -0,0 +1,157 @@ +package animateatlas; + +/** + * All data needed for the json importer + some extra for after parsing. + * Stolen mostly from https://github.com/TomByrne/Starling-Extension-Adobe-Animate-Hx/blob/master/hx/src/starling/extensions/animate/AnimationAtlasData.hx + */ +typedef AnimationData = { + ?ANIMATION:SymbolData, + ?SYMBOL_DICTIONARY:{ + Symbols:Array + }, + ?metadata:{ + ?framerate:Null + } +} + +typedef AtlasData = { + ?ATLAS:{ + SPRITES:Array + }, + ?meta:{ + app:String, + version:String, + image:String, + format:String, + size:{w:Int, h:Int}, + scale:String, + } +} + +typedef SpriteDummy = { + SPRITE:SpriteData +} + +typedef SpriteData = { + name:String, + x:Int, + y:Int, + w:Int, + h:Int, + rotated:Bool +} + +typedef SymbolData = { + ?name:String, + SYMBOL_name:String, + ?TIMELINE:SymbolTimelineData +} + +typedef SymbolTimelineData = { + ?sortedForRender:Bool, + LAYERS:Array +} + +typedef LayerData = { + Layer_name:String, + Frames:Array, + FrameMap:Map +} + +typedef LayerFrameData = { + index:Int, + ?name:String, + duration:Int, + elements:Array +} + +typedef ElementData = { + ?ATLAS_SPRITE_instance:Dynamic, + ?SYMBOL_Instance:SymbolInstanceData +} + +typedef SymbolInstanceData = { + SYMBOL_name:String, + Instance_Name:String, + bitmap:BitmapPosData, + symbolType:String, + transformationPoint:PointData, + Matrix3D:Matrix3DData, + ?DecomposedMatrix:Decomposed3DData, + ?color:ColorData, + + ?loop:String, + firstFrame:Int, + ?filters:FilterData +} + +typedef ColorData = { + mode:String, + + ?RedMultiplier:Float, + ?greenMultiplier:Float, + ?blueMultiplier:Float, + ?alphaMultiplier:Float, + ?redOffset:Float, + ?greenOffset:Float, + ?blueOffset:Float, + ?AlphaOffset:Float, + ?tintColor:String, + ?tintMultiplier:Float +} + +typedef BitmapPosData = { + name:String, + Position:PointData, +} + +typedef PointData = { + x:Int, + y:Int +} + +typedef Matrix3DData = { + m00:Float, + m01:Float, + m02:Float, + m03:Float, + m10:Float, + m11:Float, + m12:Float, + m13:Float, + m20:Float, + m21:Float, + m22:Float, + m23:Float, + m30:Float, + m31:Float, + m32:Float, + m33:Float, +} +//tryna add more support gimme a sec +typedef FilterData = { + ?BlurFilter: { + blurX:Float, + blurY:Float, + quality:Int + }, + + ?GlowFilter: { + blurX: Float, + blurY: Float, + color: Int, + alpha: Int, + quality: Int, + strength: Int, + knockout: Bool, + inner: Bool + } + + +} + +typedef Decomposed3DData = { + Position:{x:Float, y:Float, z:Float}, + Rotation:{x:Float, y:Float, z:Float}, + Scaling:{x:Float, y:Float, z:Float}, +} diff --git a/source/animateatlas/Main.hx b/source/animateatlas/Main.hx new file mode 100644 index 00000000..8bbe8105 --- /dev/null +++ b/source/animateatlas/Main.hx @@ -0,0 +1,109 @@ +package; + +import animateatlas.JSONData.AtlasData; +import openfl.display.BitmapData; +import animateatlas.JSONData.AnimationData; +import openfl.display.FPS; +import openfl.Lib; +import openfl.events.MouseEvent; +import animateatlas.HelperEnums.LoopMode; +import openfl.events.Event; +import openfl.display.Tilemap; +import openfl.display.Tileset; +import openfl.Assets; +import haxe.Json; +import animateatlas.tilecontainer.TileAnimationLibrary; +import animateatlas.tilecontainer.TileContainerMovieClip; +import animateatlas.displayobject.SpriteAnimationLibrary; +import animateatlas.displayobject.SpriteMovieClip; +import openfl.display.Sprite; + +class Main extends Sprite { + var aa:TileAnimationLibrary; + var ss:SpriteAnimationLibrary; + + // + var tileSymbols:Array; + + var spriteSymbols:Array; + + var renderer:Tilemap; + + public function new() { + super(); + graphics.beginFill(0x333333); + graphics.drawRect(0, 0, Lib.current.stage.stageWidth, Lib.current.stage.stageHeight); + + var animationData:AnimationData = Json.parse(Assets.getText("assets/TEST/Animation.json")); + var atlasData:AtlasData = Json.parse(Assets.getText("assets/TEST/spritemap.json")); + var bitmapData:BitmapData = Assets.getBitmapData("assets/TEST/spritemap.png"); + + aa = new TileAnimationLibrary(animationData, atlasData, bitmapData); + ss = new SpriteAnimationLibrary(animationData, atlasData, bitmapData); + + renderer = new Tilemap(Lib.current.stage.stageWidth, Lib.current.stage.stageHeight, null, true); + + renderer.tileAlphaEnabled = false; + renderer.tileBlendModeEnabled = false; + renderer.tileColorTransformEnabled = false; + + addChild(renderer); + addChild(new FPS(10, 10, 0xFFFFFF)); + + tileSymbols = []; + spriteSymbols = []; + + addEventListener(Event.ENTER_FRAME, update); + // addEventListener(MouseEvent.CLICK, addSpriteGirl); + addEventListener(MouseEvent.CLICK, addTileGirl); + } + + var prev:Int = 0; + var dt:Int = 0; + var curr:Int = 0; + + public function update(_) { + // making a dt + curr = Lib.getTimer(); + dt = curr - prev; + prev = curr; + + for (symbol in tileSymbols) { + symbol.update(dt); + } + for (symbol in spriteSymbols) { + symbol.update(dt); + } + } + + public function addSpriteGirl(_) { + for (i in 0...1) { + var t = ss.createAnimation(); + t.x = mouseX + i * 20 * (-1 * i % 2); + t.y = mouseY + i * 20 * (-1 * i % 2); + + addChild(t); + t.loopMode = LoopMode.SINGLE_FRAME; + + t.currentLabel = t.getFrameLabels()[Std.random(t.getFrameLabels().length)]; + spriteSymbols.push(t); + trace(spriteSymbols.length); + } + } + + public function addTileGirl(_) { + for (i in 0...1) { + var t = aa.createAnimation(); + t.x = mouseX + i * 5 * (-1 * i % 2); + t.y = mouseY + i * 5 * (-1 * i % 2); + + renderer.addTile(t); + t.loopMode = LoopMode.SINGLE_FRAME; + + t.currentLabel = t.getFrameLabels()[Std.random(t.getFrameLabels().length)]; + tileSymbols.push(t); + + trace(tileSymbols.length); + } + } +} diff --git a/source/animateatlas/displayobject/SpriteAnimationLibrary.hx b/source/animateatlas/displayobject/SpriteAnimationLibrary.hx new file mode 100644 index 00000000..2f835ab4 --- /dev/null +++ b/source/animateatlas/displayobject/SpriteAnimationLibrary.hx @@ -0,0 +1,225 @@ +package animateatlas.displayobject; + +import openfl.display.BitmapData; +import animateatlas.JSONData.AnimationData; +import animateatlas.JSONData.ElementData; +import animateatlas.JSONData.LayerFrameData; +import animateatlas.JSONData.LayerData; +import animateatlas.JSONData.SymbolTimelineData; +import animateatlas.JSONData.Matrix3DData; +import animateatlas.JSONData.AtlasData; +import animateatlas.JSONData.SymbolData; +import animateatlas.JSONData.SpriteData; +import animateatlas.HelperEnums.LoopMode; +import animateatlas.HelperEnums.SymbolType; +import openfl.errors.ArgumentError; + +/** + * Performance will be REALLY BAD. + * Consider using TileAnimationLibrary whenever possible. + */ +class SpriteAnimationLibrary { + public var frameRate:Float; + + private var _atlas:Map; + private var _symbolData:Map; + private var _symbolPool:Map>; + private var _defaultSymbolName:String; + private var _texture:BitmapData; + + public static inline var BITMAP_SYMBOL_NAME:String = "___atlas_sprite___"; + + private static var STD_MATRIX3D_DATA:Matrix3DData = { + m00: 1, + m01: 0, + m02: 0, + m03: 0, + m10: 0, + m11: 1, + m12: 0, + m13: 0, + m20: 0, + m21: 0, + m22: 1, + m23: 0, + m30: 0, + m31: 0, + m32: 0, + m33: 1 + }; + + public function new(data:AnimationData, atlas:AtlasData, texture:BitmapData) { + parseAnimationData(data); + parseAtlasData(atlas); + _texture = texture; + _symbolPool = new Map(); + } + + public function hasAnimation(name:String):Bool { + return hasSymbol(name); + } + + public function createAnimation(symbol:String = null):SpriteMovieClip { + symbol = (symbol != null) ? symbol : _defaultSymbolName; + if (!hasSymbol(symbol)) { + throw new ArgumentError("Symbol not found: " + symbol); + } + return new SpriteMovieClip(getSymbol(symbol)); + } + + public function getAnimationNames(prefix:String = ""):Array { + var out = new Array(); + + for (name in _symbolData.keys()) { + if (name != BITMAP_SYMBOL_NAME && name.indexOf(prefix) == 0) { + out.push(name); + } + } + + // but... why? + out.sort(function(a1, a2):Int { + a1 = a1.toLowerCase(); + a2 = a2.toLowerCase(); + if (a1 < a2) { + return -1; + } else if (a1 > a2) { + return 1; + } else { + return 0; + } + }); + return out; + } + + private function getSpriteData(name:String):SpriteData { + return _atlas.get(name); + } + + private function hasSymbol(name:String):Bool { + return _symbolData.exists(name); + } + + // # region Pooling + // todo migrate this to lime pool + + @:access(animateatlas) + @:allow(AtlasFrameMaker) + private function getSymbol(name:String):SpriteSymbol { + var pool:Array = getSymbolPool(name); + if (pool.length == 0) { + return new SpriteSymbol(getSymbolData(name), this, _texture); + } else { + return pool.pop(); + } + } + + private function putSymbol(symbol:SpriteSymbol):Void { + symbol.reset(); + var pool:Array = getSymbolPool(symbol.symbolName); + pool.push(symbol); + symbol.currentFrame = 0; + } + + private function getSymbolPool(name:String):Array { + var pool:Array = _symbolPool.get(name); + if (pool == null) { + pool = []; + _symbolPool.set(name, pool); + } + return pool; + } + + // # end region + // # region helpers + private function parseAnimationData(data:AnimationData):Void { + var metaData = data.metadata; + + if (metaData != null && metaData.framerate != null && metaData.framerate > 0) { + frameRate = (metaData.framerate); + } else { + frameRate = 24; + } + + _symbolData = new Map(); + + // the actual symbol dictionary + var symbols = data.SYMBOL_DICTIONARY.Symbols; + for (symbolData in symbols) { + _symbolData[symbolData.SYMBOL_name] = preprocessSymbolData(symbolData); + } + + // the main animation + var defaultSymbolData:SymbolData = preprocessSymbolData(data.ANIMATION); + _defaultSymbolName = defaultSymbolData.SYMBOL_name; + _symbolData.set(_defaultSymbolName, defaultSymbolData); + + // a purely internal symbol for bitmaps - simplifies their handling + _symbolData.set(BITMAP_SYMBOL_NAME, { + SYMBOL_name: BITMAP_SYMBOL_NAME, + TIMELINE: { + LAYERS: [] + } + }); + } + + private function preprocessSymbolData(symbolData:SymbolData):SymbolData { + var timeLineData:SymbolTimelineData = symbolData.TIMELINE; + var layerDates:Array = timeLineData.LAYERS; + + // In Animate CC, layers are sorted front to back. + // In Starling, it's the other way round - so we simply reverse the layer data. + + if (!timeLineData.sortedForRender) { + timeLineData.sortedForRender = true; + layerDates.reverse(); + } + + // We replace all "ATLAS_SPRITE_instance" elements with symbols of the same contents. + // That way, we are always only dealing with symbols. + + for (layerData in layerDates) { + var frames:Array = layerData.Frames; + + for (frame in frames) { + var elements:Array = frame.elements; + for (e in 0...elements.length) { + var element:ElementData = elements[e]; + if (element.ATLAS_SPRITE_instance != null) { + element = elements[e] = { + SYMBOL_Instance: { + SYMBOL_name: BITMAP_SYMBOL_NAME, + Instance_Name: "InstName", + bitmap: element.ATLAS_SPRITE_instance, + symbolType: SymbolType.GRAPHIC, + firstFrame: 0, + loop: LoopMode.LOOP, + transformationPoint: { + x: 0, + y: 0 + }, + Matrix3D: STD_MATRIX3D_DATA + } + }; + } + } + } + } + + return symbolData; + } + + private function parseAtlasData(atlas:AtlasData):Void { + _atlas = new Map(); + if (atlas.ATLAS != null && atlas.ATLAS.SPRITES != null) { + for (s in atlas.ATLAS.SPRITES) { + _atlas.set(s.SPRITE.name, s.SPRITE); + } + } + } + + private function getSymbolData(name:String):SymbolData { + return _symbolData.get(name); + } + + // # end region +} diff --git a/source/animateatlas/displayobject/SpriteMovieClip.hx b/source/animateatlas/displayobject/SpriteMovieClip.hx new file mode 100644 index 00000000..2b2b746d --- /dev/null +++ b/source/animateatlas/displayobject/SpriteMovieClip.hx @@ -0,0 +1,117 @@ +package animateatlas.displayobject; + +import openfl.display.Sprite; +import animateatlas.HelperEnums.LoopMode; +import animateatlas.HelperEnums.SymbolType; + +@:access(animateatlas.displayobject.SpriteSymbol) +class SpriteMovieClip extends Sprite { + public var framerate(get, set):Float; + public var currentLabel(get, set):String; + public var currentFrame(get, set):Int; + public var type(get, set):String; + public var loopMode(get, set):String; + public var symbolName(get, never):String; + public var numLayers(get, never):Int; + public var numFrames(get, never):Int; + public var layers(get, never):Array; // ! Dangerous AF. + + private var symbol:SpriteSymbol; + private var _framerate:Null = null; + + private var frameElapsed:Float = 0; + + public function new(symbol:SpriteSymbol) { + super(); + this.symbol = symbol; + addChild(this.symbol); + } + + public function update(dt:Int) { + // + var frameDuration:Float = 1000 / framerate; + frameElapsed += dt; + + while (frameElapsed > frameDuration) { + frameElapsed -= frameDuration; + symbol.nextFrame(); + } + while (frameElapsed < -frameDuration) { + frameElapsed += frameDuration; + symbol.prevFrame(); + } + } + + public function getFrameLabels():Array { + return symbol.getFrameLabels(); + } + + public function getFrame(label:String):Int { + return symbol.getFrame(label); + } + + // # region Property setter and getter + + private function set_currentLabel(value:String):String { + symbol.currentFrame = symbol.getFrame(value); + return value; + } + + private function get_currentLabel():String { + return symbol.currentLabel; + } + + + private function set_currentFrame(value:Int):Int { + symbol.currentFrame = value; + return value; + } + + private function get_currentFrame():Int { + return symbol.currentFrame; + } + + private function set_type(value:SymbolType):SymbolType { + symbol.type = value; + return value; + } + + private function get_type():SymbolType { + return symbol.type; + } + + private function set_loopMode(value:LoopMode):LoopMode { + symbol.loopMode = value; + return value; + } + + private function get_loopMode():LoopMode { + return symbol.loopMode; + } + + private function get_symbolName():String { + return symbol.symbolName; + } + + private function get_numLayers():Int { + return symbol.numLayers; + } + + private function get_numFrames():Int { + return symbol.numFrames; + } + + private function get_layers():Array { + return symbol._layers; + } + + private function set_framerate(value:Float):Float { + return _framerate = value; + } + + private function get_framerate():Float { + return _framerate == null ? symbol._library.frameRate : _framerate; + } + + // # end region +} diff --git a/source/animateatlas/displayobject/SpriteSymbol.hx b/source/animateatlas/displayobject/SpriteSymbol.hx new file mode 100644 index 00000000..4f813c42 --- /dev/null +++ b/source/animateatlas/displayobject/SpriteSymbol.hx @@ -0,0 +1,540 @@ +package animateatlas.displayobject; + +import flixel.util.FlxColor; +import openfl.filters.GlowFilter; +import openfl.filters.BlurFilter; +import openfl.display.PixelSnapping; +import openfl.geom.Point; +import openfl.display.BitmapData; +import openfl.display.Bitmap; +import openfl.display.Sprite; +import openfl.errors.ArgumentError; +import openfl.geom.Rectangle; +import openfl.errors.Error; +import animateatlas.JSONData.ElementData; +import animateatlas.HelperEnums.LoopMode; +import animateatlas.HelperEnums.SymbolType; +import openfl.display.FrameLabel; +import animateatlas.JSONData.SymbolData; +import animateatlas.JSONData.SymbolInstanceData; +import animateatlas.JSONData.LayerData; +import animateatlas.JSONData.BitmapPosData; +import animateatlas.JSONData.Matrix3DData; +import animateatlas.JSONData.LayerFrameData; +import animateatlas.JSONData.ColorData; +import openfl.geom.Matrix; +import openfl.geom.ColorTransform; +import animateatlas.JSONData.FilterData; + +class SpriteSymbol extends Sprite { + public var currentLabel(get, never):String; + public var currentFrame(get, set):Int; + public var type(get, set):String; + public var loopMode(get, set):String; + public var symbolName(get, never):String; + public var numLayers(get, never):Int; + public var numFrames(get, never):Int; + + private var _data:SymbolData; + private var _library:SpriteAnimationLibrary; + private var _symbolName:String; + private var _type:String; + private var _loopMode:String; + + private var _currentFrame:Int; + private var _composedFrame:Int; + private var _bitmap:Bitmap; + private var _numFrames:Int; + private var _numLayers:Int; + private var _frameLabels:Array; + private var _colorTransform:ColorTransform; + private var _layers:Array; + private var _texture:BitmapData; + private var _tempRect = new Rectangle(); + private var _zeroPoint = new Point(0, 0); + private var filterHelper:BitmapData; + public var smoothing:Bool = true; + + private static var sMatrix:Matrix = new Matrix(); + + private function new(data:SymbolData, library:SpriteAnimationLibrary, texture:BitmapData) { + super(); + _data = data; + _library = library; + _composedFrame = -1; + _numLayers = data.TIMELINE.LAYERS.length; + _numFrames = getNumFrames(); + _frameLabels = _getFrameLabels(); + _symbolName = data.SYMBOL_name; + _type = SymbolType.GRAPHIC; + _loopMode = LoopMode.LOOP; + _texture = texture; + + createLayers(); + + // Create FrameMap caches if don't exist + for (layer in data.TIMELINE.LAYERS) { + if (layer.FrameMap != null) + return; + + var map = new Map(); + + for (i in 0...layer.Frames.length) { + var frame = layer.Frames[i]; + for (j in 0...frame.duration) { + map.set(i + j, frame); + } + } + + layer.FrameMap = map; + } + } + + public function reset():Void { + sMatrix.identity(); + transform.matrix = sMatrix.clone(); + alpha = 1.0; + _currentFrame = 0; + _composedFrame = -1; + } + + public function nextFrame():Void { + if (_loopMode != LoopMode.SINGLE_FRAME) { + currentFrame += 1; + } + + moveMovieclip_MovieClips(1); + } + + public function prevFrame():Void { + if (_loopMode != LoopMode.SINGLE_FRAME) { + currentFrame -= 1; + } + + moveMovieclip_MovieClips(-1); + } + + /** Moves all movie clips n frames, recursively. */ + private function moveMovieclip_MovieClips(direction:Int = 1):Void { + if (_type == SymbolType.MOVIE_CLIP) { + currentFrame += direction; + } + + for (l in 0..._numLayers) { + var layer:Sprite = getLayer(l); + var numElements:Int = layer.numChildren; + + for (e in 0...numElements) { + (try cast(layer.getChildAt(e), SpriteSymbol) catch (e:Dynamic) null).moveMovieclip_MovieClips(direction); + } + } + } + + public function update():Void { + for (i in 0..._numLayers) { + updateLayer(i); + } + + _composedFrame = _currentFrame; + } + + @:access(animateatlas) + private function updateLayer(layerIndex:Int):Void { + var layer:Sprite = getLayer(layerIndex); + var frameData:LayerFrameData = getFrameData(layerIndex, _currentFrame); + var elements:Array = (frameData != null) ? frameData.elements : null; + var numElements:Int = (elements != null) ? elements.length : 0; + for (i in 0...numElements) { + var elementData:SymbolInstanceData = elements[i].SYMBOL_Instance; + + if (elementData == null) { + continue; + } + + // this is confusing but needed :( + var oldSymbol:SpriteSymbol = (layer.numChildren > i) ? try + cast(layer.getChildAt(i), SpriteSymbol) + catch (e:Dynamic) + null : null; + + var newSymbol:SpriteSymbol = null; + + var symbolName:String = elementData.SYMBOL_name; + + if (!_library.hasSymbol(symbolName)) { + symbolName = SpriteAnimationLibrary.BITMAP_SYMBOL_NAME; + } + + if (oldSymbol != null && oldSymbol._symbolName == symbolName) { + newSymbol = oldSymbol; + } else { + if (oldSymbol != null) { + if (oldSymbol.parent != null) + oldSymbol.removeChild(oldSymbol); + _library.putSymbol(oldSymbol); + } + + newSymbol = cast(_library.getSymbol(symbolName)); + layer.addChildAt(newSymbol, i); + } + + newSymbol.setTransformationMatrix(elementData.Matrix3D); + newSymbol.setBitmap(elementData.bitmap); + newSymbol.setFilterData(elementData.filters); + newSymbol.setColor(elementData.color); + newSymbol.setLoop(elementData.loop); + newSymbol.setType(elementData.symbolType); + + + + if (newSymbol.type == SymbolType.GRAPHIC) { + var firstFrame:Int = elementData.firstFrame; + var frameAge:Int = Std.int(_currentFrame - frameData.index); + + if (newSymbol.loopMode == LoopMode.SINGLE_FRAME) { + newSymbol.currentFrame = firstFrame; + } else if (newSymbol.loopMode == LoopMode.LOOP) { + newSymbol.currentFrame = (firstFrame + frameAge) % newSymbol._numFrames; + } else { + newSymbol.currentFrame = firstFrame + frameAge; + } + } + + } + + var numObsoleteSymbols:Int = (layer.numChildren - numElements); + + for (i in 0...numObsoleteSymbols) { + try { + var oldSymbol = cast(layer.removeChildAt(numElements), SpriteSymbol); + if (oldSymbol != null) + _library.putSymbol(oldSymbol); + } catch (e:Dynamic) {}; + } + } + + private function createLayers():Void { + // todo safety check for not initialiing twice + if (_layers != null) { + throw new Error("You must not call this twice"); + } + _layers = new Array(); + + if (_numLayers <= 1) { + _layers.push(this); + } else { + for (i in 0..._numLayers) { + var layer:Sprite = new Sprite(); + layer.name = getLayerData(i).Layer_name; + addChild(layer); + _layers.push(layer); + } + } + } + + @:access(animateatlas) + public function setBitmap(data:BitmapPosData):Void { + if (data != null) { + var spriteData = _library.getSpriteData(data.name + ""); + + if (_bitmap == null) { + _bitmap = new Bitmap(new BitmapData(1, 1), PixelSnapping.AUTO, smoothing); + addChild(_bitmap); + } + + if (_tempRect.x != spriteData.x || _tempRect.y != spriteData.y || _tempRect.width != spriteData.w || _tempRect.height != spriteData.h) { + var clippedTexture = new BitmapData(spriteData.w, spriteData.h); + _tempRect.setTo(spriteData.x, spriteData.y, spriteData.w, spriteData.h); + clippedTexture.copyPixels(_texture, _tempRect, _zeroPoint); + _bitmap.bitmapData = clippedTexture; + _bitmap.smoothing = smoothing; + + + } + // aditional checks for rotation + if (spriteData.rotated) { + _bitmap.rotation = -90; + _bitmap.x = data.Position.x; + _bitmap.y = data.Position.y + spriteData.w; + } else { + _bitmap.rotation = 0; + _bitmap.x = data.Position.x; + _bitmap.y = data.Position.y; + } + + addChildAt(_bitmap, 0); + } else if (_bitmap != null) { + if (_bitmap.parent != null) + _bitmap.parent.removeChild(_bitmap); + } + } + @:access(animateatlas) + private function setFilterData(data:FilterData):Void{ + var blur:BlurFilter; + var glow:GlowFilter; + if (data != null){ + if (data.BlurFilter != null){ + blur = new BlurFilter(); + blur.blurX = data.BlurFilter.blurX; + blur.blurY = data.BlurFilter.blurY; + blur.quality = data.BlurFilter.quality; + //_bitmap.bitmapData.applyFilter(_bitmap.bitmapData,new Rectangle(0,0,_bitmap.bitmapData.width,_bitmap.bitmapData.height),new Point(0,0),blur); + //filters.push(blur); + } + if (data.GlowFilter != null){ + //trace('GLOW' + data.GlowFilter); + //glow = new GlowFilter(); + //glow.blurX = data.GlowFilter.blurX; + //glow.blurY = data.GlowFilter.blurY; + //glow.color = data.GlowFilter.color; + //glow.alpha = data.GlowFilter.alpha; + //glow.quality = data.GlowFilter.quality; + //glow.strength = data.GlowFilter.strength; + //glow.knockout = data.GlowFilter.knockout; + //glow.inner = data.GlowFilter.inner; + //filters.push(glow); + + + + + } + + } + + } + + private function setTransformationMatrix(data:Matrix3DData):Void { + sMatrix.setTo(data.m00, data.m01, data.m10, data.m11, data.m30, data.m31); + if (sMatrix.a != transform.matrix.a || sMatrix.b != transform.matrix.b || sMatrix.c != transform.matrix.c || sMatrix.d != transform.matrix.d + || sMatrix.tx != transform.matrix.tx || sMatrix.ty != transform.matrix.ty) + transform.matrix = sMatrix.clone(); // todo stop the cloning :( + } + + private function setColor(data:ColorData):Void { + var newTransform = new ColorTransform(); + if (data != null) { + newTransform.redOffset = (data.redOffset == null ? 0 : data.redOffset); + newTransform.greenOffset = (data.greenOffset == null ? 0 : data.greenOffset); + newTransform.blueOffset = (data.blueOffset == null ? 0 : data.blueOffset); + newTransform.alphaOffset = (data.AlphaOffset == null ? 0 : data.AlphaOffset); + + newTransform.redMultiplier = (data.RedMultiplier == null ? 1 : data.RedMultiplier); + newTransform.greenMultiplier = (data.greenMultiplier == null ? 1 : data.greenMultiplier); + newTransform.blueMultiplier = (data.blueMultiplier == null ? 1 : data.blueMultiplier); + newTransform.alphaMultiplier = (data.alphaMultiplier == null ? 1 : data.alphaMultiplier); + + + if (data.mode == "Tint"){//Tint color effect + var color:FlxColor = Std.parseInt(StringTools.replace(data.tintColor, "#", "0xFF")); + + + newTransform.redMultiplier = 0; + newTransform.greenMultiplier = 0;//(data.greenMultiplier == null ? 1 : data.greenMultiplier); + newTransform.blueMultiplier = 0;//(data.blueMultiplier == null ? 1 : data.blueMultiplier); + newTransform.redOffset = color.red*data.tintMultiplier; + newTransform.greenOffset = color.green * data.tintMultiplier;//(data.greenMultiplier == null ? 1 : data.greenMultiplier); + newTransform.blueOffset = color.blue * data.tintMultiplier;//(data.blueMultiplier == null ? 1 : data.blueMultiplier); + } + + } + transform.colorTransform = newTransform; + + } + + private function setLoop(data:String):Void { + if (data != null) { + _loopMode = data; + } else { + _loopMode = LoopMode.LOOP; + } + } + + private function setType(data:String):Void { + if (data != null) { + _type = data; + } + } + + private function getNumFrames():Int { + var numFrames:Int = 0; + + for (i in 0..._numLayers) { + var layer = getLayerData(i); + var frameDates:Array = (layer == null ? [] : layer.Frames); + var numFrameDates:Int = (frameDates != null) ? frameDates.length : 0; + var layerNumFrames:Int = (numFrameDates != 0) ? frameDates[0].index : 0; + + for (j in 0...numFrameDates) { + layerNumFrames += frameDates[j].duration; + } + + if (layerNumFrames > numFrames) { + numFrames = layerNumFrames; + } + } + + return numFrames == 0 ? 1 : numFrames; + } + + private function _getFrameLabels():Array { + var labels:Array = []; + + for (i in 0..._numLayers) { + var layer = getLayerData(i); + var frameDates:Array = (layer == null ? [] : layer.Frames); + var numFrameDates:Int = (frameDates != null) ? frameDates.length : 0; + + for (j in 0...numFrameDates) { + var frameData:LayerFrameData = frameDates[j]; + if (frameData.name != null) { + labels.push(new FrameLabel(frameData.name, frameData.index)); + } + } + } + labels.sort(sortLabels); + return labels; + } + + public function getFrameLabels():Array { + return _frameLabels.map(f -> f.name); // Inlining. I feel a js + } + + + + function sortLabels(i1:FrameLabel, i2:FrameLabel):Int { + var f1 = i1.frame; + var f2 = i2.frame; + if (f1 < f2) { + return -1; + } else if (f1 > f2) { + return 1; + } else { + return 0; + } + } + + private function getLayer(layerIndex:Int):Sprite { + return _layers[layerIndex]; + } + + public function getTexture():BitmapData{ + //THIS GETS THE ENTIRE THING I'M RETARDED LOL + return _texture; + } + + public function getNextLabel(afterLabel:String = null):String { + var numLabels:Int = _frameLabels.length; + var startFrame:Int = getFrame(afterLabel == null ? currentLabel : afterLabel); + + for (i in 0...numLabels) { + var label:FrameLabel = _frameLabels[i]; + if (label.frame > startFrame) { + return label.name; + } + } + + return (_frameLabels != null) ? _frameLabels[0].name : null; + } + + private function get_currentLabel():String { + var numLabels:Int = _frameLabels.length; + var highestLabel:FrameLabel = (numLabels != 0) ? _frameLabels[0] : null; + + for (i in 1...numLabels) { + var label:FrameLabel = _frameLabels[i]; + + if (label.frame <= _currentFrame) { + highestLabel = label; + } else { + break; + } + } + + return (highestLabel != null) ? highestLabel.name : null; + } + + public function getFrame(label:String):Int { + var numLabels:Int = _frameLabels.length; + for (i in 0...numLabels) { + var frameLabel:FrameLabel = _frameLabels[i]; + if (frameLabel.name == label) { + return frameLabel.frame; + } + } + return -1; + } + + private function get_currentFrame():Int { + return _currentFrame; + } + + private function set_currentFrame(value:Int):Int { + while (value < 0) { + value += _numFrames; + } + + if (_loopMode == LoopMode.PLAY_ONCE) { + _currentFrame = Std.int(Math.min(Math.max(value, 0), _numFrames - 1)); + } else { + _currentFrame = Std.int(Math.abs(value % _numFrames)); + } + + if (_composedFrame != _currentFrame) { + update(); + } + return value; + } + + private function get_type():String { + return _type; + } + + private function set_type(value:String):String { + if (SymbolType.isValid(value)) { + _type = value; + } else { + throw new ArgumentError("Invalid symbol type: " + value); + } + return value; + } + + private function get_loopMode():String { + return _loopMode; + } + + private function set_loopMode(value:String):String { + if (LoopMode.isValid(value)) { + _loopMode = value; + } else { + throw new ArgumentError("Invalid loop mode: " + value); + } + return value; + } + + private function get_symbolName():String { + return _symbolName; + } + + private function get_numLayers():Int { + return _numLayers; + } + + private function get_numFrames():Int { + return _numFrames; + } + + // data access + + private function getLayerData(layerIndex:Int):LayerData { + return _data.TIMELINE.LAYERS[layerIndex]; + } + + private function getFrameData(layerIndex:Int, frameIndex:Int):LayerFrameData { + var layer = getLayerData(layerIndex); + if (layer == null) + return null; + + return layer.FrameMap.get(frameIndex); + } + +} diff --git a/source/animateatlas/tilecontainer/TileAnimationLibrary.hx b/source/animateatlas/tilecontainer/TileAnimationLibrary.hx new file mode 100644 index 00000000..008f0e36 --- /dev/null +++ b/source/animateatlas/tilecontainer/TileAnimationLibrary.hx @@ -0,0 +1,226 @@ +package animateatlas.tilecontainer; + +import openfl.display.Tileset; +import haxe.Constraints.Constructible; +import openfl.display.BitmapData; +import animateatlas.JSONData.AnimationData; +import animateatlas.JSONData.ElementData; +import animateatlas.JSONData.LayerFrameData; +import animateatlas.JSONData.LayerData; +import animateatlas.JSONData.SymbolTimelineData; +import animateatlas.JSONData.Matrix3DData; +import animateatlas.JSONData.AtlasData; +import animateatlas.JSONData.SymbolData; +import animateatlas.JSONData.SpriteData; +import animateatlas.HelperEnums.LoopMode; +import animateatlas.HelperEnums.SymbolType; +import openfl.errors.ArgumentError; + +/** + * Since we can extract symbols from the exported timeline and instance them separatedly, this keeps track of all symbols. + * Also, this is a "more readable" way of understanding the AnimationData + */ +class TileAnimationLibrary { + public var frameRate:Float; + + private var _atlas:Map; + private var _symbolData:Map; + private var _symbolPool:Map>; + private var _defaultSymbolName:String; + private var _texture:Tileset; + + public static inline var BITMAP_SYMBOL_NAME:String = "___atlas_sprite___"; + + private static var STD_MATRIX3D_DATA:Matrix3DData = { + m00: 1, + m01: 0, + m02: 0, + m03: 0, + m10: 0, + m11: 1, + m12: 0, + m13: 0, + m20: 0, + m21: 0, + m22: 1, + m23: 0, + m30: 0, + m31: 0, + m32: 0, + m33: 1 + }; + + public function new(data:AnimationData, atlas:AtlasData, texture:BitmapData) { + parseAnimationData(data); + parseAtlasData(atlas); + _texture = new Tileset(texture); + _symbolPool = new Map(); + } + + public function hasAnimation(name:String):Bool { + return hasSymbol(name); + } + + public function createAnimation(symbol:String = null):TileContainerMovieClip { + symbol = (symbol != null) ? symbol : _defaultSymbolName; + if (!hasSymbol(symbol)) { + throw new ArgumentError("Symbol not found: " + symbol); + } + return new TileContainerMovieClip(getSymbol(symbol)); + } + + public function getAnimationNames(prefix:String = ""):Array { + var out = new Array(); + + for (name in _symbolData.keys()) { + if (name != BITMAP_SYMBOL_NAME && name.indexOf(prefix) == 0) { + out.push(name); + } + } + + // but... why? + out.sort(function(a1, a2):Int { + a1 = a1.toLowerCase(); + a2 = a2.toLowerCase(); + if (a1 < a2) { + return -1; + } else if (a1 > a2) { + return 1; + } else { + return 0; + } + }); + return out; + } + + private function getSpriteData(name:String):SpriteData { + return _atlas.get(name); + } + + private function hasSymbol(name:String):Bool { + return _symbolData.exists(name); + } + + // # region Pooling + // todo migrate this to lime pool + + @:access(animateatlas) + private function getSymbol(name:String):TileContainerSymbol { + var pool:Array = getSymbolPool(name); + if (pool.length == 0) { + return new TileContainerSymbol(getSymbolData(name), this, _texture); + } else { + return pool.pop(); + } + } + + private function putSymbol(symbol:TileContainerSymbol):Void { + symbol.reset(); + var pool:Array = getSymbolPool(symbol.symbolName); + pool.push(symbol); + symbol.currentFrame = 0; + } + + private function getSymbolPool(name:String):Array { + var pool:Array = _symbolPool.get(name); + if (pool == null) { + pool = []; + _symbolPool.set(name, pool); + } + return pool; + } + + // # end region + // # region helpers + private function parseAnimationData(data:AnimationData):Void { + var metaData = data.metadata; + + if (metaData != null && metaData.framerate != null && metaData.framerate > 0) { + frameRate = (metaData.framerate); + } else { + frameRate = 24; + } + + _symbolData = new Map(); + + // the actual symbol dictionary + var symbols = data.SYMBOL_DICTIONARY.Symbols; + for (symbolData in symbols) { + _symbolData[symbolData.SYMBOL_name] = preprocessSymbolData(symbolData); + } + + // the main animation + var defaultSymbolData:SymbolData = preprocessSymbolData(data.ANIMATION); + _defaultSymbolName = defaultSymbolData.SYMBOL_name; + _symbolData.set(_defaultSymbolName, defaultSymbolData); + + // a purely internal symbol for bitmaps - simplifies their handling + _symbolData.set(BITMAP_SYMBOL_NAME, { + SYMBOL_name: BITMAP_SYMBOL_NAME, + TIMELINE: { + LAYERS: [] + } + }); + } + + private function preprocessSymbolData(symbolData:SymbolData):SymbolData { + var timeLineData:SymbolTimelineData = symbolData.TIMELINE; + var layerDates:Array = timeLineData.LAYERS; + + // In Animate CC, layers are sorted front to back. + // In Starling, it's the other way round - so we simply reverse the layer data. + + if (!timeLineData.sortedForRender) { + timeLineData.sortedForRender = true; + layerDates.reverse(); + } + + // We replace all "ATLAS_SPRITE_instance" elements with symbols of the same contents. + // That way, we are always only dealing with symbols. + + for (layerData in layerDates) { + var frames:Array = layerData.Frames; + + for (frame in frames) { + var elements:Array = frame.elements; + for (e in 0...elements.length) { + var element:ElementData = elements[e]; + if (element.ATLAS_SPRITE_instance != null) { + element = elements[e] = { + SYMBOL_Instance: { + SYMBOL_name: BITMAP_SYMBOL_NAME, + Instance_Name: "InstName", + bitmap: element.ATLAS_SPRITE_instance, + symbolType: SymbolType.GRAPHIC, + firstFrame: 0, + loop: LoopMode.LOOP, + transformationPoint: { + x: 0, + y: 0 + }, + Matrix3D: STD_MATRIX3D_DATA + } + }; + } + } + } + } + + return symbolData; + } + + private function parseAtlasData(atlas:AtlasData):Void { + _atlas = new Map(); + if (atlas.ATLAS != null && atlas.ATLAS.SPRITES != null) { + for (s in atlas.ATLAS.SPRITES) { + _atlas.set(s.SPRITE.name, s.SPRITE); + } + } + } + + private function getSymbolData(name:String):SymbolData { + return _symbolData.get(name); + } + + // # end region +} diff --git a/source/animateatlas/tilecontainer/TileContainerMovieClip.hx b/source/animateatlas/tilecontainer/TileContainerMovieClip.hx new file mode 100644 index 00000000..66de4ded --- /dev/null +++ b/source/animateatlas/tilecontainer/TileContainerMovieClip.hx @@ -0,0 +1,135 @@ +package animateatlas.tilecontainer; + +import animateatlas.HelperEnums.LoopMode; +import animateatlas.HelperEnums.SymbolType; +import openfl.display.TileContainer; + +@:access(animateatlas.tilecontainer.TileContainerSymbol) +class TileContainerMovieClip extends TileContainer { + public var framerate(get, set):Float; + public var currentLabel(get, set):String; + public var currentFrame(get, set):Int; + public var type(get, set):String; + public var loopMode(get, set):String; + public var symbolName(get, never):String; + public var numLayers(get, never):Int; + public var numFrames(get, never):Int; + public var layers(get, never):Array; // ! Dangerous AF. + + private var symbol:TileContainerSymbol; + private var _framerate:Null = null; + + private var frameElapsed:Float = 0; + + public function new(symbol:TileContainerSymbol) { + super(); + this.symbol = symbol; + addTile(this.symbol); + } + + public function update(dt:Int) { + var frameDuration:Float = 1000 / framerate; + frameElapsed += dt; + + while (frameElapsed > frameDuration) { + frameElapsed -= frameDuration; + symbol.nextFrame(); + } + while (frameElapsed < -frameDuration) { + frameElapsed += frameDuration; + symbol.prevFrame(); + } + } + + public function getFrameLabels():Array { + return symbol.getFrameLabels(); + } + + public function getFrame(label:String):Int { + return symbol.getFrame(label); + } + public function getFramesofAnim(label:String):Int { + var framesArray:Array; + var uncalculatedArray:Array = []; + var uncalculatedFrames:Int = 0; + + for (i in 0...getFrameLabels().length){ + uncalculatedArray.push(getFrame(getFrameLabels()[i])); + } + + + uncalculatedFrames = uncalculatedArray[0]+uncalculatedArray.length; + + return uncalculatedFrames; + } + + // # region Property setter and getter + + private function set_currentLabel(value:String):String { + symbol.currentFrame = symbol.getFrame(value); + return value; + } + + private function get_currentLabel():String { + return symbol.currentLabel; + } + + private function set_currentFrame(value:Int):Int { + symbol.currentFrame = value; + return value; + } + + public function get_animFrames():Int{ + return symbol.get_numFrames(); + } + + + private function get_currentFrame():Int { + return symbol.currentFrame; + } + + private function set_type(value:SymbolType):SymbolType { + symbol.type = value; + return value; + } + + private function get_type():SymbolType { + return symbol.type; + } + + private function set_loopMode(value:LoopMode):LoopMode { + symbol.loopMode = value; + return value; + } + + private function get_loopMode():LoopMode { + return symbol.loopMode; + } + + private function get_symbolName():String { + return symbol.symbolName; + } + + public function get_numLayers():Int { + return symbol.numLayers; + } + + private function get_numFrames():Int { + return symbol.numFrames; + } + + + public function get_layers():Array { + return symbol._layers; + } + + private function set_framerate(value:Float):Float { + return _framerate = value; + } + + private function get_framerate():Float { + return _framerate == null ? symbol._library.frameRate : _framerate; + } + + // # end region +} diff --git a/source/animateatlas/tilecontainer/TileContainerSymbol.hx b/source/animateatlas/tilecontainer/TileContainerSymbol.hx new file mode 100644 index 00000000..316dddfa --- /dev/null +++ b/source/animateatlas/tilecontainer/TileContainerSymbol.hx @@ -0,0 +1,475 @@ +package animateatlas.tilecontainer; + +import openfl.display.Tileset; +import animateatlas.JSONData.PointData; +import openfl.errors.ArgumentError; +import openfl.geom.Rectangle; +import openfl.errors.Error; +import animateatlas.JSONData.ElementData; +import animateatlas.HelperEnums.LoopMode; +import animateatlas.HelperEnums.SymbolType; +import openfl.display.FrameLabel; +import animateatlas.JSONData.SymbolData; +import animateatlas.JSONData.SymbolInstanceData; +import animateatlas.JSONData.LayerData; +import animateatlas.JSONData.BitmapPosData; +import animateatlas.JSONData.Matrix3DData; +import animateatlas.JSONData.LayerFrameData; +import animateatlas.JSONData.ColorData; +import openfl.geom.Matrix; +import openfl.geom.ColorTransform; +import openfl.display.DisplayObjectContainer; +import openfl.display.TileContainer; +import openfl.display.Tile; + +class TileContainerSymbol extends TileContainer { + public var currentLabel(get, never):String; + public var currentFrame(get, set):Int; + public var type(get, set):String; + public var loopMode(get, set):String; + public var symbolName(get, never):String; + public var numLayers(get, never):Int; + public var numFrames(get, never):Int; + + private var _data:SymbolData; + private var _library:TileAnimationLibrary; + private var _symbolName:String; + private var _type:String; + private var _loopMode:String; + private var _currentFrame:Int; + private var _composedFrame:Int; + private var _bitmap:Tile; + private var _numFrames:Int; + private var _numLayers:Int; + private var _frameLabels:Array; + private var _colorTransform:ColorTransform; + private var _layers:Array; + + private function new(data:SymbolData, library:TileAnimationLibrary, tileset:Tileset) { + super(); + this.tileset = tileset; + _data = data; + _library = library; + _composedFrame = -1; + _numLayers = data.TIMELINE.LAYERS.length; + _numFrames = getNumFrames(); + _frameLabels = _getFrameLabels(); + _symbolName = data.SYMBOL_name; + _type = SymbolType.GRAPHIC; + _loopMode = LoopMode.LOOP; + + createLayers(); + + // Create FrameMap caches if don't exist + for (layer in data.TIMELINE.LAYERS) { + if (layer.FrameMap != null) + return; + + var map = new Map(); + + for (i in 0...layer.Frames.length) { + var frame = layer.Frames[i]; + for (j in 0...frame.duration) { + map.set(i + j, frame); + } + } + + layer.FrameMap = map; + } + } + + public function reset():Void { + matrix.identity(); + + // copied from the setter for tile so we don't create a new matrix. + __rotation = null; + __scaleX = null; + __scaleY = null; + __setRenderDirty(); + + alpha = 1.0; + _currentFrame = 0; + _composedFrame = -1; + } + + public function nextFrame():Void { + if (_loopMode != LoopMode.SINGLE_FRAME) { + currentFrame += 1; + } + + moveMovieclip_MovieClips(1); + } + + public function prevFrame():Void { + if (_loopMode != LoopMode.SINGLE_FRAME) { + currentFrame -= 1; + } + + moveMovieclip_MovieClips(-1); + } + + /** Moves all movie clips n frames, recursively. */ + private function moveMovieclip_MovieClips(direction:Int = 1):Void { + if (_type == SymbolType.MOVIE_CLIP) { + currentFrame += direction; + } + + for (l in 0..._numLayers) { + var layer:TileContainer = getLayer(l); + var numElements:Int = layer.numTiles; + + for (e in 0...numElements) { + (try cast(layer.getTileAt(e), TileContainerSymbol) catch (e:Dynamic) null).moveMovieclip_MovieClips(direction); + } + } + } + + public function update():Void { + for (i in 0..._numLayers) { + updateLayer(i); + } + + _composedFrame = _currentFrame; + } + + @:access(animateatlas) + private function updateLayer(layerIndex:Int):Void { + var layer:TileContainer = getLayer(layerIndex); + var frameData:LayerFrameData = getFrameData(layerIndex, _currentFrame); + var elements:Array = (frameData != null) ? frameData.elements : null; + var numElements:Int = (elements != null) ? elements.length : 0; + for (i in 0...numElements) { + var elementData:SymbolInstanceData = elements[i].SYMBOL_Instance; + if (elementData == null) { + continue; + } + // this is confusing but needed :( + var oldSymbol:TileContainerSymbol = (layer.numTiles > i) ? try + cast(layer.getTileAt(i), TileContainerSymbol) + catch (e:Dynamic) + null : null; + + var newSymbol:TileContainerSymbol = null; + var symbolName:String = elementData.SYMBOL_name; + + if (!_library.hasSymbol(symbolName)) { + symbolName = TileAnimationLibrary.BITMAP_SYMBOL_NAME; + } + + if (oldSymbol != null && oldSymbol._symbolName == symbolName) { + newSymbol = oldSymbol; + } else { + if (oldSymbol != null) { + if (oldSymbol.parent != null) + oldSymbol.removeTile(oldSymbol); + _library.putSymbol(oldSymbol); + } + + newSymbol = cast(_library.getSymbol(symbolName)); + layer.addTileAt(newSymbol, i); + } + + newSymbol.setTransformationMatrix(elementData.Matrix3D); + newSymbol.setBitmap(elementData.bitmap); + newSymbol.setColor(elementData.color); + newSymbol.setLoop(elementData.loop); + newSymbol.setType(elementData.symbolType); + + if (newSymbol.type == SymbolType.GRAPHIC) { + var firstFrame:Int = elementData.firstFrame; + var frameAge:Int = Std.int(_currentFrame - frameData.index); + + if (newSymbol.loopMode == LoopMode.SINGLE_FRAME) { + newSymbol.currentFrame = firstFrame; + } else if (newSymbol.loopMode == LoopMode.LOOP) { + newSymbol.currentFrame = (firstFrame + frameAge) % newSymbol._numFrames; + } else { + newSymbol.currentFrame = firstFrame + frameAge; + } + } + } + + var numObsoleteSymbols:Int = (layer.numTiles - numElements); + + for (i in 0...numObsoleteSymbols) { + try { + var oldSymbol = cast(layer.removeTileAt(numElements), TileContainerSymbol); + if (oldSymbol != null) + _library.putSymbol(oldSymbol); + } catch (e:Dynamic) {}; + } + } + + private function createLayers():Void { + // todo safety check for not initialiing twice + if (_layers != null) { + throw new Error("You must not call this twice"); + } + _layers = new Array(); + + if (_numLayers <= 1) { + _layers.push(this); + } else { + for (i in 0..._numLayers) { + var layer:TileContainer = new TileContainer(); + if (layer.data == null) { + layer.data = {layerName: getLayerData(i).Layer_name}; + } else { + layer.data.layerName = getLayerData(i).Layer_name; + } + addTile(layer); + _layers.push(layer); + } + } + } + + @:access(animateatlas) + public function setBitmap(data:BitmapPosData):Void { + if (data != null) { + var spriteData = _library.getSpriteData(data.name + ""); + + if (_bitmap == null) { + _bitmap = new Tile(-1); + _bitmap.rect = new Rectangle(); + addTile(_bitmap); + } + + _bitmap.rect.setTo(spriteData.x, spriteData.y, spriteData.w, spriteData.h); + _bitmap.__setRenderDirty(); // setTo() doesn't trigger the renderdirty + + // aditional checks for rotation + if (spriteData.rotated) { + _bitmap.rotation = -90; + _bitmap.x = data.Position.x; + _bitmap.y = data.Position.y + spriteData.w; + } else { + _bitmap.rotation = 0; + _bitmap.x = data.Position.x; + _bitmap.y = data.Position.y; + } + + addTileAt(_bitmap, 0); + } else if (_bitmap != null) { + if (_bitmap.parent != null) + _bitmap.parent.removeTile(_bitmap); + } + } + + private function setTransformationMatrix(data:Matrix3DData):Void { + if (data.m00 != matrix.a || data.m01 != matrix.b || data.m10 != matrix.c || data.m11 != matrix.d || data.m30 != matrix.tx || data.m31 != matrix.ty) { + matrix.setTo(data.m00, data.m01, data.m10, data.m11, data.m30, data.m31); + + // copied from the setter for tile so we don't create a new matrix. + __rotation = null; + __scaleX = null; + __scaleY = null; + __setRenderDirty(); + } + } + + private function setColor(data:ColorData):Void { + var newTransform = new ColorTransform(); + if (data != null) { + newTransform.redOffset = (data.redOffset == null ? 0 : data.redOffset); + newTransform.greenOffset = (data.greenOffset == null ? 0 : data.greenOffset); + newTransform.blueOffset = (data.blueOffset == null ? 0 : data.blueOffset); + newTransform.alphaOffset = (data.AlphaOffset == null ? 0 : data.AlphaOffset); + + newTransform.redMultiplier = (data.RedMultiplier == null ? 1 : data.RedMultiplier); + newTransform.greenMultiplier = (data.greenMultiplier == null ? 1 : data.greenMultiplier); + newTransform.blueMultiplier = (data.blueMultiplier == null ? 1 : data.blueMultiplier); + newTransform.alphaMultiplier = (data.alphaMultiplier == null ? 1 : data.alphaMultiplier); + } + colorTransform = newTransform; + } + + private function setLoop(data:String):Void { + if (data != null) { + _loopMode = data; + } else { + _loopMode = LoopMode.LOOP; + } + } + + private function setType(data:String):Void { + if (data != null) { + _type = data; + } + } + + private function getNumFrames():Int { + var numFrames:Int = 0; + + for (i in 0..._numLayers) { + var layer = getLayerData(i); + var frameDates:Array = (layer == null ? [] : layer.Frames); + var numFrameDates:Int = (frameDates != null) ? frameDates.length : 0; + var layerNumFrames:Int = (numFrameDates != 0) ? frameDates[0].index : 0; + + for (j in 0...numFrameDates) { + layerNumFrames += frameDates[j].duration; + } + + if (layerNumFrames > numFrames) { + numFrames = layerNumFrames; + } + } + + return numFrames == 0 ? 1 : numFrames; + } + + private function _getFrameLabels():Array { + var labels:Array = []; + + for (i in 0..._numLayers) { + var layer = getLayerData(i); + var frameDates:Array = (layer == null ? [] : layer.Frames); + var numFrameDates:Int = (frameDates != null) ? frameDates.length : 0; + + for (j in 0...numFrameDates) { + var frameData:LayerFrameData = frameDates[j]; + if (frameData.name != null) { + labels.push(new FrameLabel(frameData.name, frameData.index)); + } + } + } + labels.sort(sortLabels); + return labels; + } + + public function getFrameLabels():Array { + return _frameLabels.map(f -> f.name); // Inlining. I feel a js + } + + function sortLabels(i1:FrameLabel, i2:FrameLabel):Int { + var f1 = i1.frame; + var f2 = i2.frame; + if (f1 < f2) { + return -1; + } else if (f1 > f2) { + return 1; + } else { + return 0; + } + } + + private function getLayer(layerIndex:Int):TileContainer { + return _layers[layerIndex]; + } + + public function getNextLabel(afterLabel:String = null):String { + var numLabels:Int = _frameLabels.length; + var startFrame:Int = getFrame(afterLabel == null ? currentLabel : afterLabel); + + for (i in 0...numLabels) { + var label:FrameLabel = _frameLabels[i]; + if (label.frame > startFrame) { + return label.name; + } + } + + return (_frameLabels != null) ? _frameLabels[0].name : null; + } + + private function get_currentLabel():String { + var numLabels:Int = _frameLabels.length; + var highestLabel:FrameLabel = (numLabels != 0) ? _frameLabels[0] : null; + + for (i in 1...numLabels) { + var label:FrameLabel = _frameLabels[i]; + + if (label.frame <= _currentFrame) { + highestLabel = label; + } else { + break; + } + } + + return (highestLabel != null) ? highestLabel.name : null; + } + + public function getFrame(label:String):Int { + var numLabels:Int = _frameLabels.length; + for (i in 0...numLabels) { + var frameLabel:FrameLabel = _frameLabels[i]; + if (frameLabel.name == label) { + return frameLabel.frame; + } + } + return -1; + } + + + private function get_currentFrame():Int { + return _currentFrame; + } + + private function set_currentFrame(value:Int):Int { + while (value < 0) { + value += _numFrames; + } + + if (_loopMode == LoopMode.PLAY_ONCE) { + _currentFrame = Std.int(Math.min(Math.max(value, 0), _numFrames - 1)); + } else { + _currentFrame = Std.int(Math.abs(value % _numFrames)); + } + + if (_composedFrame != _currentFrame) { + update(); + } + return value; + } + + private function get_type():String { + return _type; + } + + private function set_type(value:String):String { + if (SymbolType.isValid(value)) { + _type = value; + } else { + throw new ArgumentError("Invalid symbol type: " + value); + } + return value; + } + + private function get_loopMode():String { + return _loopMode; + } + + private function set_loopMode(value:String):String { + if (LoopMode.isValid(value)) { + _loopMode = value; + } else { + throw new ArgumentError("Invalid loop mode: " + value); + } + return value; + } + + private function get_symbolName():String { + return _symbolName; + } + + public function get_numLayers():Int { + return _numLayers; + } + + public function get_numFrames():Int { + return _numFrames; + } + + // data access + + private function getLayerData(layerIndex:Int):LayerData { + return _data.TIMELINE.LAYERS[layerIndex]; + } + + private function getFrameData(layerIndex:Int, frameIndex:Int):LayerFrameData { + var layer = getLayerData(layerIndex); + if (layer == null) + return null; + + return layer.FrameMap.get(frameIndex); + } +} diff --git a/source/funkin/system/CoolUtil.hx b/source/funkin/system/CoolUtil.hx index 458c4332..65c76da8 100644 --- a/source/funkin/system/CoolUtil.hx +++ b/source/funkin/system/CoolUtil.hx @@ -1,5 +1,6 @@ package funkin.system; +import animateatlas.AtlasFrameMaker; import haxe.Json; import funkin.menus.StoryMenuState.WeekData; import flixel.graphics.frames.FlxAtlasFrames; @@ -268,6 +269,11 @@ class CoolUtil public static function loadFrames(path:String, Unique:Bool = false, Key:String = null):FlxFramesCollection { var noExt = Path.withoutExtension(path); + if (Assets.exists('$noExt/Animation.json') + && Assets.exists('$noExt/spritemap.json') + && Assets.exists('$noExt/spritemap.png')) { + // TODO: Credit Smokey555!! + return AtlasFrameMaker.construct(noExt); if (Assets.exists('$noExt.xml')) { return Paths.getSparrowAtlasAlt(noExt); } else if (Assets.exists('$noExt.txt')) { diff --git a/source/funkin/system/Main.hx b/source/funkin/system/Main.hx index c18d8e5b..66bc9ab8 100644 --- a/source/funkin/system/Main.hx +++ b/source/funkin/system/Main.hx @@ -32,6 +32,8 @@ import funkin.mods.ModsFolder; class Main extends Sprite { + // TODO: CREDIT SMOKEY FOR ATLAS STUFF!! + var gameWidth:Int = 1280; // Width of the game in pixels (might be less / more in actual pixels depending on your zoom). var gameHeight:Int = 720; // Height of the game in pixels (might be less / more in actual pixels depending on your zoom). var initialState:Class = TitleState; // The FlxState the game starts with. diff --git a/update.bat b/update.bat index 1866d461..178086fb 100644 --- a/update.bat +++ b/update.bat @@ -1,3 +1,4 @@ haxelib git flixel-yc29 https://www.github.com/YoshiCrafter29/flixel haxelib git hscript-improved https://www.github.com/YoshiCrafter29/hscript-improved +haxelib git Flixel-TextureAtlas https://github.com/Smokey555/Flixel-TextureAtlas haxelib install hxCodec \ No newline at end of file