فهرست منبع

spine-libgdx updated to 3.2.00.

* Added transform constraint rotate, scale, and shear offsets and mixes.
* Added TransformConstraintTimeline.
* Added bone shearing.
* Added ShearTimeline.
* Changed Skeleton#updateCache.
* Changed JSON and binary formats. Some binary format fields and enums were rearranged for consistency -- sorry, but it's better for the long term. Docs for both are up to date.
http://esotericsoftware.com/spine-json-format
http://esotericsoftware.com/spine-binary-format
NathanSweet 9 سال پیش
والد
کامیت
8d7f761311
28فایلهای تغییر یافته به همراه553 افزوده شده و 177 حذف شده
  1. 1 1
      spine-as3/README.md
  2. 1 1
      spine-c/README.md
  3. 1 1
      spine-cocos2d-iphone/2/README.md
  4. 1 1
      spine-cocos2d-iphone/3/README.md
  5. 1 1
      spine-cocos2dx/2/README.md
  6. 1 1
      spine-cocos2dx/3/README.md
  7. 1 1
      spine-corona/README.md
  8. 1 1
      spine-csharp/README.md
  9. 2 2
      spine-js/README.md
  10. 188 90
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java
  11. 35 17
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java
  12. 19 3
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java
  13. 5 3
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java
  14. 6 5
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java
  15. 44 14
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java
  16. 40 10
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java
  17. 1 0
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonMeshRenderer.java
  18. 1 0
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java
  19. 130 7
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraint.java
  20. 66 10
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraintData.java
  21. 1 1
      spine-love/README.md
  22. 1 1
      spine-lua/README.md
  23. 1 1
      spine-monogame/README.md
  24. 1 1
      spine-sfml/README.md
  25. 1 1
      spine-starling/README.md
  26. 1 1
      spine-threejs/README.md
  27. 1 1
      spine-turbulenz/README.md
  28. 1 1
      spine-unity/README.md

+ 1 - 1
spine-as3/README.md

@@ -10,7 +10,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f
 
 ## Spine version
 
