From 8534996d249cd0164df2fa3d6be2ff3a0c6188c8 Mon Sep 17 00:00:00 2001 From: Kipp Ashford Date: Wed, 31 Jul 2013 13:16:18 -0700 Subject: [PATCH] Added Flambe support. See spinehx.platform.flambe.renderers.SpineMovie and spinehx.platform.flambe.SpineData. Example projects coming soon. Signed-off-by: Kipp Ashford --- src/spinehx/platform/flambe/SpineData.hx | 31 +++ src/spinehx/platform/flambe/SpineTexture.hx | 30 +++ .../platform/flambe/SpineTextureAtlas.hx | 20 ++ .../platform/flambe/SpineTextureLoader.hx | 22 ++ .../platform/flambe/renderers/RegionSprite.hx | 67 ++++++ .../platform/flambe/renderers/SpineMovie.hx | 200 ++++++++++++++++++ 6 files changed, 370 insertions(+) create mode 100644 src/spinehx/platform/flambe/SpineData.hx create mode 100644 src/spinehx/platform/flambe/SpineTexture.hx create mode 100644 src/spinehx/platform/flambe/SpineTextureAtlas.hx create mode 100644 src/spinehx/platform/flambe/SpineTextureLoader.hx create mode 100644 src/spinehx/platform/flambe/renderers/RegionSprite.hx create mode 100644 src/spinehx/platform/flambe/renderers/SpineMovie.hx diff --git a/src/spinehx/platform/flambe/SpineData.hx b/src/spinehx/platform/flambe/SpineData.hx new file mode 100644 index 0000000..2c8438c --- /dev/null +++ b/src/spinehx/platform/flambe/SpineData.hx @@ -0,0 +1,31 @@ +package spinehx.platform.flambe; + +import flambe.asset.AssetPack; +import spinehx.platform.flambe.*; +import spinehx.Skeleton; +import spinehx.SkeletonData; +import spinehx.SkeletonJson; + +using flambe.util.Strings; + +class SpineData +{ + + /** The skeleton information */ + public var skeleton (default, null) :Skeleton; + /** The atlas used for this skeleton. */ + public var atlas (default, null) :SpineTextureAtlas; + + public function new(pack :AssetPack, directory :String) + { + var aDirName :Array = directory.split("/"); + var name :String = aDirName[aDirName.length-1]; + + this.atlas = new SpineTextureAtlas(pack, name); + + var json :SkeletonJson = SkeletonJson.create(new SpineTextureAtlas(pack, name)); + var data :SkeletonData = json.readSkeletonData(name, pack.getFile(name + "/" + name + ".json").toString()); + this.skeleton = Skeleton.create(data); + } + +} \ No newline at end of file diff --git a/src/spinehx/platform/flambe/SpineTexture.hx b/src/spinehx/platform/flambe/SpineTexture.hx new file mode 100644 index 0000000..a8ebabc --- /dev/null +++ b/src/spinehx/platform/flambe/SpineTexture.hx @@ -0,0 +1,30 @@ +package spinehx.platform.flambe; + +import flambe.asset.AssetPack; + +using flambe.util.Strings; + +class SpineTexture implements spinehx.atlas.Texture { + + public var texture(default, null) :flambe.display.Texture; + + public function new(pack :AssetPack, file :String) { + texture = pack.getTexture(file.removeFileExtension()); + } + + public function getWidth() :Int { + return texture.width; + } + + public function getHeight() :Int { + return texture.height; + } + + public function dispose() :Void { + texture.dispose(); + } + + public function setWrap(uWrap, vWrap) :Void {} + + public function setFilter(minFilter, magFilter) :Void {} +} diff --git a/src/spinehx/platform/flambe/SpineTextureAtlas.hx b/src/spinehx/platform/flambe/SpineTextureAtlas.hx new file mode 100644 index 0000000..6dbcb2e --- /dev/null +++ b/src/spinehx/platform/flambe/SpineTextureAtlas.hx @@ -0,0 +1,20 @@ + +package spinehx.platform.flambe; + +import flambe.asset.AssetPack; +import spinehx.atlas.TextureAtlas; +import spinehx.platform.flambe.SpineTextureLoader; + + +class SpineTextureAtlas extends TextureAtlas +{ + public function new(pack :AssetPack, directory :String) + { + // var atlas:TextureAtlas = TextureAtlas.create(nme.Assets.getText("assets/" + name + ".atlas"), "assets/", new BitmapDataTextureLoader()); + var aDirName :Array = directory.split("/"); + var definition :String = pack.getFile(directory + "/" + aDirName[aDirName.length-1] + ".atlas").toString(); + + super(new TextureAtlasData(definition, directory + "/", false), new SpineTextureLoader(pack) ); + + } +} \ No newline at end of file diff --git a/src/spinehx/platform/flambe/SpineTextureLoader.hx b/src/spinehx/platform/flambe/SpineTextureLoader.hx new file mode 100644 index 0000000..7eb90b8 --- /dev/null +++ b/src/spinehx/platform/flambe/SpineTextureLoader.hx @@ -0,0 +1,22 @@ +package spinehx.platform.flambe; + +import flambe.asset.AssetPack; +import spinehx.atlas.Texture; +import spinehx.atlas.TextureLoader; +import spinehx.platform.flambe.SpineTexture; + +class SpineTextureLoader implements TextureLoader { + + /** The asset pack to draw textures from. */ + public var pack (default, null) :AssetPack; + + public function new(pack :AssetPack) + { + this.pack = pack; + } + + public function loadTexture(textureFile :String, format, useMipMaps) :Texture + { + return new SpineTexture(pack, textureFile); + } +} diff --git a/src/spinehx/platform/flambe/renderers/RegionSprite.hx b/src/spinehx/platform/flambe/renderers/RegionSprite.hx new file mode 100644 index 0000000..210b9a4 --- /dev/null +++ b/src/spinehx/platform/flambe/renderers/RegionSprite.hx @@ -0,0 +1,67 @@ +// +// Flambe - Rapid game development +// https://github.com/aduros/flambe/blob/master/LICENSE.txt + +package spinehx.platform.flambe.renderers; + +import flambe.display.Graphics; +import flambe.display.Sprite; +import flambe.display.Texture; +import spinehx.atlas.TextureRegion; +import spinehx.attachments.RegionAttachment; +import spinehx.atlas.TextureAtlas; + +/** + * An instanced Flump atlased texture. + */ +class RegionSprite extends Sprite +{ + /** The region attachment for this sprite */ + public var regionAttachment (default, null):RegionAttachment; + /** The texture to use for rendering. */ + public var region(default, null) :AtlasRegion; + /** The atlas to draw from. */ + public var atlas(default, null) :Texture; + + public function new (regionAttachment :RegionAttachment) + { + super(); + this.regionAttachment = regionAttachment; + this.region = cast regionAttachment.getRegion(); + this.atlas = cast(region.getTexture(), SpineTexture).texture; + + this.anchorX._ = regionAttachment.width / 2; + this.anchorY._ = regionAttachment.height / 2; + + if (region.rotate) { + _w = region.getRegionHeight(); // Swap width and height if rotated texture. + _h = region.getRegionWidth(); + rotation._ = 90; + this.anchorX._ += region.getRegionWidth(); + } else { + _w = region.getRegionWidth(); + _h = region.getRegionHeight(); + } + } + + override public function draw (g :Graphics) + { + g.drawSubImage(atlas, 0, 0, region.getRegionX(), region.getRegionY(), _w, _h); + } + + override public function getNaturalWidth () :Float + { + return region.getRegionWidth(); + } + + override public function getNaturalHeight () :Float + { + return region.getRegionHeight(); + } + + /** The real width of this region */ + private var _w :Float = 0; + /** The real height of this region */ + private var _h :Float = 0; + +} diff --git a/src/spinehx/platform/flambe/renderers/SpineMovie.hx b/src/spinehx/platform/flambe/renderers/SpineMovie.hx new file mode 100644 index 0000000..2570997 --- /dev/null +++ b/src/spinehx/platform/flambe/renderers/SpineMovie.hx @@ -0,0 +1,200 @@ +/******************************************************************************* + * Copyright (c) 2013, Esoteric Software + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ + +/** + * + * Flambe renderer by Kipp Ashford. + * + */ +package spinehx.platform.flambe.renderers; + +import flambe.Component; +import flambe.display.Sprite; +import flambe.display.Texture; +import flambe.Entity; +import flambe.util.Assert; + +import flambe.util.Value; +import haxe.ds.ObjectMap; + +import spinehx.Animation; +import spinehx.AnimationState; +import spinehx.atlas.TextureAtlas.AtlasRegion; +import spinehx.atlas.TextureRegion; +import spinehx.attachments.Attachment; +import spinehx.attachments.RegionAttachment; +import spinehx.platform.flambe.renderers.RegionSprite; +import spinehx.platform.flambe.SpineTexture; + +class SpineMovie extends Component +{ + /** Skeleton information. */ + public var skeleton (default, null) :Skeleton; + /** The skin currently being used. */ + public var skin (default, null) :Value; + /** The sprites based on region attachment */ + public var sprites :ObjectMap ; + + public function new (data :SpineData, skin :Null = null) + { + this.skin = new Value("default"); + + skeleton = data.skeleton; + if (skeleton.data.getSkins().length > 1) { + if (skin != null) { + setSkin(skin); + } else { + setSkin(skeleton.data.getSkins()[1].getName()); // Set to the first skin unless otherwise specified. + } + } + + skeleton.setFlipY(true); // I don't know why we need this, but it keeps everything upright. + _state = new AnimationState(new AnimationStateData(skeleton.data)); + skeleton.setToSetupPose(); + _holder = new Entity(); + sprites = new ObjectMap(); + } + + override public function onAdded() + { + owner.addChild(_holder); + } + + /** + * Sets up mixing between one animation and the other. + */ + public function setMix(fromName:String, toName:String, duration:Float) :SpineMovie + { + var from :Animation = skeleton.data.findAnimation(fromName); + var to :Animation = skeleton.data.findAnimation(toName); + + if (from == null) { + Assert.fail("SpineMovie.setMix() from animation name '" + fromName + "' is not a valid animation."); + } + if (to == null) { + Assert.fail("SpineMovie.setMix() to animation name '" + toName + "' is not a valid animation."); + } + + _state.getData().setMix(from, to, duration); + return this; + } + + public function setSkin(id :String) :SpineMovie + { + if (skeleton.data.getSkins().length > 1) { + this.skeleton.setSkinByName(id); + this.skin._ = id; + } + return this; + } + + public function play(id :String) :SpineMovie + { + _state.setAnimationByName(id, false); + if (_loop != null) { + _state.addAnimationByNameSimple(_loop, true); + } + return this; + } + + public function loop(id :String) :SpineMovie + { + _loop = id; + _state.setAnimationByName(id, true); + + // animation = skeleton.data.findAnimation(id); + return this; + } + + override public function onUpdate(dt :Float) + { + _state.update(dt); + _state.apply(skeleton); + + skeleton.updateWorldTransform(); + skeleton.update(dt); + + clearBuffers(); + draw(); + } + + private inline function clearBuffers() + { + for (s in sprites) { + s.visible = false; + } + } + + private inline function draw () + { + var drawOrder:Array = skeleton.drawOrder; + var flipX:Int = (skeleton.flipX) ? -1 : 1; + var flipY:Int = (skeleton.flipY) ? 1 : -1; + var flip:Int = flipX * flipY; + + var i :Int = -1, nLen :Int = drawOrder.length-1; + while (i++ < nLen) + { + var slot :Slot = drawOrder[i]; + var attachment :Attachment = slot.attachment; + + if (Std.is(attachment, RegionAttachment)) + { + var regionAttachment:RegionAttachment = cast(attachment, RegionAttachment); + regionAttachment.updateVertices(slot); + + var vertices = regionAttachment.getVertices(); + var partSprite :RegionSprite = sprites.get(regionAttachment); + + if(partSprite == null) + { + partSprite = new RegionSprite(regionAttachment); + var part :Entity = new Entity().add(partSprite); + sprites.set(regionAttachment, partSprite); + _holder.addChild(part); + } + + var region :AtlasRegion = cast regionAttachment.getRegion(); + var bone :Bone = slot.getBone(); + var x :Float = regionAttachment.x - region.offsetX; + var y :Float = regionAttachment.y - region.offsetY; + + partSprite.x._ = bone.worldX + x * bone.m00 + y * bone.m01; + partSprite.y._ = bone.worldY + x * bone.m10 + y * bone.m11; + partSprite.rotation._ = (!region.rotate ? 0 : 90 ) -(bone.worldRotation + regionAttachment.rotation) * flip; + partSprite.scaleX._ = (bone.worldScaleX + regionAttachment.scaleX - 1) * flipX; + partSprite.scaleY._ = (bone.worldScaleY + regionAttachment.scaleY - 1) * flipY; + partSprite.visible = true; + } + } + } + + /** The container for this skeleton. */ + private var _holder :Entity; + /** If the running animation should loop or not. */ + private var _loop :String; + /** The animation state data. */ + private var _state (default, null) :AnimationState; +}