diff --git a/spine-as3/spine-as3/src/spine/Bone.as b/spine-as3/spine-as3/src/spine/Bone.as index b2442091b..c548b36e0 100644 --- a/spine-as3/spine-as3/src/spine/Bone.as +++ b/spine-as3/spine-as3/src/spine/Bone.as @@ -42,6 +42,8 @@ public class Bone { public var rotationIK:Number; public var scaleX:Number public var scaleY:Number; + public var flipX:Boolean; + public var flipY:Boolean; internal var _m00:Number; internal var _m01:Number; @@ -52,6 +54,8 @@ public class Bone { internal var _worldRotation:Number; internal var _worldScaleX:Number; internal var _worldScaleY:Number; + internal var _worldFlipX:Boolean; + internal var _worldFlipY:Boolean; /** @param parent May be null. */ public function Bone (data:BoneData, skeleton:Skeleton, parent:Bone) { @@ -77,24 +81,29 @@ public class Bone { _worldScaleY = scaleY; } _worldRotation = _data.inheritRotation ? parent._worldRotation + rotationIK : rotationIK; + _worldFlipX = parent._worldFlipX != flipX; + _worldFlipY = parent._worldFlipY != flipY; } else { - _worldX = _skeleton.flipX ? -x : x; - _worldY = _skeleton.flipY != yDown ? -y : y; + var skeletonFlipX:Boolean = _skeleton.flipX, skeletonFlipY:Boolean = _skeleton.flipY; + _worldX = skeletonFlipX ? -x : x; + _worldY = skeletonFlipY != yDown ? -y : y; _worldScaleX = scaleX; _worldScaleY = scaleY; _worldRotation = rotationIK; + _worldFlipX = skeletonFlipX != flipX; + _worldFlipY = skeletonFlipY != flipY; } var radians:Number = _worldRotation * (Math.PI / 180); var cos:Number = Math.cos(radians); var sin:Number = Math.sin(radians); - if (skeleton.flipX) { + if (_worldFlipX) { _m00 = -cos * _worldScaleX; _m01 = sin * _worldScaleY; } else { _m00 = cos * _worldScaleX; _m01 = -sin * _worldScaleY; } - if (_skeleton.flipY != yDown) { + if (_worldFlipY != yDown) { _m10 = -sin * _worldScaleX; _m11 = -cos * _worldScaleY; } else { @@ -110,6 +119,8 @@ public class Bone { rotationIK = rotation; scaleX = _data.scaleX; scaleY = _data.scaleY; + flipX = _data.flipX; + flipY = _data.flipY; } public function get data () : BoneData { @@ -159,11 +170,19 @@ public class Bone { public function get worldScaleY () : Number { return _worldScaleY; } + + public function get worldFlipX () : Boolean { + return _worldFlipX; + } + + public function get worldFlipY () : Boolean { + return _worldFlipY; + } public function worldToLocal (world:Vector.) : void { var dx:Number = world[0] - _worldX, dy:Number = world[1] - _worldY; var m00:Number = _m00, m10:Number = _m10, m01:Number = _m01, m11:Number = _m11; - if (_skeleton.flipX != (_skeleton.flipY != yDown)) { + if (_worldFlipX != (_worldFlipY != yDown)) { m00 = -m00; m11 = -m11; } diff --git a/spine-as3/spine-as3/src/spine/BoneData.as b/spine-as3/spine-as3/src/spine/BoneData.as index 57b02450e..a5cc2546e 100644 --- a/spine-as3/spine-as3/src/spine/BoneData.as +++ b/spine-as3/spine-as3/src/spine/BoneData.as @@ -41,6 +41,8 @@ public class BoneData { public var scaleY:Number = 1; public var inheritScale:Boolean = true; public var inheritRotation:Boolean = true; + public var flipX:Boolean; + public var flipY:Boolean; /** @param parent May be null. */ public function BoneData (name:String, parent:BoneData) { diff --git a/spine-as3/spine-as3/src/spine/SkeletonJson.as b/spine-as3/spine-as3/src/spine/SkeletonJson.as index e51ac89b5..cfed98b0b 100644 --- a/spine-as3/spine-as3/src/spine/SkeletonJson.as +++ b/spine-as3/spine-as3/src/spine/SkeletonJson.as @@ -38,6 +38,8 @@ import spine.animation.CurveTimeline; import spine.animation.DrawOrderTimeline; import spine.animation.EventTimeline; import spine.animation.FfdTimeline; +import spine.animation.FlipXTimeline; +import spine.animation.FlipYTimeline; import spine.animation.IkConstraintTimeline; import spine.animation.RotateTimeline; import spine.animation.ScaleTimeline; @@ -52,12 +54,6 @@ import spine.attachments.RegionAttachment; import spine.attachments.SkinnedMeshAttachment; public class SkeletonJson { - static public const TIMELINE_SCALE:String = "scale"; - static public const TIMELINE_ROTATE:String = "rotate"; - static public const TIMELINE_TRANSLATE:String = "translate"; - static public const TIMELINE_ATTACHMENT:String = "attachment"; - static public const TIMELINE_COLOR:String = "color"; - public var attachmentLoader:AttachmentLoader; public var scale:Number = 1; @@ -107,6 +103,8 @@ public class SkeletonJson { boneData.rotation = (boneMap["rotation"] || 0); boneData.scaleX = boneMap.hasOwnProperty("scaleX") ? boneMap["scaleX"] : 1; boneData.scaleY = boneMap.hasOwnProperty("scaleY") ? boneMap["scaleY"] : 1; + boneData.flipX = boneMap.hasOwnProperty("flipX") ? boneMap["flipX"] : false; + boneData.flipY = boneMap.hasOwnProperty("flipY") ? boneMap["flipY"] : false; boneData.inheritScale = boneMap.hasOwnProperty("inheritScale") ? boneMap["inheritScale"] : true; boneData.inheritRotation = boneMap.hasOwnProperty("inheritRotation") ? boneMap["inheritRotation"] : true; skeletonData.bones[skeletonData.bones.length] = boneData; @@ -312,7 +310,7 @@ public class SkeletonJson { for (timelineName in slotMap) { values = slotMap[timelineName]; - if (timelineName == TIMELINE_COLOR) { + if (timelineName == "color") { var colorTimeline:ColorTimeline = new ColorTimeline(values.length); colorTimeline.slotIndex = slotIndex; @@ -330,7 +328,7 @@ public class SkeletonJson { timelines[timelines.length] = colorTimeline; duration = Math.max(duration, colorTimeline.frames[colorTimeline.frameCount * 5 - 5]); - } else if (timelineName == TIMELINE_ATTACHMENT) { + } else if (timelineName == "attachment") { var attachmentTimeline:AttachmentTimeline = new AttachmentTimeline(values.length); attachmentTimeline.slotIndex = slotIndex; @@ -339,7 +337,7 @@ public class SkeletonJson { attachmentTimeline.setFrame(frameIndex++, valueMap["time"], valueMap["name"]); timelines[timelines.length] = attachmentTimeline; duration = Math.max(duration, attachmentTimeline.frames[attachmentTimeline.frameCount - 1]); - + } else throw new Error("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")"); } @@ -353,7 +351,7 @@ public class SkeletonJson { for (timelineName in boneMap) { values = boneMap[timelineName]; - if (timelineName == TIMELINE_ROTATE) { + if (timelineName == "rotate") { var rotateTimeline:RotateTimeline = new RotateTimeline(values.length); rotateTimeline.boneIndex = boneIndex; @@ -366,10 +364,10 @@ public class SkeletonJson { timelines[timelines.length] = rotateTimeline; duration = Math.max(duration, rotateTimeline.frames[rotateTimeline.frameCount * 2 - 2]); - } else if (timelineName == TIMELINE_TRANSLATE || timelineName == TIMELINE_SCALE) { + } else if (timelineName == "translate" || timelineName == "scale") { var timeline:TranslateTimeline; var timelineScale:Number = 1; - if (timelineName == TIMELINE_SCALE) + if (timelineName == "scale") timeline = new ScaleTimeline(values.length); else { timeline = new TranslateTimeline(values.length); @@ -388,6 +386,20 @@ public class SkeletonJson { timelines[timelines.length] = timeline; duration = Math.max(duration, timeline.frames[timeline.frameCount * 3 - 3]); + } else if (timelineName == "flipX" || timelineName == "flipY") { + var flipX:Boolean = timelineName == "flipX"; + var flipTimeline:FlipXTimeline = flipX ? new FlipXTimeline(values.length) : new FlipYTimeline(values.length); + flipTimeline.boneIndex = boneIndex; + + var field:String = flipX ? "x" : "y"; + frameIndex = 0; + for each (valueMap in values) { + flipTimeline.setFrame(frameIndex, valueMap["time"], valueMap.hasOwnProperty(field) ? valueMap[field] : false); + frameIndex++; + } + timelines[timelines.length] = flipTimeline; + duration = Math.max(duration, flipTimeline.frames[flipTimeline.frameCount * 3 - 3]); + } else throw new Error("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")"); } diff --git a/spine-as3/spine-as3/src/spine/animation/FlipXTimeline.as b/spine-as3/spine-as3/src/spine/animation/FlipXTimeline.as new file mode 100644 index 000000000..c69180d0d --- /dev/null +++ b/spine-as3/spine-as3/src/spine/animation/FlipXTimeline.as @@ -0,0 +1,73 @@ +/****************************************************************************** + * Spine Runtimes Software License + * Version 2.1 + * + * Copyright (c) 2013, Esoteric Software + * All rights reserved. + * + * You are granted a perpetual, non-exclusive, non-sublicensable and + * non-transferable license to install, execute and perform the Spine Runtimes + * Software (the "Software") solely for internal use. Without the written + * permission of Esoteric Software (typically granted by licensing Spine), you + * may not (a) modify, translate, adapt or otherwise create derivative works, + * improvements of the Software or develop new applications using the Software + * or (b) remove, delete, alter or obscure any trademarks or any copyright, + * trademark, patent or other intellectual property or proprietary rights + * notices on or in the Software, including any copy thereof. Redistributions + * in binary or source form must include this license and terms. + * + * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 ESOTERIC SOFTARE 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. + *****************************************************************************/ + +package spine.animation { +import spine.Bone; +import spine.Event; +import spine.Skeleton; + +public class FlipXTimeline implements Timeline { + public var boneIndex:int; + public var frames:Vector.; // time, flip, ... + + public function FlipXTimeline (frameCount:int) { + frames = new Vector.(frameCount * 2, true); + } + + public function get frameCount () : int { + return frames.length / 2; + } + + /** Sets the time and angle of the specified keyframe. */ + public function setFrame (frameIndex:int, time:Number, flip:Boolean) : void { + frameIndex *= 2; + frames[frameIndex] = time; + frames[int(frameIndex + 1)] = flip ? 1 : 0; + } + + public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number) : void { + if (time < frames[0]) { + if (lastTime > time) apply(skeleton, lastTime, int.MAX_VALUE, null, 0); + return; + } else if (lastTime > time) // + lastTime = -1; + + var frameIndex:int = (time >= frames[frames.length - 2] ? frames.length : Animation.binarySearch(frames, time, 2)) - 2; + if (frames[frameIndex] < lastTime) return; + + setFlip(skeleton.bones[boneIndex], frames[frameIndex + 1] != 0); + } + + protected function setFlip (bone:Bone, flip:Boolean) : void { + bone.flipX = flip; + } +} + +} diff --git a/spine-as3/spine-as3/src/spine/animation/FlipYTimeline.as b/spine-as3/spine-as3/src/spine/animation/FlipYTimeline.as new file mode 100644 index 000000000..827ae0ed6 --- /dev/null +++ b/spine-as3/spine-as3/src/spine/animation/FlipYTimeline.as @@ -0,0 +1,44 @@ +/****************************************************************************** + * Spine Runtimes Software License + * Version 2.1 + * + * Copyright (c) 2013, Esoteric Software + * All rights reserved. + * + * You are granted a perpetual, non-exclusive, non-sublicensable and + * non-transferable license to install, execute and perform the Spine Runtimes + * Software (the "Software") solely for internal use. Without the written + * permission of Esoteric Software (typically granted by licensing Spine), you + * may not (a) modify, translate, adapt or otherwise create derivative works, + * improvements of the Software or develop new applications using the Software + * or (b) remove, delete, alter or obscure any trademarks or any copyright, + * trademark, patent or other intellectual property or proprietary rights + * notices on or in the Software, including any copy thereof. Redistributions + * in binary or source form must include this license and terms. + * + * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 ESOTERIC SOFTARE 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. + *****************************************************************************/ + +package spine.animation { +import spine.Bone; + +public class FlipYTimeline extends FlipXTimeline { + public function FlipYTimeline (frameCount:int) { + super(frameCount); + } + + override protected function setFlip (bone:Bone, flip:Boolean) : void { + bone.flipY = flip; + } +} + +} \ No newline at end of file diff --git a/spine-as3/spine-as3/src/spine/flash/SkeletonSprite.as b/spine-as3/spine-as3/src/spine/flash/SkeletonSprite.as index cfef97a80..656892827 100644 --- a/spine-as3/spine-as3/src/spine/flash/SkeletonSprite.as +++ b/spine-as3/spine-as3/src/spine/flash/SkeletonSprite.as @@ -97,6 +97,7 @@ public class SkeletonSprite extends Sprite { bitmap.rotation = -regionAttachment.rotation; bitmap.scaleX = regionAttachment.scaleX * (regionAttachment.width / region.width); bitmap.scaleY = regionAttachment.scaleY * (regionAttachment.height / region.height); + // Position using attachment translation, shifted as if scale and rotation were at image center. var radians:Number = -regionAttachment.rotation * Math.PI / 180; @@ -127,10 +128,12 @@ public class SkeletonSprite extends Sprite { colorTransform.alphaMultiplier = skeleton.a * slot.a * regionAttachment.a; wrapper.transform.colorTransform = colorTransform; + var bone:Bone = slot.bone; var flipX:int = skeleton.flipX ? -1 : 1; var flipY:int = skeleton.flipY ? -1 : 1; + if (bone.worldFlipX) flipX = -flipX; + if (bone.worldFlipY) flipY = -flipY; - var bone:Bone = slot.bone; wrapper.x = bone.worldX; wrapper.y = bone.worldY; wrapper.rotation = -bone.worldRotation * flipX * flipY;