-spine-as3 works with data exported from the latest version of Spine.
+spine-as3 works with data exported from Spine 3.1.08. Updating spine-as3 to [v3.2](https://trello.com/c/k7KtGdPW/76-update-runtimes-to-support-v3-2-shearing) is in progress.
 
 spine-as3 supports all Spine features, including meshes. If using the `spine.flash` classes for rendering, meshes are not supported.
 

+ 1 - 1
spine-c/README.md

@@ -12,7 +12,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f
 
 ## Spine version
 
-spine-c works with data exported from the latest version of Spine.
+spine-c works with data exported from Spine 3.1.08. Updating spine-c to [v3.2](https://trello.com/c/k7KtGdPW/76-update-runtimes-to-support-v3-2-shearing) is in progress.
 
 spine-c supports all Spine features.
 

+ 1 - 1
spine-cocos2d-iphone/2/README.md

@@ -10,7 +10,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f
 
 ## Spine version
 
-spine-cocos2d-iphone v2 works with data exported from the latest version of Spine.
+spine-cocos2d-iphone v2 works with data exported from Spine 3.1.08. Updating spine-cocos2d-iphone v2 to [v3.2](https://trello.com/c/k7KtGdPW/76-update-runtimes-to-support-v3-2-shearing) is in progress.
 
 spine-cocos2d-iphone v2 supports all Spine features.
 

+ 1 - 1
spine-cocos2d-iphone/3/README.md

@@ -10,7 +10,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f
 
 ## Spine version
 
-spine-cocos2d-iphone v3 works with data exported from the latest version of Spine.
+spine-cocos2d-iphone v3 works with data exported from Spine 3.1.08. Updating spine-cocos2d-iphone v3 to [v3.2](https://trello.com/c/k7KtGdPW/76-update-runtimes-to-support-v3-2-shearing) is in progress.
 
 spine-cocos2d-iphone v3 supports all Spine features.
 

+ 1 - 1
spine-cocos2dx/2/README.md

@@ -10,7 +10,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f
 
 ## Spine version
 
-spine-cocos2dx v2 works with data exported from the latest version of Spine.
+spine-cocos2dx v2 works with data exported from Spine 3.1.08. Updating spine-cocos2dx v2 to [v3.2](https://trello.com/c/k7KtGdPW/76-update-runtimes-to-support-v3-2-shearing) is in progress.
 
 spine-cocos2dx v2 supports all Spine features.
 

+ 1 - 1
spine-cocos2dx/3/README.md

@@ -10,7 +10,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f
 
 ## Spine version
 
-spine-cocos2dx v3 works with data exported from the latest version of Spine.
+spine-cocos2dx v3 works with data exported from Spine 3.1.08. Updating spine-cocos2dx v3 to [v3.2](https://trello.com/c/k7KtGdPW/76-update-runtimes-to-support-v3-2-shearing) is in progress.
 
 spine-cocos2dx v3 supports all Spine features.
 

+ 1 - 1
spine-corona/README.md

@@ -10,7 +10,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f
 
 ## Spine version
 
-spine-corona works with data exported from Spine 2.1.27. Updating spine-corona to [v3.0](https://trello.com/c/tF8UykBM/72-update-runtimes-to-support-v3-0-skewing-scale) and [v3.1](https://trello.com/c/bERJAFEq/73-update-runtimes-to-support-v3-1-linked-meshes) is in progress.
+spine-corona works with data exported from Spine 2.1.27. Updating spine-corona to [v3.0](https://trello.com/c/tF8UykBM/72-update-runtimes-to-support-v3-0-skewing-scale), [v3.1](https://trello.com/c/bERJAFEq/73-update-runtimes-to-support-v3-1-linked-meshes), and [v3.2](https://trello.com/c/k7KtGdPW/76-update-runtimes-to-support-v3-2-shearing) is in progress.
 
 spine-corona supports all Spine features except for rendering meshes due to Corona having a limited graphics API.
 

+ 1 - 1
spine-csharp/README.md

@@ -10,7 +10,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f
 
 ## Spine version
 
-spine-csharp works with data exported from the latest version of Spine.
+spine-csharp works with data exported from Spine 3.1.08. Updating spine-csharp to [v3.2](https://trello.com/c/k7KtGdPW/76-update-runtimes-to-support-v3-2-shearing) is in progress.
 
 spine-csharp supports all Spine features.
 

+ 2 - 2
spine-js/README.md

@@ -14,9 +14,9 @@ The Spine Runtimes are developed with the intent to be used with data exported f
 
 ## Spine version
 
-spine-js works with data exported from the latest version of Spine.
+spine-js works with data exported from Spine 3.1.08. Updating spine-js to [v3.2](https://trello.com/c/k7KtGdPW/76-update-runtimes-to-support-v3-2-shearing) is in progress.
 
-spine-js supports all Spine features. spine-canvas does not support mesh attachments or nonuniform scaling.~
+spine-js supports all Spine features. spine-canvas does not support mesh attachments or nonuniform scaling.
 
 spine-js does not yet support loading the binary format.
 

+ 188 - 90
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java

@@ -240,11 +240,11 @@ public class Animation {
 	}
 
 	static public class RotateTimeline extends CurveTimeline {
-		static private final int PREV_FRAME_TIME = -2;
-		static private final int FRAME_VALUE = 1;
+		static final int PREV_TIME = -2;
+		static final int VALUE = 1;
 
 		int boneIndex;
-		private final float[] frames; // time, angle, ...
+		final float[] frames; // time, angle, ...
 
 		public RotateTimeline (int frameCount) {
 			super(frameCount);
@@ -287,13 +287,13 @@ public class Animation {
 			}
 
 			// Interpolate between the previous frame and the current frame.
-			int frameIndex = binarySearch(frames, time, 2);
-			float prevFrameValue = frames[frameIndex - 1];
-			float frameTime = frames[frameIndex];
-			float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime), 0, 1);
-			percent = getCurvePercent((frameIndex >> 1) - 1, percent);
+			int frame = binarySearch(frames, time, 2);
+			float prevFrameValue = frames[frame - 1];
+			float frameTime = frames[frame];
+			float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime), 0, 1);
+			percent = getCurvePercent((frame >> 1) - 1, percent);
 
-			float amount = frames[frameIndex + FRAME_VALUE] - prevFrameValue;
+			float amount = frames[frame + VALUE] - prevFrameValue;
 			while (amount > 180)
 				amount -= 360;
 			while (amount < -180)
@@ -308,9 +308,9 @@ public class Animation {
 	}
 
 	static public class TranslateTimeline extends CurveTimeline {
-		static final int PREV_FRAME_TIME = -3;
-		static final int FRAME_X = 1;
-		static final int FRAME_Y = 2;
+		static final int PREV_TIME = -3;
+		static final int X = 1;
+		static final int Y = 2;
 
 		int boneIndex;
 		final float[] frames; // time, x, y, ...
@@ -353,15 +353,15 @@ public class Animation {
 			}
 
 			// Interpolate between the previous frame and the current frame.
-			int frameIndex = binarySearch(frames, time, 3);
-			float prevFrameX = frames[frameIndex - 2];
-			float prevFrameY = frames[frameIndex - 1];
-			float frameTime = frames[frameIndex];
-			float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime), 0, 1);
-			percent = getCurvePercent(frameIndex / 3 - 1, percent);
+			int frame = binarySearch(frames, time, 3);
+			float prevFrameX = frames[frame - 2];
+			float prevFrameY = frames[frame - 1];
+			float frameTime = frames[frame];
+			float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime), 0, 1);
+			percent = getCurvePercent(frame / 3 - 1, percent);
 
-			bone.x += (bone.data.x + prevFrameX + (frames[frameIndex + FRAME_X] - prevFrameX) * percent - bone.x) * alpha;
-			bone.y += (bone.data.y + prevFrameY + (frames[frameIndex + FRAME_Y] - prevFrameY) * percent - bone.y) * alpha;
+			bone.x += (bone.data.x + prevFrameX + (frames[frame + X] - prevFrameX) * percent - bone.x) * alpha;
+			bone.y += (bone.data.y + prevFrameY + (frames[frame + Y] - prevFrameY) * percent - bone.y) * alpha;
 		}
 	}
 
@@ -382,26 +382,53 @@ public class Animation {
 			}
 
 			// Interpolate between the previous frame and the current frame.
-			int frameIndex = binarySearch(frames, time, 3);
-			float prevFrameX = frames[frameIndex - 2];
-			float prevFrameY = frames[frameIndex - 1];
-			float frameTime = frames[frameIndex];
-			float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime), 0, 1);
-			percent = getCurvePercent(frameIndex / 3 - 1, percent);
-
-			bone.scaleX += (bone.data.scaleX * (prevFrameX + (frames[frameIndex + FRAME_X] - prevFrameX) * percent) - bone.scaleX)
-				* alpha;
-			bone.scaleY += (bone.data.scaleY * (prevFrameY + (frames[frameIndex + FRAME_Y] - prevFrameY) * percent) - bone.scaleY)
-				* alpha;
+			int frame = binarySearch(frames, time, 3);
+			float prevFrameX = frames[frame - 2];
+			float prevFrameY = frames[frame - 1];
+			float frameTime = frames[frame];
+			float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime), 0, 1);
+			percent = getCurvePercent(frame / 3 - 1, percent);
+
+			bone.scaleX += (bone.data.scaleX * (prevFrameX + (frames[frame + X] - prevFrameX) * percent) - bone.scaleX) * alpha;
+			bone.scaleY += (bone.data.scaleY * (prevFrameY + (frames[frame + Y] - prevFrameY) * percent) - bone.scaleY) * alpha;
+		}
+	}
+
+	static public class ShearTimeline extends TranslateTimeline {
+		public ShearTimeline (int frameCount) {
+			super(frameCount);
+		}
+
+		public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha) {
+			float[] frames = this.frames;
+			if (time < frames[0]) return; // Time is before first frame.
+
+			Bone bone = skeleton.bones.get(boneIndex);
+			if (time >= frames[frames.length - 3]) { // Time is after last frame.
+				bone.shearX += (bone.data.shearX + frames[frames.length - 2] - bone.shearX) * alpha;
+				bone.shearY += (bone.data.shearY + frames[frames.length - 1] - bone.shearY) * alpha;
+				return;
+			}
+
+			// Interpolate between the previous frame and the current frame.
+			int frame = binarySearch(frames, time, 3);
+			float prevFrameX = frames[frame - 2];
+			float prevFrameY = frames[frame - 1];
+			float frameTime = frames[frame];
+			float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime), 0, 1);
+			percent = getCurvePercent(frame / 3 - 1, percent);
+
+			bone.shearX += (bone.data.shearX + (prevFrameX + (frames[frame + X] - prevFrameX) * percent) - bone.shearX) * alpha;
+			bone.shearY += (bone.data.shearY + (prevFrameY + (frames[frame + Y] - prevFrameY) * percent) - bone.shearY) * alpha;
 		}
 	}
 
 	static public class ColorTimeline extends CurveTimeline {
-		static private final int PREV_FRAME_TIME = -5;
-		static private final int FRAME_R = 1;
-		static private final int FRAME_G = 2;
-		static private final int FRAME_B = 3;
-		static private final int FRAME_A = 4;
+		static private final int PREV_TIME = -5;
+		static private final int R = 1;
+		static private final int G = 2;
+		static private final int B = 3;
+		static private final int A = 4;
 
 		int slotIndex;
 		private final float[] frames; // time, r, g, b, a, ...
@@ -438,8 +465,7 @@ public class Animation {
 			if (time < frames[0]) return; // Time is before first frame.
 
 			float r, g, b, a;
-			if (time >= frames[frames.length - 5]) {
-				// Time is after last frame.
+			if (time >= frames[frames.length - 5]) { // Time is after last frame.
 				int i = frames.length - 1;
 				r = frames[i - 3];
 				g = frames[i - 2];
@@ -447,19 +473,19 @@ public class Animation {
 				a = frames[i];
 			} else {
 				// Interpolate between the previous frame and the current frame.
-				int frameIndex = binarySearch(frames, time, 5);
-				float prevFrameR = frames[frameIndex - 4];
-				float prevFrameG = frames[frameIndex - 3];
-				float prevFrameB = frames[frameIndex - 2];
-				float prevFrameA = frames[frameIndex - 1];
-				float frameTime = frames[frameIndex];
-				float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime), 0, 1);
-				percent = getCurvePercent(frameIndex / 5 - 1, percent);
-
-				r = prevFrameR + (frames[frameIndex + FRAME_R] - prevFrameR) * percent;
-				g = prevFrameG + (frames[frameIndex + FRAME_G] - prevFrameG) * percent;
-				b = prevFrameB + (frames[frameIndex + FRAME_B] - prevFrameB) * percent;
-				a = prevFrameA + (frames[frameIndex + FRAME_A] - prevFrameA) * percent;
+				int frame = binarySearch(frames, time, 5);
+				float frameTime = frames[frame];
+				float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime), 0, 1);
+				percent = getCurvePercent(frame / 5 - 1, percent);
+
+				r = frames[frame - 4];
+				g = frames[frame - 3];
+				b = frames[frame - 2];
+				a = frames[frame - 1];
+				r += (frames[frame + R] - r) * percent;
+				g += (frames[frame + G] - g) * percent;
+				b += (frames[frame + B] - b) * percent;
+				a += (frames[frame + A] - a) * percent;
 			}
 			Color color = skeleton.slots.get(slotIndex).color;
 			if (alpha < 1)
