Browse Source

Flip for bones. Flip timelines now affect bones. "draworder" in JSON -> "drawOrder".

Falls back to "draworder" to not break existing JSON (for the time being).
NathanSweet 11 years ago
parent
commit
22b2086f39

+ 2 - 1
spine-as3/spine-as3/src/spine/SkeletonJson.as

@@ -469,7 +469,8 @@ public class SkeletonJson {
 			}
 		}
 
-		var drawOrderValues:Object = map["draworder"];
+		var drawOrderValues:Object = map["drawOrder"];
+		if (!drawOrderValues) drawOrderValues = map["draworder"];
 		if (drawOrderValues) {
 			var drawOrderTimeline:DrawOrderTimeline = new DrawOrderTimeline(drawOrderValues.length);
 			var slotCount:int = skeletonData.slots.length;

+ 2 - 1
spine-c/include/spine/Animation.h

@@ -307,10 +307,11 @@ typedef spIkConstraintTimeline IkConstraintTimeline;
 /**/
 
 typedef struct {
-	spCurveTimeline super;
+	spTimeline super;
 	int const x;
 	int const framesCount;
 	float* const frames; /* time, flip, ... */
+	int boneIndex;
 } spFlipTimeline;
 
 spFlipTimeline* spFlipTimeline_create (int framesCount, int/*bool*/x);

+ 2 - 0
spine-c/include/spine/Bone.h

@@ -47,11 +47,13 @@ struct spBone {
 	float x, y;
 	float rotation, rotationIK;
 	float scaleX, scaleY;
+	int/*bool*/flipX, flipY;
 
 	float const m00, m01, worldX; /* a b x */
 	float const m10, m11, worldY; /* c d y */
 	float const worldRotation;
 	float const worldScaleX, worldScaleY;
+	int/*bool*/const worldFlipX, worldFlipY;
 };
 
 void spBone_setYDown (int/*bool*/yDown);

+ 1 - 0
spine-c/include/spine/BoneData.h

@@ -43,6 +43,7 @@ struct spBoneData {
 	float x, y;
 	float rotation;
 	float scaleX, scaleY;
+	int/*bool*/flipX, flipY;
 	int/*bool*/inheritScale, inheritRotation;
 };
 

+ 4 - 5
spine-c/src/spine/Animation.c

@@ -808,22 +808,21 @@ void _spFlipTimeline_apply (const spTimeline* timeline, spSkeleton* skeleton, fl
 	if (self->frames[frameIndex] <= lastTime) return;
 
 	if (self->x)
-		skeleton->flipX = self->frames[frameIndex + 1];
+		skeleton->bones[self->boneIndex]->flipX = self->frames[frameIndex + 1];
 	else
-		skeleton->flipY = self->frames[frameIndex + 1];
+		skeleton->bones[self->boneIndex]->flipY = self->frames[frameIndex + 1];
 }
 
 void _spFlipTimeline_dispose (spTimeline* timeline) {
 	spFlipTimeline* self = SUB_CAST(spFlipTimeline, timeline);
-	_spCurveTimeline_deinit(SUPER(self));
+	_spTimeline_deinit(SUPER(self));
 	FREE(self->frames);
 	FREE(self);
 }
 
 spFlipTimeline* spFlipTimeline_create (int framesCount, int/*bool*/x) {
 	spFlipTimeline* self = NEW(spFlipTimeline);
-	_spCurveTimeline_init(SUPER(self), x ? SP_TIMELINE_FLIPX : SP_TIMELINE_FLIPY, framesCount,
-			_spFlipTimeline_dispose, _spFlipTimeline_apply);
+	_spTimeline_init(SUPER(self), x ? SP_TIMELINE_FLIPX : SP_TIMELINE_FLIPY, _spFlipTimeline_dispose, _spFlipTimeline_apply);
 	CONST_CAST(int, self->x) = x;
 	CONST_CAST(int, self->framesCount) = framesCount << 1;
 	CONST_CAST(float*, self->frames) = CALLOC(float, self->framesCount);

+ 10 - 3
spine-c/src/spine/Bone.c

@@ -64,24 +64,29 @@ void spBone_updateWorldTransform (spBone* self) {
 		}
 		CONST_CAST(float, self->worldRotation) =
 				self->data->inheritRotation ? self->parent->worldRotation + self->rotationIK : self->rotationIK;
+		CONST_CAST(int, self->worldFlipX) = self->parent->worldFlipX ^ self->flipX;
+		CONST_CAST(int, self->worldFlipY) = self->parent->worldFlipY ^ self->flipY;
 	} else {
+		int skeletonFlipX = self->skeleton->flipX, skeletonFlipY = self->skeleton->flipY;
 		CONST_CAST(float, self->worldX) = self->skeleton->flipX ? -self->x : self->x;
 		CONST_CAST(float, self->worldY) = self->skeleton->flipY != yDown ? -self->y : self->y;
 		CONST_CAST(float, self->worldScaleX) = self->scaleX;
 		CONST_CAST(float, self->worldScaleY) = self->scaleY;
 		CONST_CAST(float, self->worldRotation) = self->rotationIK;
+		CONST_CAST(int, self->worldFlipX) = skeletonFlipX ^ self->flipX;
+		CONST_CAST(int, self->worldFlipY) = skeletonFlipY ^ self->flipY;
 	}
 	radians = self->worldRotation * DEG_RAD;
 	cosine = COS(radians);
 	sine = SIN(radians);
-	if (self->skeleton->flipX) {
+	if (self->worldFlipX) {
 		CONST_CAST(float, self->m00) = -cosine * self->worldScaleX;
 		CONST_CAST(float, self->m01) = sine * self->worldScaleY;
 	} else {
 		CONST_CAST(float, self->m00) = cosine * self->worldScaleX;
 		CONST_CAST(float, self->m01) = -sine * self->worldScaleY;
 	}
-	if (self->skeleton->flipY != yDown) {
+	if (self->worldFlipY != yDown) {
 		CONST_CAST(float, self->m10) = -sine * self->worldScaleX;
 		CONST_CAST(float, self->m11) = -cosine * self->worldScaleY;
 	} else {
@@ -97,13 +102,15 @@ void spBone_setToSetupPose (spBone* self) {
 	self->rotationIK = self->rotation;
 	self->scaleX = self->data->scaleX;
 	self->scaleY = self->data->scaleY;
+	self->flipX = self->data->flipX;
+	self->flipY = self->data->flipY;
 }
 
 void spBone_worldToLocal (spBone* self, float worldX, float worldY, float* localX, float* localY) {
 	float invDet;
 	float dx = worldX - self->worldX, dy = worldY - self->worldY;
 	float m00 = self->m00, m11 = self->m11;
-	if (self->skeleton->flipX != (self->skeleton->flipY != yDown)) {
+	if (self->worldFlipX != (self->worldFlipY != yDown)) {
 		m00 *= -1;
 		m11 *= -1;
 	}

+ 13 - 21
spine-c/src/spine/SkeletonJson.c

@@ -111,7 +111,8 @@ static spAnimation* _spSkeletonJson_readAnimation (spSkeletonJson* self, Json* r
 	Json* slots = Json_getItem(root, "slots");
 	Json* ik = Json_getItem(root, "ik");
 	Json* ffd = Json_getItem(root, "ffd");
-	Json* drawOrder = Json_getItem(root, "draworder");
+	Json* drawOrder = Json_getItem(root, "drawOrder");
+	if (!drawOrder) drawOrder = Json_getItem(root, "draworder");
 	Json* events = Json_getItem(root, "events");
 	Json* flipX = Json_getItem(root, "flipx");
 	Json* flipY = Json_getItem(root, "flipy");
@@ -218,6 +219,17 @@ static spAnimation* _spSkeletonJson_readAnimation (spSkeletonJson* self, Json* r
 					animation->timelines[animation->timelinesCount++] = SUPER_CAST(spTimeline, timeline);
 					duration = timeline->frames[timelineArray->size * 3 - 3];
 					if (duration > animation->duration) animation->duration = duration;
+				} else if (strcmp(timelineArray->name, "flipX") == 0 || strcmp(timelineArray->name, "flipY") == 0) {
+					int x = strcmp(timelineArray->name, "flipX") == 0;
+					char* field = x ? "x" : "y";
+					spFlipTimeline *timeline = spFlipTimeline_create(timelineArray->size, x);
+					timeline->boneIndex = boneIndex;
+					for (frame = timelineArray->child, i = 0; frame; frame = frame->next, ++i)
+						spFlipTimeline_setFrame(timeline, i, Json_getFloat(frame, "time", 0), Json_getInt(frame, field, 0));
+					animation->timelines[animation->timelinesCount++] = SUPER_CAST(spTimeline, timeline);
+					duration = timeline->frames[timelineArray->size * 2 - 2];
+					if (duration > animation->duration) animation->duration = duration;
+
 				} else {
 					spAnimation_dispose(animation);
 					_spSkeletonJson_setError(self, 0, "Invalid timeline type for a bone: ", timelineArray->name);
@@ -316,26 +328,6 @@ static spAnimation* _spSkeletonJson_readAnimation (spSkeletonJson* self, Json* r
 		}
 	}
 
-	/* Flip timelines. */
-	if (flipX) {
-		Json* frame;
-		spFlipTimeline* timeline = spFlipTimeline_create(flipX->size, 1);
-		for (frame = flipX->child, i = 0; frame; frame = frame->next, ++i)
-			spFlipTimeline_setFrame(timeline, i, Json_getFloat(frame, "time", 0), Json_getInt(frame, "x", 0));
-		animation->timelines[animation->timelinesCount++] = SUPER_CAST(spTimeline, timeline);
-		duration = timeline->frames[flipX->size * 2 - 2];
-		if (duration > animation->duration) animation->duration = duration;
-	}
-	if (flipY) {
-		Json* frame;
-		spFlipTimeline* timeline = spFlipTimeline_create(flipY->size, 0);
-		for (frame = flipY->child, i = 0; frame; frame = frame->next, ++i)
-			spFlipTimeline_setFrame(timeline, i, Json_getFloat(frame, "time", 0), Json_getInt(frame, "y", 0));
-		animation->timelines[animation->timelinesCount++] = SUPER_CAST(spTimeline, timeline);
-		duration = timeline->frames[flipY->size * 2 - 2];
-		if (duration > animation->duration) animation->duration = duration;
-	}
-
 	/* Draw order timeline. */
 	if (drawOrder) {
 		spDrawOrderTimeline* timeline = spDrawOrderTimeline_create(drawOrder->size, skeletonData->slotsCount);

+ 11 - 9
spine-csharp/src/Animation.cs

@@ -670,13 +670,15 @@ namespace Spine {
 		}
 	}
 
-	public class FlipXTimeline : CurveTimeline {
+	public class FlipXTimeline : Timeline {
+		internal int boneIndex;
 		internal float[] frames;
 
+		public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } }
 		public float[] Frames { get { return frames; } set { frames = value; } } // time, flip, ...
+		public int FrameCount { get { return frames.Length >> 1; } }
 
-		public FlipXTimeline (int frameCount)
-			: base(frameCount) {
+		public FlipXTimeline (int frameCount) {
 			frames = new float[frameCount << 1];
 		}
 
@@ -687,7 +689,7 @@ namespace Spine {
 			frames[frameIndex + 1] = flip ? 1 : 0;
 		}
 
-		override public void Apply (Skeleton skeleton, float lastTime, float time, List<Event> firedEvents, float alpha) {
+		public void Apply (Skeleton skeleton, float lastTime, float time, List<Event> firedEvents, float alpha) {
 			float[] frames = this.frames;
 			if (time < frames[0]) {
 				if (lastTime > time) Apply(skeleton, lastTime, int.MaxValue, null, 0);
@@ -698,11 +700,11 @@ namespace Spine {
 			int frameIndex = (time >= frames[frames.Length - 2] ? frames.Length : Animation.binarySearch(frames, time, 2)) - 2;
 			if (frames[frameIndex] <= lastTime) return;
 
-			Flip(skeleton, frames[frameIndex + 1] != 0);
+			SetFlip(skeleton.bones[boneIndex], frames[frameIndex + 1] != 0);
 		}
 
-		virtual protected void Flip (Skeleton skeleton, bool flip) {
-			skeleton.flipX = flip;
+		virtual protected void SetFlip (Bone bone, bool flip) {
+			bone.flipX = flip;
 		}
 	}
 
@@ -711,8 +713,8 @@ namespace Spine {
 			: base(frameCount) {
 		}
 
-		override protected void Flip (Skeleton skeleton, bool flip) {
-			skeleton.flipY = flip;
+		override protected void SetFlip (Bone bone, bool flip) {
+			bone.flipY = flip;
 		}
 	}
 }

+ 18 - 6
spine-csharp/src/Bone.cs

@@ -40,8 +40,10 @@ namespace Spine {
 		internal Bone parent;
 		internal List<Bone> children;
 		internal float x, y, rotation, rotationIK, scaleX, scaleY;
+		internal bool flipX, flipY;
 		internal float m00, m01, m10, m11;
 		internal float worldX, worldY, worldRotation, worldScaleX, worldScaleY;
+		internal bool worldFlipX, worldFlipY;
 
 		public BoneData Data { get { return data; } }
 		public Skeleton Skeleton { get { return skeleton; } }
@@ -55,6 +57,8 @@ namespace Spine {
 		public float RotationIK { get { return rotationIK; } set { rotationIK = value; } }
 		public float ScaleX { get { return scaleX; } set { scaleX = value; } }
 		public float ScaleY { get { return scaleY; } set { scaleY = value; } }
+		public bool FlipX { get { return flipX; } set { flipX = value; } }
+		public bool FlipY { get { return flipY; } set { flipY = value; } }
 
 		public float M00 { get { return m00; } }
 		public float M01 { get { return m01; } }
@@ -65,6 +69,8 @@ namespace Spine {
 		public float WorldRotation { get { return worldRotation; } }
 		public float WorldScaleX { get { return worldScaleX; } }
 		public float WorldScaleY { get { return worldScaleY; } }
+		public bool WorldFlipX { get { return worldFlipX; } set { worldFlipX = value; } }
+		public bool WorldFlipY { get { return worldFlipY; } set { worldFlipY = value; } }
 
 		/// <param name="parent">May be null.</param>
 		public Bone (BoneData data, Skeleton skeleton, Bone parent) {
@@ -93,24 +99,29 @@ namespace Spine {
 					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;
+				bool skeletonFlipX = skeleton.flipX, skeletonFlipY = skeleton.flipY;
+				worldX = skeletonFlipX ? -x : x;
+				worldY = skeletonFlipY != yDown ? -y : y;
 				worldScaleX = scaleX;
 				worldScaleY = scaleY;
 				worldRotation = rotationIK;
+				worldFlipX = skeletonFlipX ^ flipX;
+				worldFlipY = skeletonFlipY ^ flipY;
 			}
 			float radians = worldRotation * (float)Math.PI / 180;
 			float cos = (float)Math.Cos(radians);
 			float sin = (float)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 {
@@ -127,13 +138,14 @@ namespace Spine {
 			rotationIK = rotation;
 			scaleX = data.scaleX;
 			scaleY = data.scaleY;
+			flipX = data.flipX;
+			flipY = data.flipY;
 		}
 
 		public void worldToLocal (float worldX, float worldY, out float localX, out float localY) {
 			float dx = worldX - this.worldX, dy = worldY - this.worldY;
 			float m00 = this.m00, m10 = this.m10, m01 = this.m01, m11 = this.m11;
-			Skeleton skeleton = this.skeleton;
-			if (skeleton.flipX != (skeleton.flipY != yDown)) {
+			if (worldFlipX != (worldFlipY != yDown)) {
 				m00 = -m00;
 				m11 = -m11;
 			}

+ 3 - 0
spine-csharp/src/BoneData.cs

@@ -35,6 +35,7 @@ namespace Spine {
 		internal BoneData parent;
 		internal String name;
 		internal float length, x, y, rotation, scaleX = 1, scaleY = 1;
+		internal bool flipX, flipY;
 		internal bool inheritScale = true, inheritRotation = true;
 
 		/// <summary>May be null.</summary>
@@ -46,6 +47,8 @@ namespace Spine {
 		public float Rotation { get { return rotation; } set { rotation = value; } }
 		public float ScaleX { get { return scaleX; } set { scaleX = value; } }
 		public float ScaleY { get { return scaleY; } set { scaleY = value; } }
+		public bool FlipX { get { return flipX; } set { flipX = value; } }
+		public bool FlipY { get { return flipY; } set { flipY = value; } }
 		public bool InheritScale { get { return inheritScale; } set { inheritScale = value; } }
 		public bool InheritRotation { get { return inheritRotation; } set { inheritRotation = value; } }
 

+ 24 - 26
spine-csharp/src/SkeletonJson.cs

@@ -114,6 +114,8 @@ namespace Spine {
 				boneData.rotation = GetFloat(boneMap, "rotation", 0);
 				boneData.scaleX = GetFloat(boneMap, "scaleX", 1);
 				boneData.scaleY = GetFloat(boneMap, "scaleY", 1);
+				boneData.flipX = GetBoolean(boneMap, "flipX", false);
+				boneData.flipY = GetBoolean(boneMap, "flipY", false);
 				boneData.inheritScale = GetBoolean(boneMap, "inheritScale", true);
 				boneData.inheritRotation = GetBoolean(boneMap, "inheritRotation", true);
 				skeletonData.bones.Add(boneData);
@@ -387,7 +389,7 @@ namespace Spine {
 					foreach (KeyValuePair<String, Object> timelineEntry in timelineMap) {
 						var values = (List<Object>)timelineEntry.Value;
 						var timelineName = (String)timelineEntry.Key;
-						if (timelineName.Equals("color")) {
+						if (timelineName == "color") {
 							var timeline = new ColorTimeline(values.Count);
 							timeline.slotIndex = slotIndex;
 
@@ -402,7 +404,7 @@ namespace Spine {
 							timelines.Add(timeline);
 							duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 5 - 5]);
 
-						} else if (timelineName.Equals("attachment")) {
+						} else if (timelineName == "attachment") {
 							var timeline = new AttachmentTimeline(values.Count);
 							timeline.slotIndex = slotIndex;
 
@@ -431,7 +433,7 @@ namespace Spine {
 					foreach (KeyValuePair<String, Object> timelineEntry in timelineMap) {
 						var values = (List<Object>)timelineEntry.Value;
 						var timelineName = (String)timelineEntry.Key;
-						if (timelineName.Equals("rotate")) {
+						if (timelineName == "rotate") {
 							var timeline = new RotateTimeline(values.Count);
 							timeline.boneIndex = boneIndex;
 
@@ -445,10 +447,10 @@ namespace Spine {
 							timelines.Add(timeline);
 							duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 2 - 2]);
 
-						} else if (timelineName.Equals("translate") || timelineName.Equals("scale")) {
+						} else if (timelineName == "translate" || timelineName == "scale") {
 							TranslateTimeline timeline;
 							float timelineScale = 1;
-							if (timelineName.Equals("scale"))
+							if (timelineName == "scale")
 								timeline = new ScaleTimeline(values.Count);
 							else {
 								timeline = new TranslateTimeline(values.Count);
@@ -468,6 +470,21 @@ namespace Spine {
 							timelines.Add(timeline);
 							duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 3 - 3]);
 
+						} else if (timelineName == "flipX" || timelineName == "flipY") {
+							bool x = timelineName == "flipX";
+							var timeline = x ? new FlipXTimeline(values.Count) : new FlipYTimeline(values.Count);
+							timeline.boneIndex = boneIndex;
+
+							String field = x ? "x" : "y";
+							int frameIndex = 0;
+							foreach (Dictionary<String, Object> valueMap in values) {
+								float time = (float)valueMap["time"];
+								timeline.SetFrame(frameIndex, time, valueMap.ContainsKey(field) ? (bool)valueMap[field] : false);
+								frameIndex++;
+							}
+							timelines.Add(timeline);
+							duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 2 - 2]);
+
 						} else
 							throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")");
 					}
@@ -550,27 +567,8 @@ namespace Spine {
 				}
 			}
 
-			if (map.ContainsKey("flipx")) {
-				var flipMap = (List<Object>)map["flipx"];
-				var timeline = new FlipXTimeline(flipMap.Count);
-				int frameIndex = 0;
-				foreach (Dictionary<String, Object> valueMap in flipMap)
-					timeline.SetFrame(frameIndex++, (float)valueMap["time"], valueMap.ContainsKey("x") ? (bool)valueMap["x"] : false);
-				timelines.Add(timeline);
-				duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 2 - 2]);
-			}
-			if (map.ContainsKey("flipy")) {
-				var flipMap = (List<Object>)map["flipy"];
-				var timeline = new FlipYTimeline(flipMap.Count);
-				int frameIndex = 0;
-				foreach (Dictionary<String, Object> valueMap in flipMap)
-					timeline.SetFrame(frameIndex++, (float)valueMap["time"], valueMap.ContainsKey("y") ? (bool)valueMap["y"] : false);
-				timelines.Add(timeline);
-				duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 2 - 2]);
-			}
-
-			if (map.ContainsKey("draworder")) {
-				var values = (List<Object>)map["draworder"];
+			if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) {
+				var values = (List<Object>)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"];
 				var timeline = new DrawOrderTimeline(values.Count);
 				int slotCount = skeletonData.slots.Count;
 				int frameIndex = 0;

+ 2 - 1
spine-js/spine.js

@@ -1712,7 +1712,8 @@ spine.SkeletonJson.prototype = {
 			}
 		}
 
-		var drawOrderValues = map["draworder"];
+		var drawOrderValues = map["drawOrder"];
+		if (!drawOrderValues) drawOrderValues = map["draworder"];
 		if (drawOrderValues) {
 			var timeline = new spine.DrawOrderTimeline(drawOrderValues.length);
 			var slotCount = skeletonData.slots.length;

+ 14 - 7
spine-libgdx/src/com/esotericsoftware/spine/Animation.java

@@ -778,12 +778,21 @@ public class Animation {
 	}
 
 	static public class FlipXTimeline implements Timeline {
+		int boneIndex;
 		final float[] frames; // time, flip, ...
 
 		public FlipXTimeline (int frameCount) {
 			frames = new float[frameCount << 1];
 		}
 
+		public void setBoneIndex (int boneIndex) {
+			this.boneIndex = boneIndex;
+		}
+
+		public int getBoneIndex () {
+			return boneIndex;
+		}
+
 		public int getFrameCount () {
 			return frames.length >> 1;
 		}
@@ -806,15 +815,13 @@ public class Animation {
 				return;
 			} else if (lastTime > time) //
 				lastTime = -1;
-
 			int frameIndex = (time >= frames[frames.length - 2] ? frames.length : binarySearch(frames, time, 2)) - 2;
 			if (frames[frameIndex] <= lastTime) return;
-
-			flip(skeleton, frames[frameIndex + 1] != 0);
+			setFlip(skeleton.bones.get(boneIndex), frames[frameIndex + 1] != 0);
 		}
 
-		protected void flip (Skeleton skeleton, boolean flip) {
-			skeleton.setFlipX(flip);
+		protected void setFlip (Bone bone, boolean flip) {
+			bone.setFlipX(flip);
 		}
 	}
 
@@ -823,8 +830,8 @@ public class Animation {
 			super(frameCount);
 		}
 
-		protected void flip (Skeleton skeleton, boolean flip) {
-			skeleton.setFlipY(flip);
+		protected void setFlip (Bone bone, boolean flip) {
+			bone.setFlipY(flip);
 		}
 	}
 }

+ 40 - 6
spine-libgdx/src/com/esotericsoftware/spine/Bone.java

@@ -43,11 +43,13 @@ public class Bone {
 	float x, y;
 	float rotation, rotationIK;
 	float scaleX, scaleY;
+	boolean flipX, flipY;
 
 	float m00, m01, worldX; // a b x
 	float m10, m11, worldY; // c d y
 	float worldRotation;
 	float worldScaleX, worldScaleY;
+	boolean worldFlipX, worldFlipY;
 
 	Bone (BoneData data) {
 		this.data = data;
@@ -78,6 +80,8 @@ public class Bone {
 		rotationIK = bone.rotationIK;
 		scaleX = bone.scaleX;
 		scaleY = bone.scaleY;
+		flipX = bone.flipX;
+		flipY = bone.flipY;
 	}
 
 	/** Computes the world SRT using the parent bone and the local SRT. */
@@ -96,23 +100,28 @@ 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 ? -y : y;
+			boolean skeletonFlipX = skeleton.flipX, skeletonFlipY = skeleton.flipY;
+			worldX = skeletonFlipX ? -x : x;
+			worldY = skeletonFlipY ? -y : y;
 			worldScaleX = scaleX;
 			worldScaleY = scaleY;
 			worldRotation = rotationIK;
+			worldFlipX = skeletonFlipX ^ flipX;
+			worldFlipY = skeletonFlipY ^ flipY;
 		}
 		float cos = MathUtils.cosDeg(worldRotation);
 		float sin = MathUtils.sinDeg(worldRotation);
-		if (skeleton.flipX) {
+		if (worldFlipX) {
 			m00 = -cos * worldScaleX;
 			m01 = sin * worldScaleY;
 		} else {
 			m00 = cos * worldScaleX;
 			m01 = -sin * worldScaleY;
 		}
-		if (skeleton.flipY) {
+		if (worldFlipY) {
 			m10 = -sin * worldScaleX;
 			m11 = -cos * worldScaleY;
 		} else {
@@ -129,6 +138,8 @@ public class Bone {
 		rotationIK = rotation;
 		scaleX = data.scaleX;
 		scaleY = data.scaleY;
+		flipX = data.flipX;
+		flipY = data.flipY;
 	}
 
 	public BoneData getData () {
@@ -208,6 +219,22 @@ public class Bone {
 		scaleY = scale;
 	}
 
+	public boolean getFlipX () {
+		return flipX;
+	}
+
+	public void setFlipX (boolean flipX) {
+		this.flipX = flipX;
+	}
+
+	public boolean getFlipY () {
+		return flipY;
+	}
+
+	public void setFlipY (boolean flipY) {
+		this.flipY = flipY;
+	}
+
 	public float getM00 () {
 		return m00;
 	}
@@ -244,6 +271,14 @@ public class Bone {
 		return worldScaleY;
 	}
 
+	public boolean getWorldFlipX () {
+		return worldFlipX;
+	}
+
+	public boolean getWorldFlipY () {
+		return worldFlipY;
+	}
+
 	public Matrix3 getWorldTransform (Matrix3 worldTransform) {
 		if (worldTransform == null) throw new IllegalArgumentException("worldTransform cannot be null.");
 		float[] val = worldTransform.val;
@@ -262,8 +297,7 @@ public class Bone {
 	public Vector2 worldToLocal (Vector2 world) {
 		float x = world.x - worldX, y = world.y - worldY;
 		float m00 = this.m00, m10 = this.m10, m01 = this.m01, m11 = this.m11;
-		Skeleton skeleton = this.skeleton;
-		if (skeleton.flipX != skeleton.flipY) {
+		if (worldFlipX != worldFlipY) {
 			m00 = -m00;
 			m11 = -m11;
 		}

+ 19 - 0
spine-libgdx/src/com/esotericsoftware/spine/BoneData.java

@@ -39,6 +39,7 @@ public class BoneData {
 	float x, y;
 	float rotation;
 	float scaleX = 1, scaleY = 1;
+	boolean flipX, flipY;
 	boolean inheritScale = true, inheritRotation = true;
 
 	// Nonessential.
@@ -63,6 +64,8 @@ public class BoneData {
 		rotation = bone.rotation;
 		scaleX = bone.scaleX;
 		scaleY = bone.scaleY;
+		flipX = bone.flipX;
+		flipY = bone.flipY;
 	}
 
 	/** @return May be null. */
@@ -132,6 +135,22 @@ public class BoneData {
 		this.scaleY = scaleY;
 	}
 
+	public boolean getFlipX () {
+		return flipX;
+	}
+
+	public void setFlipX (boolean flipX) {
+		this.flipX = flipX;
+	}
+
+	public boolean getFlipY () {
+		return flipY;
+	}
+
+	public void setFlipY (boolean flipY) {
+		this.flipY = flipY;
+	}
+
 	public boolean getInheritScale () {
 		return inheritScale;
 	}

+ 17 - 23
spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java

@@ -68,6 +68,8 @@ public class SkeletonBinary {
 	static public final int TIMELINE_TRANSLATE = 2;
 	static public final int TIMELINE_ATTACHMENT = 3;
 	static public final int TIMELINE_COLOR = 4;
+	static public final int TIMELINE_FLIPX = 5;
+	static public final int TIMELINE_FLIPY = 6;
 
 	static public final int CURVE_LINEAR = 0;
 	static public final int CURVE_STEPPED = 1;
@@ -124,6 +126,8 @@ public class SkeletonBinary {
 				boneData.scaleY = input.readFloat();
 				boneData.rotation = input.readFloat();
 				boneData.length = input.readFloat() * scale;
+				boneData.flipX = input.readBoolean();
+				boneData.flipY = input.readBoolean();
 				boneData.inheritScale = input.readBoolean();
 				boneData.inheritRotation = input.readBoolean();
 				if (nonessential) Color.rgba8888ToColor(boneData.color, input.readInt());
@@ -384,7 +388,7 @@ public class SkeletonBinary {
 						break;
 					}
 					case TIMELINE_TRANSLATE:
-					case TIMELINE_SCALE:
+					case TIMELINE_SCALE: {
 						TranslateTimeline timeline;
 						float timelineScale = 1;
 						if (timelineType == TIMELINE_SCALE)
@@ -403,6 +407,18 @@ public class SkeletonBinary {
 						duration = Math.max(duration, timeline.getFrames()[frameCount * 3 - 3]);
 						break;
 					}
+					case TIMELINE_FLIPX:
+					case TIMELINE_FLIPY: {
+						FlipXTimeline timeline = timelineType == TIMELINE_FLIPX ? new FlipXTimeline(frameCount) : new FlipYTimeline(
+							frameCount);
+						timeline.boneIndex = boneIndex;
+						for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
+							timeline.setFrame(frameIndex, input.readFloat(), input.readBoolean());
+						timelines.add(timeline);
+						duration = Math.max(duration, timeline.getFrames()[frameCount * 2 - 2]);
+						break;
+					}
+					}
 				}
 			}
 
@@ -474,28 +490,6 @@ public class SkeletonBinary {
 				}
 			}
 
-			// Flip timelines.
-			int flipCount = input.readInt(true);
-			if (flipCount > 0) {
-				FlipXTimeline timeline = new FlipXTimeline(flipCount);
-				for (int i = 0; i < flipCount; i++) {
-					float time = input.readFloat();
-					timeline.setFrame(i, time, input.readBoolean());
-				}
-				timelines.add(timeline);
-				duration = Math.max(duration, timeline.getFrames()[flipCount * 2 - 2]);
-			}
-			flipCount = input.readInt(true);
-			if (flipCount > 0) {
-				FlipYTimeline timeline = new FlipYTimeline(flipCount);
-				for (int i = 0; i < flipCount; i++) {
-					float time = input.readFloat();
-					timeline.setFrame(i, time, input.readBoolean());
-				}
-				timelines.add(timeline);
-				duration = Math.max(duration, timeline.getFrames()[flipCount * 2 - 2]);
-			}
-
 			// Draw order timeline.
 			int drawOrderCount = input.readInt(true);
 			if (drawOrderCount > 0) {

+ 18 - 21
spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java

@@ -116,6 +116,8 @@ public class SkeletonJson {
 			boneData.rotation = boneMap.getFloat("rotation", 0);
 			boneData.scaleX = boneMap.getFloat("scaleX", 1);
 			boneData.scaleY = boneMap.getFloat("scaleY", 1);
+			boneData.flipX = boneMap.getBoolean("flipX", false);
+			boneData.flipY = boneMap.getBoolean("flipY", false);
 			boneData.inheritScale = boneMap.getBoolean("inheritScale", true);
 			boneData.inheritRotation = boneMap.getBoolean("inheritRotation", true);
 
@@ -385,6 +387,20 @@ public class SkeletonJson {
 					timelines.add(timeline);
 					duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() * 3 - 3]);
 
+				} else if (timelineName.equals("flipX") || timelineName.equals("flipY")) {
+					boolean x = timelineName.equals("flipX");
+					FlipXTimeline timeline = x ? new FlipXTimeline(timelineMap.size) : new FlipYTimeline(timelineMap.size);
+					timeline.boneIndex = boneIndex;
+
+					String field = x ? "x" : "y";
+					int frameIndex = 0;
+					for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next) {
+						timeline.setFrame(frameIndex, valueMap.getFloat("time"), valueMap.getBoolean(field, false));
+						frameIndex++;
+					}
+					timelines.add(timeline);
+					duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() * 2 - 2]);
+
 				} else
 					throw new RuntimeException("Invalid timeline type for a bone: " + timelineName + " (" + boneMap.name + ")");
 			}
@@ -460,28 +476,9 @@ public class SkeletonJson {
 			}
 		}
 
-		// Flip timelines.
-		JsonValue flipMap = map.get("flipx");
-		if (flipMap != null) {
-			FlipXTimeline timeline = new FlipXTimeline(flipMap.size);
-			int frameIndex = 0;
-			for (JsonValue valueMap = flipMap.child; valueMap != null; valueMap = valueMap.next)
-				timeline.setFrame(frameIndex++, valueMap.getFloat("time"), valueMap.getBoolean("x", false));
-			timelines.add(timeline);
-			duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() * 2 - 2]);
-		}
-		flipMap = map.get("flipy");
-		if (flipMap != null) {
-			FlipYTimeline timeline = new FlipYTimeline(flipMap.size);
-			int frameIndex = 0;
-			for (JsonValue valueMap = flipMap.child; valueMap != null; valueMap = valueMap.next)
-				timeline.setFrame(frameIndex++, valueMap.getFloat("time"), valueMap.getBoolean("y", false));
-			timelines.add(timeline);
-			duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() * 2 - 2]);
-		}
-
 		// Draw order timeline.
-		JsonValue drawOrdersMap = map.get("draworder");
+		JsonValue drawOrdersMap = map.get("drawOrder");
+		if (drawOrdersMap == null) drawOrdersMap = map.get("draworder");
 		if (drawOrdersMap != null) {
 			DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrdersMap.size);
 			int slotCount = skeletonData.slots.size;

+ 0 - 2
spine-libgdx/test/com/esotericsoftware/spine/SkeletonViewer.java

@@ -206,8 +206,6 @@ public class SkeletonViewer extends ApplicationAdapter {
 			if (!ui.pauseButton.isChecked()) {
 				state.update(delta);
 				state.apply(skeleton);
-				ui.flipXCheckbox.setChecked(skeleton.getFlipX());
-				ui.flipYCheckbox.setChecked(skeleton.getFlipY());
 			}
 			skeleton.setPosition(skeletonX, skeletonY);
 			// skeleton.setPosition(0, 0);

+ 2 - 1
spine-lua/SkeletonJson.lua

@@ -447,7 +447,8 @@ function SkeletonJson.new (attachmentLoader)
 			end
 		end
 
-		local drawOrderValues = map["draworder"]
+		local drawOrderValues = map["drawOrder"]
+		if not drawOrderValues then drawOrderValues = map["draworder"] end
 		if drawOrderValues then
 			local timeline = Animation.DrawOrderTimeline.new(#drawOrderValues)
 			local slotCount = #skeletonData.slots