@@ -513,10 +539,10 @@ public class Animation {
 			} else if (lastTime > time) //
 				lastTime = -1;
 
-			int frameIndex = (time >= frames[frames.length - 1] ? frames.length : binarySearch(frames, time)) - 1;
-			if (frames[frameIndex] < lastTime) return;
+			int frame = (time >= frames[frames.length - 1] ? frames.length : binarySearch(frames, time)) - 1;
+			if (frames[frame] < lastTime) return;
 
-			String attachmentName = attachmentNames[frameIndex];
+			String attachmentName = attachmentNames[frame];
 			skeleton.slots.get(slotIndex)
 				.setAttachment(attachmentName == null ? null : skeleton.getAttachment(slotIndex, attachmentName));
 		}
@@ -562,19 +588,19 @@ public class Animation {
 				return;
 			if (time < frames[0]) return; // Time is before first frame.
 
-			int frameIndex;
+			int frame;
 			if (lastTime < frames[0])
-				frameIndex = 0;
+				frame = 0;
 			else {
-				frameIndex = binarySearch(frames, lastTime);
-				float frame = frames[frameIndex];
-				while (frameIndex > 0) { // Fire multiple events with the same frame.
-					if (frames[frameIndex - 1] != frame) break;
-					frameIndex--;
+				frame = binarySearch(frames, lastTime);
+				float frameTime = frames[frame];
+				while (frame > 0) { // Fire multiple events with the same frame.
+					if (frames[frame - 1] != frameTime) break;
+					frame--;
 				}
 			}
-			for (; frameIndex < frameCount && time >= frames[frameIndex]; frameIndex++)
-				firedEvents.add(events[frameIndex]);
+			for (; frame < frameCount && time >= frames[frame]; frame++)
+				firedEvents.add(events[frame]);
 		}
 	}
 
@@ -610,15 +636,15 @@ public class Animation {
 			float[] frames = this.frames;
 			if (time < frames[0]) return; // Time is before first frame.
 
-			int frameIndex;
+			int frame;
 			if (time >= frames[frames.length - 1]) // Time is after last frame.
-				frameIndex = frames.length - 1;
+				frame = frames.length - 1;
 			else
-				frameIndex = binarySearch(frames, time) - 1;
+				frame = binarySearch(frames, time) - 1;
 
 			Array<Slot> drawOrder = skeleton.drawOrder;
 			Array<Slot> slots = skeleton.slots;
-			int[] drawOrderToSetupIndex = drawOrders[frameIndex];
+			int[] drawOrderToSetupIndex = drawOrders[frame];
 			if (drawOrderToSetupIndex == null)
 				System.arraycopy(slots.items, 0, drawOrder.items, 0, slots.size);
 			else {
@@ -699,14 +725,13 @@ public class Animation {
 			}
 
 			// Interpolate between the previous frame and the current frame.
-			int frameIndex = binarySearch(frames, time);
-			float frameTime = frames[frameIndex];
-			float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex - 1] - frameTime), 0, 1);
-			percent = getCurvePercent(frameIndex - 1, percent);
-
-			float[] prevVertices = frameVertices[frameIndex - 1];
-			float[] nextVertices = frameVertices[frameIndex];
+			int frame = binarySearch(frames, time);
+			float frameTime = frames[frame];
+			float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frame - 1] - frameTime), 0, 1);
+			percent = getCurvePercent(frame - 1, percent);
 
+			float[] prevVertices = frameVertices[frame - 1];
+			float[] nextVertices = frameVertices[frame];
 			if (alpha < 1) {
 				for (int i = 0; i < vertexCount; i++) {
 					float prev = prevVertices[i];
@@ -722,10 +747,10 @@ public class Animation {
 	}
 
 	static public class IkConstraintTimeline extends CurveTimeline {
-		static private final int PREV_FRAME_TIME = -3;
-		static private final int PREV_FRAME_MIX = -2;
-		static private final int PREV_FRAME_BEND_DIRECTION = -1;
-		static private final int FRAME_MIX = 1;
+		static private final int PREV_TIME = -3;
+		static private final int PREV_MIX = -2;
+		static private final int PREV_BEND_DIRECTION = -1;
+		static private final int MIX = 1;
 
 		int ikConstraintIndex;
 		private final float[] frames; // time, mix, bendDirection, ...
@@ -759,24 +784,97 @@ public class Animation {
 			float[] frames = this.frames;
 			if (time < frames[0]) return; // Time is before first frame.
 
-			IkConstraint ikConstraint = skeleton.ikConstraints.get(ikConstraintIndex);
+			IkConstraint constraint = skeleton.ikConstraints.get(ikConstraintIndex);
 
 			if (time >= frames[frames.length - 3]) { // Time is after last frame.
-				ikConstraint.mix += (frames[frames.length - 2] - ikConstraint.mix) * alpha;
-				ikConstraint.bendDirection = (int)frames[frames.length - 1];
+				constraint.mix += (frames[frames.length + PREV_MIX] - constraint.mix) * alpha;
+				constraint.bendDirection = (int)frames[frames.length + PREV_BEND_DIRECTION];
+				return;
+			}
+
+			// Interpolate between the previous frame and the current frame.
+			int frame = binarySearch(frames, time, 3);
+			float frameTime = frames[frame];
+			float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime), 0, 1);
+			percent = getCurvePercent(frame / 3 - 1, percent);
+
+			float mix = frames[frame + PREV_MIX];
+			constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha;
+			constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION];
+		}
+	}
+
+	static public class TransformConstraintTimeline extends CurveTimeline {
+		static private final int PREV_TIME = -5;
+		static private final int PREV_ROTATE_MIX = -4;
+		static private final int PREV_TRANSLATE_MIX = -3;
+		static private final int PREV_SCALE_MIX = -2;
+		static private final int PREV_SHEAR_MIX = -1;
+		static private final int ROTATE_MIX = 1;
+		static private final int TRANSLATE_MIX = 2;
+		static private final int SCALE_MIX = 3;
+		static private final int SHEAR_MIX = 4;
+
+		int transformConstraintIndex;
+		private final float[] frames; // time, rotate mix, translate mix, scale mix, shear mix, ...
+
+		public TransformConstraintTimeline (int frameCount) {
+			super(frameCount);
+			frames = new float[frameCount * 5];
+		}
+
+		public void setTransformConstraintIndex (int ikConstraint) {
+			this.transformConstraintIndex = ikConstraint;
+		}
+
+		public int getTransformConstraintIndex () {
+			return transformConstraintIndex;
+		}
+
+		public float[] getFrames () {
+			return frames;
+		}
+
+		/** Sets the time and mixes of the specified keyframe. */
+		public void setFrame (int frameIndex, float time, float rotateMix, float translateMix, float scaleMix, float shearMix) {
+			frameIndex *= 5;
+			frames[frameIndex] = time;
+			frames[frameIndex + 1] = rotateMix;
+			frames[frameIndex + 2] = translateMix;
+			frames[frameIndex + 3] = scaleMix;
+			frames[frameIndex + 4] = shearMix;
+		}
+
+		public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha) {
+			float[] frames = this.frames;
+			if (time < frames[0]) return; // Time is before first frame.
+
+			TransformConstraint constraint = skeleton.transformConstraints.get(transformConstraintIndex);
+
+			if (time >= frames[frames.length - 5]) { // Time is after last frame.
+				int i = frames.length - 1;
+				constraint.rotateMix += (frames[i - 3] - constraint.rotateMix) * alpha;
+				constraint.translateMix += (frames[i - 2] - constraint.translateMix) * alpha;
+				constraint.scaleMix += (frames[i - 1] - constraint.scaleMix) * alpha;
+				constraint.shearMix += (frames[i] - constraint.shearMix) * alpha;
 				return;
 			}
 
 			// Interpolate between the previous frame and the current frame.
-			int frameIndex = binarySearch(frames, time, 3);
-			float prevFrameMix = frames[frameIndex + PREV_FRAME_MIX];
-			float frameTime = frames[frameIndex];
-			float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime), 0, 1);
-			percent = getCurvePercent(frameIndex / 3 - 1, percent);
-
-			float mix = prevFrameMix + (frames[frameIndex + FRAME_MIX] - prevFrameMix) * percent;
-			ikConstraint.mix += (mix - ikConstraint.mix) * alpha;
-			ikConstraint.bendDirection = (int)frames[frameIndex + PREV_FRAME_BEND_DIRECTION];
+			int frame = binarySearch(frames, time, 5);
+			float frameTime = frames[frame];
+			float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime), 0, 1);
+			percent = getCurvePercent(frame / 5 - 1, percent);
+
+			float rotate = frames[frame + PREV_ROTATE_MIX];
+			float translate = frames[frame + PREV_TRANSLATE_MIX];
+			float scale = frames[frame + PREV_SCALE_MIX];
+			float shear = frames[frame + PREV_SHEAR_MIX];
+			constraint.rotateMix += (rotate + (frames[frame + ROTATE_MIX] - rotate) * percent - constraint.rotateMix) * alpha;
+			constraint.translateMix += (translate + (frames[frame + TRANSLATE_MIX] - translate) * percent - constraint.translateMix)
+				* alpha;
+			constraint.scaleMix += (scale + (frames[frame + SCALE_MIX] - scale) * percent - constraint.scaleMix) * alpha;
+			constraint.shearMix += (shear + (frames[frame + SHEAR_MIX] - shear) * percent - constraint.shearMix) * alpha;
 		}
 	}
 }

+ 35 - 17
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java

@@ -31,9 +31,9 @@
 
 package com.esotericsoftware.spine;
 
+import static com.badlogic.gdx.math.MathUtils.*;
 import static com.badlogic.gdx.math.Matrix3.*;
 
-import com.badlogic.gdx.math.MathUtils;
 import com.badlogic.gdx.math.Matrix3;
 import com.badlogic.gdx.math.Vector2;
 
@@ -41,7 +41,7 @@ public class Bone implements Updatable {
 	final BoneData data;
 	final Skeleton skeleton;
 	final Bone parent;
-	float x, y, rotation, scaleX, scaleY;
+	float x, y, rotation, scaleX, scaleY, shearX, shearY;
 	float appliedRotation, appliedScaleX, appliedScaleY;
 
 	float a, b, worldX;
@@ -76,26 +76,30 @@ public class Bone implements Updatable {
 		rotation = bone.rotation;
 		scaleX = bone.scaleX;
 		scaleY = bone.scaleY;
+		shearX = bone.shearX;
+		shearY = bone.shearY;
 	}
 
 	/** Same as {@link #updateWorldTransform()}. This method exists for Bone to implement {@link Updatable}. */
 	public void update () {
-		updateWorldTransform(x, y, rotation, scaleX, scaleY);
+		updateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY);
 	}
 
 	/** Computes the world SRT using the parent bone and this bone's local SRT. */
 	public void updateWorldTransform () {
-		updateWorldTransform(x, y, rotation, scaleX, scaleY);
+		updateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY);
 	}
 
 	/** Computes the world SRT using the parent bone and the specified local SRT. */
-	public void updateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY) {
+	public void updateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) {
 		appliedRotation = rotation;
 		appliedScaleX = scaleX;
 		appliedScaleY = scaleY;
 
-		float cos = MathUtils.cosDeg(rotation), sin = MathUtils.sinDeg(rotation);
-		float la = cos * scaleX, lb = -sin * scaleY, lc = sin * scaleX, ld = cos * scaleY;
+		float rotationY = rotation + 90 + shearY;
+		float la = cosDeg(rotation + shearX) * scaleX, lb = cosDeg(rotationY) * scaleY;
+		float lc = sinDeg(rotation + shearX) * scaleX, ld = sinDeg(rotationY) * scaleY;
+
 		Bone parent = this.parent;
 		if (parent == null) { // Root bone.
 			Skeleton skeleton = this.skeleton;
@@ -138,8 +142,7 @@ public class Bone implements Updatable {
 				pc = 0;
 				pd = 1;
 				do {
-					cos = MathUtils.cosDeg(parent.appliedRotation);
-					sin = MathUtils.sinDeg(parent.appliedRotation);
+					float cos = cosDeg(parent.appliedRotation), sin = sinDeg(parent.appliedRotation);
 					float temp = pa * cos + pb * sin;
 					pb = pa * -sin + pb * cos;
 					pa = temp;
@@ -160,9 +163,7 @@ public class Bone implements Updatable {
 				pc = 0;
 				pd = 1;
 				do {
-					float r = parent.appliedRotation;
-					cos = MathUtils.cosDeg(r);
-					sin = MathUtils.sinDeg(r);
+					float r = parent.appliedRotation, cos = cosDeg(r), sin = sinDeg(r);
 					float psx = parent.appliedScaleX, psy = parent.appliedScaleY;
 					float za = cos * psx, zb = -sin * psy, zc = sin * psx, zd = cos * psy;
 					float temp = pa * za + pb * zc;
@@ -173,8 +174,8 @@ public class Bone implements Updatable {
 					pc = temp;
 
 					if (psx < 0) r = -r;
-					cos = MathUtils.cosDeg(-r);
-					sin = MathUtils.sinDeg(-r);
+					cos = cosDeg(-r);
+					sin = sinDeg(-r);
 					temp = pa * cos + pb * sin;
 					pb = pa * -sin + pb * cos;
 					pa = temp;
@@ -213,6 +214,8 @@ public class Bone implements Updatable {
 		rotation = data.rotation;
 		scaleX = data.scaleX;
 		scaleY = data.scaleY;
+		shearX = data.shearX;
+		shearY = data.shearY;
 	}
 
 	public BoneData getData () {
@@ -248,7 +251,6 @@ public class Bone implements Updatable {
 		this.y = y;
 	}
 
-	/** Returns the forward kinetics rotation. */
 	public float getRotation () {
 		return rotation;
 	}
@@ -283,6 +285,22 @@ public class Bone implements Updatable {
 		scaleY = scale;
 	}
 
+	public float getShearX () {
+		return shearX;
+	}
+
+	public void setShearX (float shearX) {
+		this.shearX = shearX;
+	}
+
+	public float getShearY () {
+		return shearY;
+	}
+
+	public void setShearY (float shearY) {
+		this.shearY = shearY;
+	}
+
 	public float getA () {
 		return a;
 	}
@@ -316,11 +334,11 @@ public class Bone implements Updatable {
 	}
 
 	public float getWorldRotationX () {
-		return MathUtils.atan2(c, a) * MathUtils.radDeg;
+		return atan2(c, a) * radDeg;
 	}
 
 	public float getWorldRotationY () {
-		return MathUtils.atan2(d, b) * MathUtils.radDeg;
+		return atan2(d, b) * radDeg;
 	}
 
 	public float getWorldScaleX () {

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

@@ -37,9 +37,7 @@ public class BoneData {
 	final BoneData parent;
 	final String name;
 	float length;
-	float x, y;
-	float rotation;
-	float scaleX = 1, scaleY = 1;
+	float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY;
 	boolean inheritScale = true, inheritRotation = true;
 
 	// Nonessential.
@@ -64,6 +62,8 @@ public class BoneData {
 		rotation = bone.rotation;
 		scaleX = bone.scaleX;
 		scaleY = bone.scaleY;
+		shearX = bone.shearX;
+		shearY = bone.shearY;
 	}
 
 	/** @return May be null. */
@@ -133,6 +133,22 @@ public class BoneData {
 		this.scaleY = scaleY;
 	}
 
+	public float getShearX () {
+		return shearX;
+	}
+
+	public void setShearX (float shearX) {
+		this.shearX = shearX;
+	}
+
+	public float getShearY () {
+		return shearY;
+	}
+
+	public void setShearY (float shearY) {
+		this.shearY = shearY;
+	}
+
 	public boolean getInheritScale () {
 		return inheritScale;
 	}

+ 5 - 3
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java

@@ -130,7 +130,7 @@ public class IkConstraint implements Updatable {
 			rotationIK -= 360;
 		else if (rotationIK < -180) rotationIK += 360;
 		bone.updateWorldTransform(bone.x, bone.y, rotation + (rotationIK - rotation) * alpha, bone.appliedScaleX,
-			bone.appliedScaleY);
+			bone.appliedScaleY, bone.shearX, bone.shearY);
 	}
 
 	/** Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as possible. The
@@ -259,8 +259,10 @@ public class IkConstraint implements Updatable {
 			a2 -= 360;
 		else if (a2 < -180) a2 += 360;
 		float rotation = parent.rotation;
-		parent.updateWorldTransform(px, py, rotation + (a1 - rotation) * alpha, parent.appliedScaleX, parent.appliedScaleY);
+		parent.updateWorldTransform(px, py, rotation + (a1 - rotation) * alpha, parent.appliedScaleX, parent.appliedScaleY,
+			parent.shearX, parent.shearY);
 		rotation = child.rotation;
-		child.updateWorldTransform(cx, cy, rotation + (a2 - rotation) * alpha, child.appliedScaleX, child.appliedScaleY);
+		child.updateWorldTransform(cx, cy, rotation + (a2 - rotation) * alpha, child.appliedScaleX, child.appliedScaleY,
+			child.shearX, child.shearY);
 	}
 }

+ 6 - 5
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java

@@ -148,8 +148,7 @@ public class Skeleton {
 		for (int i = 0; i < transformConstraintsCount; i++) {
 			TransformConstraint transformConstraint = transformConstraints.get(i);
 			for (int ii = updateCache.size - 1; ii >= 0; ii--) {
-				Updatable object = updateCache.get(ii);
-				if (object == transformConstraint.bone || object == transformConstraint.target) {
+				if (updateCache.get(ii) == transformConstraint.bone) {
 					updateCache.insert(ii + 1, transformConstraint);
 					break;
 				}
@@ -186,9 +185,11 @@ public class Skeleton {
 		Array<TransformConstraint> transformConstraints = this.transformConstraints;
 		for (int i = 0, n = transformConstraints.size; i < n; i++) {
 			TransformConstraint constraint = transformConstraints.get(i);
-			constraint.translateMix = constraint.data.translateMix;
-			constraint.x = constraint.data.x;
-			constraint.y = constraint.data.y;
+			TransformConstraintData data = constraint.data;
+			constraint.rotateMix = data.rotateMix;
+			constraint.translateMix = data.translateMix;
+			constraint.scaleMix = data.scaleMix;
+			constraint.translateMix = data.translateMix;
 		}
 	}
 

+ 44 - 14
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java

@@ -50,7 +50,9 @@ import com.esotericsoftware.spine.Animation.FfdTimeline;
 import com.esotericsoftware.spine.Animation.IkConstraintTimeline;
 import com.esotericsoftware.spine.Animation.RotateTimeline;
 import com.esotericsoftware.spine.Animation.ScaleTimeline;
+import com.esotericsoftware.spine.Animation.ShearTimeline;
 import com.esotericsoftware.spine.Animation.Timeline;
+import com.esotericsoftware.spine.Animation.TransformConstraintTimeline;
 import com.esotericsoftware.spine.Animation.TranslateTimeline;
 import com.esotericsoftware.spine.SkeletonJson.LinkedMesh;
 import com.esotericsoftware.spine.attachments.AtlasAttachmentLoader;
@@ -63,11 +65,12 @@ import com.esotericsoftware.spine.attachments.RegionAttachment;
 import com.esotericsoftware.spine.attachments.WeightedMeshAttachment;
 
 public class SkeletonBinary {
-	static public final int TIMELINE_SCALE = 0;
-	static public final int TIMELINE_ROTATE = 1;
-	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_ROTATE = 0;
+	static public final int TIMELINE_TRANSLATE = 1;
+	static public final int TIMELINE_SCALE = 2;
+	static public final int TIMELINE_SHEAR = 3;
+	static public final int TIMELINE_ATTACHMENT = 4;
+	static public final int TIMELINE_COLOR = 5;
 
 	static public final int CURVE_LINEAR = 0;
 	static public final int CURVE_STEPPED = 1;
@@ -159,14 +162,16 @@ public class SkeletonBinary {
 				String name = input.readString();
 				BoneData parent = i == 0 ? null : skeletonData.bones.get(input.readInt(true));
 				BoneData boneData = new BoneData(name, parent);
+				boneData.rotation = input.readFloat();
 				boneData.x = input.readFloat() * scale;
 				boneData.y = input.readFloat() * scale;
 				boneData.scaleX = input.readFloat();
 				boneData.scaleY = input.readFloat();
-				boneData.rotation = input.readFloat();
+				boneData.shearX = input.readFloat();
+				boneData.shearY = input.readFloat();
 				boneData.length = input.readFloat() * scale;
-				boneData.inheritScale = input.readBoolean();
 				boneData.inheritRotation = input.readBoolean();
+				boneData.inheritScale = input.readBoolean();
 				if (nonessential) Color.rgba8888ToColor(boneData.color, input.readInt());
 				skeletonData.bones.add(boneData);
 			}
@@ -187,9 +192,16 @@ public class SkeletonBinary {
 				TransformConstraintData transformConstraintData = new TransformConstraintData(input.readString());
 				transformConstraintData.bone = skeletonData.bones.get(input.readInt(true));
 				transformConstraintData.target = skeletonData.bones.get(input.readInt(true));
+				transformConstraintData.offsetRotation = input.readFloat();
+				transformConstraintData.offsetX = input.readFloat();
+				transformConstraintData.offsetY = input.readFloat();
+				transformConstraintData.offsetScaleX = input.readFloat();
+				transformConstraintData.offsetScaleY = input.readFloat();
+				transformConstraintData.offsetShearY = input.readFloat();
+				transformConstraintData.rotateMix = input.readFloat();
 				transformConstraintData.translateMix = input.readFloat();
-				transformConstraintData.x = input.readFloat();
-				transformConstraintData.y = input.readFloat();
+				transformConstraintData.scaleMix = input.readFloat();
+				transformConstraintData.shearMix = input.readFloat();
 				skeletonData.transformConstraints.add(transformConstraintData);
 			}
 
@@ -291,11 +303,11 @@ public class SkeletonBinary {
 		switch (type) {
 		case region: {
 			String path = input.readString();
+			float rotation = input.readFloat();
 			float x = input.readFloat();
 			float y = input.readFloat();
 			float scaleX = input.readFloat();
 			float scaleY = input.readFloat();
-			float rotation = input.readFloat();
 			float width = input.readFloat();
 			float height = input.readFloat();
 			int color = input.readInt();
@@ -532,11 +544,14 @@ public class SkeletonBinary {
 						break;
 					}
 					case TIMELINE_TRANSLATE:
-					case TIMELINE_SCALE: {
+					case TIMELINE_SCALE:
+					case TIMELINE_SHEAR: {
 						TranslateTimeline timeline;
 						float timelineScale = 1;
 						if (timelineType == TIMELINE_SCALE)
 							timeline = new ScaleTimeline(frameCount);
+						else if (timelineType == TIMELINE_SHEAR)
+							timeline = new ShearTimeline(frameCount);
 						else {
 							timeline = new TranslateTimeline(frameCount);
 							timelineScale = scale;
@@ -555,12 +570,12 @@ public class SkeletonBinary {
 				}
 			}
 
-			// IK timelines.
+			// IK constraint timelines.
 			for (int i = 0, n = input.readInt(true); i < n; i++) {
-				IkConstraintData ikConstraint = skeletonData.ikConstraints.get(input.readInt(true));
+				IkConstraintData constraint = skeletonData.ikConstraints.get(input.readInt(true));
 				int frameCount = input.readInt(true);
 				IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount);
-				timeline.ikConstraintIndex = skeletonData.getIkConstraints().indexOf(ikConstraint, true);
+				timeline.ikConstraintIndex = skeletonData.getIkConstraints().indexOf(constraint, true);
 				for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
 					timeline.setFrame(frameIndex, input.readFloat(), input.readFloat(), input.readByte());
 					if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
@@ -569,6 +584,21 @@ public class SkeletonBinary {
 				duration = Math.max(duration, timeline.getFrames()[frameCount * 3 - 3]);
 			}
 
+			// Transform constraint timelines.
+			for (int i = 0, n = input.readInt(true); i < n; i++) {
+				TransformConstraintData constraint = skeletonData.transformConstraints.get(input.readInt(true));
+				int frameCount = input.readInt(true);
+				TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount);
+				timeline.transformConstraintIndex = skeletonData.getTransformConstraints().indexOf(constraint, true);
+				for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
+					timeline.setFrame(frameIndex, input.readFloat(), input.readFloat(), input.readFloat(), input.readFloat(),
+						input.readFloat());
+					if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
+				}
+				timelines.add(timeline);
+				duration = Math.max(duration, timeline.getFrames()[frameCount * 5 - 5]);
+			}
+
 			// FFD timelines.
 			for (int i = 0, n = input.readInt(true); i < n; i++) {
 				Skin skin = skeletonData.skins.get(input.readInt(true));

+ 40 - 10
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java

@@ -49,7 +49,9 @@ import com.esotericsoftware.spine.Animation.FfdTimeline;
 import com.esotericsoftware.spine.Animation.IkConstraintTimeline;
 import com.esotericsoftware.spine.Animation.RotateTimeline;
 import com.esotericsoftware.spine.Animation.ScaleTimeline;
+import com.esotericsoftware.spine.Animation.ShearTimeline;
 import com.esotericsoftware.spine.Animation.Timeline;
+import com.esotericsoftware.spine.Animation.TransformConstraintTimeline;
 import com.esotericsoftware.spine.Animation.TranslateTimeline;
 import com.esotericsoftware.spine.attachments.AtlasAttachmentLoader;
 import com.esotericsoftware.spine.attachments.Attachment;
@@ -117,6 +119,8 @@ public class SkeletonJson {
 			boneData.rotation = boneMap.getFloat("rotation", 0);
 			boneData.scaleX = boneMap.getFloat("scaleX", 1);
 			boneData.scaleY = boneMap.getFloat("scaleY", 1);
+			boneData.shearX = boneMap.getFloat("shearX", 0);
+			boneData.shearY = boneMap.getFloat("shearY", 0);
 			boneData.inheritScale = boneMap.getBoolean("inheritScale", true);
 			boneData.inheritRotation = boneMap.getBoolean("inheritRotation", true);
 
@@ -159,9 +163,17 @@ public class SkeletonJson {
 			transformConstraintData.target = skeletonData.findBone(targetName);
 			if (transformConstraintData.target == null) throw new SerializationException("Target bone not found: " + targetName);
 
+			transformConstraintData.offsetRotation = transformMap.getFloat("rotation", 0);
+			transformConstraintData.offsetX = transformMap.getFloat("x", 0) * scale;
+			transformConstraintData.offsetY = transformMap.getFloat("y", 0) * scale;
+			transformConstraintData.offsetScaleX = transformMap.getFloat("scaleX", 0) * scale;
+			transformConstraintData.offsetScaleY = transformMap.getFloat("scaleY", 0) * scale;
+			transformConstraintData.offsetShearY = transformMap.getFloat("shearY", 0) * scale;
+
+			transformConstraintData.rotateMix = transformMap.getFloat("rotateMix", 1);
 			transformConstraintData.translateMix = transformMap.getFloat("translateMix", 1);
-			transformConstraintData.x = transformMap.getFloat("x", 0) * scale;
-			transformConstraintData.y = transformMap.getFloat("y", 0) * scale;
+			transformConstraintData.scaleMix = transformMap.getFloat("scaleMix", 1);
+			transformConstraintData.shearMix = transformMap.getFloat("shearMix", 1);
 
 			skeletonData.transformConstraints.add(transformConstraintData);
 		}
@@ -422,11 +434,13 @@ public class SkeletonJson {
 					timelines.add(timeline);
 					duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() * 2 - 2]);
 
-				} else if (timelineName.equals("translate") || timelineName.equals("scale")) {
+				} else if (timelineName.equals("translate") || timelineName.equals("scale") || timelineName.equals("shear")) {
 					TranslateTimeline timeline;
 					float timelineScale = 1;
 					if (timelineName.equals("scale"))
 						timeline = new ScaleTimeline(timelineMap.size);
+					else if (timelineName.equals("shear"))
+						timeline = new ShearTimeline(timelineMap.size);
 					else {
 						timeline = new TranslateTimeline(timelineMap.size);
 						timelineScale = scale;
@@ -448,14 +462,14 @@ public class SkeletonJson {
 			}
 		}
 
-		// IK timelines.
-		for (JsonValue ikMap = map.getChild("ik"); ikMap != null; ikMap = ikMap.next) {
-			IkConstraintData ikConstraint = skeletonData.findIkConstraint(ikMap.name);
-			IkConstraintTimeline timeline = new IkConstraintTimeline(ikMap.size);
-			timeline.ikConstraintIndex = skeletonData.getIkConstraints().indexOf(ikConstraint, true);
+		// IK constraint timelines.
+		for (JsonValue constraintMap = map.getChild("ik"); constraintMap != null; constraintMap = constraintMap.next) {
+			IkConstraintData constraint = skeletonData.findIkConstraint(constraintMap.name);
+			IkConstraintTimeline timeline = new IkConstraintTimeline(constraintMap.size);
+			timeline.ikConstraintIndex = skeletonData.getIkConstraints().indexOf(constraint, true);
 			int frameIndex = 0;
-			for (JsonValue valueMap = ikMap.child; valueMap != null; valueMap = valueMap.next) {
-				timeline.setFrame(frameIndex, valueMap.getFloat("time"), valueMap.getFloat("mix"),
+			for (JsonValue valueMap = constraintMap.child; valueMap != null; valueMap = valueMap.next) {
+				timeline.setFrame(frameIndex, valueMap.getFloat("time"), valueMap.getFloat("mix", 1),
 					valueMap.getBoolean("bendPositive") ? 1 : -1);
 				readCurve(timeline, frameIndex, valueMap);
 				frameIndex++;
@@ -464,6 +478,22 @@ public class SkeletonJson {
 			duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() * 3 - 3]);
 		}
 
+		// Transform constraint timelines.
+		for (JsonValue constraintMap = map.getChild("transform"); constraintMap != null; constraintMap = constraintMap.next) {
+			TransformConstraintData constraint = skeletonData.findTransformConstraint(constraintMap.name);
+			TransformConstraintTimeline timeline = new TransformConstraintTimeline(constraintMap.size);
+			timeline.transformConstraintIndex = skeletonData.getTransformConstraints().indexOf(constraint, true);
+			int frameIndex = 0;
+			for (JsonValue valueMap = constraintMap.child; valueMap != null; valueMap = valueMap.next) {
+				timeline.setFrame(frameIndex, valueMap.getFloat("time"), valueMap.getFloat("rotateMix", 1),
+					valueMap.getFloat("translateMix", 1), valueMap.getFloat("scaleMix", 1), valueMap.getFloat("shearMix", 1));
+				readCurve(timeline, frameIndex, valueMap);
+				frameIndex++;
+			}
+			timelines.add(timeline);
+			duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() * 5 - 5]);
+		}
+
 		// FFD timelines.
 		for (JsonValue ffdMap = map.getChild("ffd"); ffdMap != null; ffdMap = ffdMap.next) {
 			Skin skin = skeletonData.findSkin(ffdMap.name);

+ 1 - 0
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonMeshRenderer.java

@@ -84,6 +84,7 @@ public class SkeletonMeshRenderer extends SkeletonRenderer<PolygonSpriteBatch> {
 				attachmentSkeleton.setPosition(skeleton.getX() + bone.getWorldX(), skeleton.getY() + bone.getWorldY());
 				// rootBone.setScaleX(1 + bone.getWorldScaleX() - oldScaleX);
 				// rootBone.setScaleY(1 + bone.getWorldScaleY() - oldScaleY);
+				// Set shear.
 				rootBone.setRotation(oldRotation + bone.getWorldRotationX());
 				attachmentSkeleton.updateWorldTransform();
 

+ 1 - 0
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java

@@ -78,6 +78,7 @@ public class SkeletonRenderer<T extends Batch> {
 				attachmentSkeleton.setPosition(skeleton.getX() + bone.getWorldX(), skeleton.getY() + bone.getWorldY());
 				// rootBone.setScaleX(1 + bone.getWorldScaleX() - oldScaleX);
 				// rootBone.setScaleY(1 + bone.getWorldScaleY() - oldScaleY);
+				// Set shear.
 				rootBone.setRotation(oldRotation + bone.getWorldRotationX());
 				attachmentSkeleton.updateWorldTransform();
 

+ 130 - 7
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraint.java

@@ -1,19 +1,25 @@
 
 package com.esotericsoftware.spine;
 
+import static com.badlogic.gdx.math.MathUtils.*;
+
 import com.badlogic.gdx.math.Vector2;
 
 public class TransformConstraint implements Updatable {
 	final TransformConstraintData data;
 	Bone bone, target;
-	float translateMix, x, y;
+	float rotateMix, translateMix, scaleMix, shearMix;
+	float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY;
 	final Vector2 temp = new Vector2();
 
 	public TransformConstraint (TransformConstraintData data, Skeleton skeleton) {
 		this.data = data;
 		translateMix = data.translateMix;
-		x = data.x;
-		y = data.y;
+		rotateMix = data.rotateMix;
+		scaleMix = data.scaleMix;
+		shearMix = data.shearMix;
+		offsetX = data.offsetX;
+		offsetY = data.offsetY;
 
 		if (skeleton != null) {
 			bone = skeleton.findBone(data.bone.name);
@@ -27,8 +33,11 @@ public class TransformConstraint implements Updatable {
 		bone = skeleton.bones.get(constraint.bone.skeleton.bones.indexOf(constraint.bone, true));
 		target = skeleton.bones.get(constraint.target.skeleton.bones.indexOf(constraint.target, true));
 		translateMix = constraint.translateMix;
-		x = constraint.x;
-		y = constraint.y;
+		rotateMix = constraint.rotateMix;
+		scaleMix = constraint.scaleMix;
+		shearMix = constraint.shearMix;
+		offsetX = constraint.offsetX;
+		offsetY = constraint.offsetY;
 	}
 
 	public void apply () {
@@ -36,11 +45,53 @@ public class TransformConstraint implements Updatable {
 	}
 
 	public void update () {
+		Bone bone = this.bone;
+		Bone target = this.target;
+
+		if (rotateMix > 0) {
+			float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
+			float r = atan2(target.c, target.a) - atan2(c, a) + offsetRotation * degRad;
+			if (r > PI)
+				r -= PI2;
+			else if (r < -PI) r += PI2;
+			r *= rotateMix;
+			float cos = cos(r), sin = sin(r);
+			bone.a = cos * a - sin * c;
+			bone.b = cos * b - sin * d;
+			bone.c = sin * a + cos * c;
+			bone.d = sin * b + cos * d;
+		}
+
+		if (scaleMix > 0) {
+			float bs = (float)Math.sqrt(bone.a * bone.a + bone.c * bone.c);
+			float ts = (float)Math.sqrt(target.a * target.a + target.c * target.c);
+			float s = (bs > 0.00001f ? (bs + (ts - bs) * scaleMix) / bs : 0) + offsetScaleX;
+			bone.a *= s;
+			bone.c *= s;
+			bs = (float)Math.sqrt(bone.b * bone.b + bone.d * bone.d);
+			ts = (float)Math.sqrt(target.b * target.b + target.d * target.d);
+			s = (bs > 0.00001f ? (bs + (ts - bs) * scaleMix) / bs : 0) + offsetScaleY;
+			bone.b *= s;
+			bone.d *= s;
+		}
+
+		if (shearMix > 0) {
+			float b = bone.b, d = bone.d;
+			float by = atan2(d, b);
+			float r = (atan2(target.d, target.b) - atan2(target.c, target.a)) - (by - atan2(bone.c, bone.a));
+			if (r > PI)
+				r -= PI2;
+			else if (r < -PI) r += PI2;
+			r = by + r * shearMix;
+			float s = (float)Math.sqrt(b * b + d * d);
+			bone.b = cos(r + offsetShearY * degRad) * s;
+			bone.d = sin(r + offsetShearY * degRad) * s;
+		}
+
 		float translateMix = this.translateMix;
 		if (translateMix > 0) {
 			Vector2 temp = this.temp;
-			target.localToWorld(temp.set(x, y));
-			Bone bone = this.bone;
+			target.localToWorld(temp.set(offsetX, offsetY));
 			bone.worldX += (temp.x - bone.worldX) * translateMix;
 			bone.worldY += (temp.y - bone.worldY) * translateMix;
 		}
@@ -62,6 +113,14 @@ public class TransformConstraint implements Updatable {
 		this.target = target;
 	}
 
+	public float getRotateMix () {
+		return rotateMix;
+	}
+
+	public void setRotateMix (float rotateMix) {
+		this.rotateMix = rotateMix;
+	}
+
 	public float getTranslateMix () {
 		return translateMix;
 	}
@@ -70,6 +129,70 @@ public class TransformConstraint implements Updatable {
 		this.translateMix = translateMix;
 	}
 
+	public float getScaleMix () {
+		return scaleMix;
+	}
+
+	public void setScaleMix (float scaleMix) {
+		this.scaleMix = scaleMix;
+	}
+
+	public float getShearMix () {
+		return shearMix;
+	}
+
+	public void setShearMix (float shearMix) {
+		this.shearMix = shearMix;
+	}
+
+	public float getOffsetRotation () {
+		return offsetRotation;
+	}
+
+	public void setOffsetRotation (float offsetRotation) {
+		this.offsetRotation = offsetRotation;
+	}
+
+	public float getOffsetX () {
+		return offsetX;
+	}
+
+	public void setOffsetX (float offsetX) {
+		this.offsetX = offsetX;
+	}
+
+	public float getOffsetY () {
+		return offsetY;
+	}
+
+	public void setOffsetY (float offsetY) {
+		this.offsetY = offsetY;
+	}
+
+	public float getOffsetScaleX () {
+		return offsetScaleX;
+	}
+
+	public void setOffsetScaleX (float offsetScaleX) {
+		this.offsetScaleX = offsetScaleX;
+	}
+
+	public float getOffsetScaleY () {
+		return offsetScaleY;
+	}
+
+	public void setOffsetScaleY (float offsetScaleY) {
+		this.offsetScaleY = offsetScaleY;
+	}
+
+	public float getOffsetShearY () {
+		return offsetShearY;
+	}
+
+	public void setOffsetShearY (float offsetShearY) {
+		this.offsetShearY = offsetShearY;
+	}
+
 	public TransformConstraintData getData () {
 		return data;
 	}

+ 66 - 10
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraintData.java

@@ -4,8 +4,8 @@ package com.esotericsoftware.spine;
 public class TransformConstraintData {
 	final String name;
 	BoneData bone, target;
-	float translateMix;
-	float x, y;
+	float rotateMix, translateMix, scaleMix, shearMix;
+	float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY;
 
 	public TransformConstraintData (String name) {
 		this.name = name;
@@ -31,6 +31,14 @@ public class TransformConstraintData {
 		this.target = target;
 	}
 
+	public float getRotateMix () {
+		return rotateMix;
+	}
+
+	public void setRotateMix (float rotateMix) {
+		this.rotateMix = rotateMix;
+	}
+
 	public float getTranslateMix () {
 		return translateMix;
 	}
@@ -39,20 +47,68 @@ public class TransformConstraintData {
 		this.translateMix = translateMix;
 	}
 
-	public float getX () {
-		return x;
+	public float getScaleMix () {
+		return scaleMix;
+	}
+
+	public void setScaleMix (float scaleMix) {
+		this.scaleMix = scaleMix;
+	}
+
+	public float getShearMix () {
+		return shearMix;
+	}
+
+	public void setShearMix (float shearMix) {
+		this.shearMix = shearMix;
+	}
+
+	public float getOffsetRotation () {
+		return offsetRotation;
+	}
+
+	public void setOffsetRotation (float offsetRotation) {
+		this.offsetRotation = offsetRotation;
+	}
+
+	public float getOffsetX () {
+		return offsetX;
+	}
+
+	public void setOffsetX (float offsetX) {
+		this.offsetX = offsetX;
+	}
+
+	public float getOffsetY () {
+		return offsetY;
+	}
+
+	public void setOffsetY (float offsetY) {
+		this.offsetY = offsetY;
+	}
+
+	public float getOffsetScaleX () {
+		return offsetScaleX;
+	}
+
+	public void setOffsetScaleX (float offsetScaleX) {
+		this.offsetScaleX = offsetScaleX;
+	}
+
+	public float getOffsetScaleY () {
+		return offsetScaleY;
 	}
 
-	public void setX (float x) {
-		this.x = x;
+	public void setOffsetScaleY (float offsetScaleY) {
+		this.offsetScaleY = offsetScaleY;
 	}
 
-	public float getY () {
-		return y;
+	public float getOffsetShearY () {
+		return offsetShearY;
 	}
 
-	public void setY (float y) {
-		this.y = y;
+	public void setOffsetShearY (float offsetShearY) {
+		this.offsetShearY = offsetShearY;
 	}
 
 	public String toString () {

+ 1 - 1
spine-love/README.md

@@ -10,7 +10,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f
 
 ## Spine version
 
-spine-love works with data exported from Spine 2.1.27. Updating spine-love to [v3.0](https://trello.com/c/tF8UykBM/72-update-runtimes-to-support-v3-0-skewing-scale) and [v3.1](https://trello.com/c/bERJAFEq/73-update-runtimes-to-support-v3-1-linked-meshes) is in progress.
+spine-love works with data exported from Spine 2.1.27. Updating spine-love to [v3.0](https://trello.com/c/tF8UykBM/72-update-runtimes-to-support-v3-0-skewing-scale), [v3.1](https://trello.com/c/bERJAFEq/73-update-runtimes-to-support-v3-1-linked-meshes), and [v3.2](https://trello.com/c/k7KtGdPW/76-update-runtimes-to-support-v3-2-shearing) is in progress.
 
 spine-love supports all Spine features except for rendering meshes.
 

+ 1 - 1
spine-lua/README.md

@@ -10,7 +10,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f
 
 ## Spine version
 
-spine-lua works with data exported from Spine 2.1.27. Updating spine-lua to [v3.0](https://trello.com/c/tF8UykBM/72-update-runtimes-to-support-v3-0-skewing-scale) and [v3.1](https://trello.com/c/bERJAFEq/73-update-runtimes-to-support-v3-1-linked-meshes) is in progress.
+spine-lua works with data exported from Spine 2.1.27. Updating spine-lua to [v3.0](https://trello.com/c/tF8UykBM/72-update-runtimes-to-support-v3-0-skewing-scale), [v3.1](https://trello.com/c/bERJAFEq/73-update-runtimes-to-support-v3-1-linked-meshes), and [v3.2](https://trello.com/c/k7KtGdPW/76-update-runtimes-to-support-v3-2-shearing) is in progress.
 
 spine-lua supports all Spine features.
 

+ 1 - 1
spine-monogame/README.md

@@ -10,7 +10,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f
 
 ## Spine version
 
-spine-monogame works with data exported from the latest version of Spine.
+spine-monogame works with data exported from Spine 3.1.08. Updating spine-monogame to [v3.2](https://trello.com/c/k7KtGdPW/76-update-runtimes-to-support-v3-2-shearing) is in progress.
 
 spine-monogame supports all Spine features.
 

+ 1 - 1
spine-sfml/README.md

@@ -10,7 +10,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f
 
 ## Spine version
 
-spine-sfml works with data exported from the latest version of Spine.
+spine-sfml works with data exported from Spine 3.1.08. Updating spine-sfml to [v3.2](https://trello.com/c/k7KtGdPW/76-update-runtimes-to-support-v3-2-shearing) is in progress.
 
 spine-sfml supports all Spine features.
 

+ 1 - 1
spine-starling/README.md

@@ -10,7 +10,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f
 
 ## Spine version
 
-spine-starling works with data exported from the latest version of Spine.
+spine-starling works with data exported from Spine 3.1.08. Updating spine-starling to [v3.2](https://trello.com/c/k7KtGdPW/76-update-runtimes-to-support-v3-2-shearing) is in progress.
 
 spine-starling supports all Spine features.
 

+ 1 - 1
spine-threejs/README.md

@@ -10,7 +10,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f
 
 ## Spine version
 
-spine-threejs works with data exported from the latest version of Spine.
+spine-threejs works with data exported from Spine 3.1.08. Updating spine-threejs to [v3.2](https://trello.com/c/k7KtGdPW/76-update-runtimes-to-support-v3-2-shearing) is in progress.
 
 spine-threejs supports all Spine features except for rendering meshes.
 

+ 1 - 1
spine-turbulenz/README.md

@@ -10,7 +10,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f
 
 ## Spine version
 
-spine-turbulenz works with data exported from the latest version of Spine.
+spine-turbulenz works with data exported from Spine 3.1.08. Updating spine-turbulenz to [v3.2](https://trello.com/c/k7KtGdPW/76-update-runtimes-to-support-v3-2-shearing) is in progress.
 
 spine-turbulenz supports all Spine features except for rendering meshes.
 

+ 1 - 1
spine-unity/README.md

@@ -14,7 +14,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f
 
 ## Spine version
 
-spine-unity works with data exported from the latest version of Spine.
+spine-unity works with data exported from Spine 3.1.08. Updating spine-unity to [v3.2](https://trello.com/c/k7KtGdPW/76-update-runtimes-to-support-v3-2-shearing) is in progress.
 
 spine-unity supports all Spine features.