Browse Source

Update spine-csharp to v3.2 (shearing) (#562)

* spine-csharp updated to 3.2.00.

* Some cleanup, catchup and fixes.

* Mix transform constraint scale and shear offsets.

* Update readmes.

* Clean up.

* Formatting.

* Fixed single bone IK with nonuniform scale. Improved two bone IK.
John 9 năm trước cách đây
mục cha
commit
f1406e2f85

+ 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 version
 
 
-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 works with data exported from the latest version of Spine.
 
 
 spine-csharp supports all Spine features.
 spine-csharp supports all Spine features.
 
 

+ 175 - 85
spine-csharp/src/Animation.cs

@@ -209,14 +209,14 @@ namespace Spine {
 	}
 	}
 
 
 	public class RotateTimeline : CurveTimeline {
 	public class RotateTimeline : CurveTimeline {
-		protected const int PREV_FRAME_TIME = -2;
-		protected const int FRAME_VALUE = 1;
+		internal const int PREV_TIME = -2;
+		internal const int VALUE = 1;
 
 
 		internal int boneIndex;
 		internal int boneIndex;
 		internal float[] frames;
 		internal float[] frames;
 
 
 		public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } }
 		public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } }
-		public float[] Frames { get { return frames; } set { frames = value; } } // time, value, ...
+		public float[] Frames { get { return frames; } set { frames = value; } } // time, angle, ...
 
 
 		public RotateTimeline (int frameCount)
 		public RotateTimeline (int frameCount)
 			: base(frameCount) {
 			: base(frameCount) {
@@ -249,13 +249,13 @@ namespace Spine {
 			}
 			}
 
 
 			// Interpolate between the previous frame and the current frame.
 			// Interpolate between the previous frame and the current frame.
-			int frameIndex = Animation.binarySearch(frames, time, 2);
-			float prevFrameValue = frames[frameIndex - 1];
-			float frameTime = frames[frameIndex];
-			float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime);
-			percent = GetCurvePercent((frameIndex >> 1) - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
+			int frame = Animation.binarySearch(frames, time, 2);
+			float prevFrameValue = frames[frame - 1];
+			float frameTime = frames[frame];
+			float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime);
+			percent = GetCurvePercent((frame >> 1) - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
 
 
-			amount = frames[frameIndex + FRAME_VALUE] - prevFrameValue;
+			amount = frames[frame + VALUE] - prevFrameValue;
 			while (amount > 180)
 			while (amount > 180)
 				amount -= 360;
 				amount -= 360;
 			while (amount < -180)
 			while (amount < -180)
@@ -270,9 +270,9 @@ namespace Spine {
 	}
 	}
 
 
 	public class TranslateTimeline : CurveTimeline {
 	public class TranslateTimeline : CurveTimeline {
-		protected const int PREV_FRAME_TIME = -3;
-		protected const int FRAME_X = 1;
-		protected const int FRAME_Y = 2;
+		protected const int PREV_TIME = -3;
+		protected const int X = 1;
+		protected const int Y = 2;
 
 
 		internal int boneIndex;
 		internal int boneIndex;
 		internal float[] frames;
 		internal float[] frames;
@@ -306,15 +306,15 @@ namespace Spine {
 			}
 			}
 
 
 			// Interpolate between the previous frame and the current frame.
 			// Interpolate between the previous frame and the current frame.
-			int frameIndex = Animation.binarySearch(frames, time, 3);
-			float prevFrameX = frames[frameIndex - 2];
-			float prevFrameY = frames[frameIndex - 1];
-			float frameTime = frames[frameIndex];
-			float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime);
-			percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
+			int frame = Animation.binarySearch(frames, time, 3);
+			float prevFrameX = frames[frame - 2];
+			float prevFrameY = frames[frame - 1];
+			float frameTime = frames[frame];
+			float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime);
+			percent = GetCurvePercent(frame / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 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;
 		}
 		}
 	}
 	}
 
 
@@ -335,24 +335,53 @@ namespace Spine {
 			}
 			}
 
 
 			// Interpolate between the previous frame and the current frame.
 			// Interpolate between the previous frame and the current frame.
-			int frameIndex = Animation.binarySearch(frames, time, 3);
-			float prevFrameX = frames[frameIndex - 2];
-			float prevFrameY = frames[frameIndex - 1];
-			float frameTime = frames[frameIndex];
-			float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime);
-			percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
+			int frame = Animation.binarySearch(frames, time, 3);
+			float prevFrameX = frames[frame - 2];
+			float prevFrameY = frames[frame - 1];
+			float frameTime = frames[frame];
+			float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime);
+			percent = GetCurvePercent(frame / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 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;
+			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;
+		}
+	}
+
+	public class ShearTimeline : TranslateTimeline {
+		public ShearTimeline (int frameCount)
+			: base (frameCount) {
+		}
+
+		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
+			float[] frames = this.frames;
+			if (time < frames[0]) return; // Time is before first frame.
+
+			Bone bone = skeleton.bones.Items[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 = Animation.binarySearch(frames, time, 3);
+			float prevFrameX = frames[frame - 2];
+			float prevFrameY = frames[frame - 1];
+			float frameTime = frames[frame];
+			float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime);
+			percent = GetCurvePercent(frame / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 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;
 		}
 		}
 	}
 	}
 
 
 	public class ColorTimeline : CurveTimeline {
 	public class ColorTimeline : CurveTimeline {
-		protected const int PREV_FRAME_TIME = -5;
-		protected const int FRAME_R = 1;
-		protected const int FRAME_G = 2;
-		protected const int FRAME_B = 3;
-		protected const int FRAME_A = 4;
+		protected const int PREV_TIME = -5;
+		protected const int R = 1;
+		protected const int G = 2;
+		protected const int B = 3;
+		protected const int A = 4;
 
 
 		internal int slotIndex;
 		internal int slotIndex;
 		internal float[] frames;
 		internal float[] frames;
@@ -380,8 +409,7 @@ namespace Spine {
 			if (time < frames[0]) return; // Time is before first frame.
 			if (time < frames[0]) return; // Time is before first frame.
 
 
 			float r, g, b, a;
 			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;
 				int i = frames.Length - 1;
 				r = frames[i - 3];
 				r = frames[i - 3];
 				g = frames[i - 2];
 				g = frames[i - 2];
@@ -389,19 +417,19 @@ namespace Spine {
 				a = frames[i];
 				a = frames[i];
 			} else {
 			} else {
 				// Interpolate between the previous frame and the current frame.
 				// Interpolate between the previous frame and the current frame.
-				int frameIndex = Animation.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 = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime);
-				percent = GetCurvePercent(frameIndex / 5 - 1, percent < 0 ? 0 : (percent > 1 ? 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 = Animation.binarySearch(frames, time, 5);
+				float frameTime = frames[frame];
+				float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime);
+				percent = GetCurvePercent(frame / 5 - 1, percent < 0 ? 0 : (percent > 1 ? 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;
 			}
 			}
 			Slot slot = skeleton.slots.Items[slotIndex];
 			Slot slot = skeleton.slots.Items[slotIndex];
 			if (alpha < 1) {
 			if (alpha < 1) {
@@ -488,19 +516,19 @@ namespace Spine {
 				return;
 				return;
 			if (time < frames[0]) return; // Time is before first frame.
 			if (time < frames[0]) return; // Time is before first frame.
 
 
-			int frameIndex;
+			int frame;
 			if (lastTime < frames[0])
 			if (lastTime < frames[0])
-				frameIndex = 0;
+				frame = 0;
 			else {
 			else {
-				frameIndex = Animation.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 = Animation.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]);
 		}
 		}
 	}
 	}
 
 
@@ -528,15 +556,15 @@ namespace Spine {
 			float[] frames = this.frames;
 			float[] frames = this.frames;
 			if (time < frames[0]) return; // Time is before first frame.
 			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.
 			if (time >= frames[frames.Length - 1]) // Time is after last frame.
-				frameIndex = frames.Length - 1;
+				frame = frames.Length - 1;
 			else
 			else
-				frameIndex = Animation.binarySearch(frames, time) - 1;
+				frame = Animation.binarySearch(frames, time) - 1;
 
 
 			ExposedList<Slot> drawOrder = skeleton.drawOrder;
 			ExposedList<Slot> drawOrder = skeleton.drawOrder;
 			ExposedList<Slot> slots = skeleton.slots;
 			ExposedList<Slot> slots = skeleton.slots;
-			int[] drawOrderToSetupIndex = drawOrders[frameIndex];
+			int[] drawOrderToSetupIndex = drawOrders[frame];
 			if (drawOrderToSetupIndex == null) {
 			if (drawOrderToSetupIndex == null) {
 				drawOrder.Clear();
 				drawOrder.Clear();
 				for (int i = 0, n = slots.Count; i < n; i++)
 				for (int i = 0, n = slots.Count; i < n; i++)
@@ -605,13 +633,13 @@ namespace Spine {
 			}
 			}
 
 
 			// Interpolate between the previous frame and the current frame.
 			// Interpolate between the previous frame and the current frame.
-			int frameIndex = Animation.binarySearch(frames, time);
-			float frameTime = frames[frameIndex];
-			float percent = 1 - (time - frameTime) / (frames[frameIndex - 1] - frameTime);
-			percent = GetCurvePercent(frameIndex - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
+			int frame = Animation.binarySearch(frames, time);
+			float frameTime = frames[frame];
+			float percent = 1 - (time - frameTime) / (frames[frame - 1] - frameTime);
+			percent = GetCurvePercent(frame - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
 
 
-			float[] prevVertices = frameVertices[frameIndex - 1];
-			float[] nextVertices = frameVertices[frameIndex];
+			float[] prevVertices = frameVertices[frame - 1];
+			float[] nextVertices = frameVertices[frame];
 
 
 			if (alpha < 1) {
 			if (alpha < 1) {
 				for (int i = 0; i < vertexCount; i++) {
 				for (int i = 0; i < vertexCount; i++) {
@@ -629,10 +657,10 @@ namespace Spine {
 	}
 	}
 
 
 	public class IkConstraintTimeline : CurveTimeline {
 	public class IkConstraintTimeline : CurveTimeline {
-		private const int PREV_FRAME_TIME = -3;
-		private const int PREV_FRAME_MIX = -2;
-		private const int PREV_FRAME_BEND_DIRECTION = -1;
-		private const int FRAME_MIX = 1;
+		private const int PREV_TIME = -3;
+		private const int PREV_MIX = -2;
+		private const int PREV_BEND_DIRECTION = -1;
+		private const int MIX = 1;
 
 
 		internal int ikConstraintIndex;
 		internal int ikConstraintIndex;
 		internal float[] frames;
 		internal float[] frames;
@@ -644,8 +672,8 @@ namespace Spine {
 			: base(frameCount) {
 			: base(frameCount) {
 			frames = new float[frameCount * 3];
 			frames = new float[frameCount * 3];
 		}
 		}
-
-		/** Sets the time, mix and bend direction of the specified keyframe. */
+			
+		/// <summary>Sets the time, mix and bend direction of the specified keyframe.</summary>
 		public void SetFrame (int frameIndex, float time, float mix, int bendDirection) {
 		public void SetFrame (int frameIndex, float time, float mix, int bendDirection) {
 			frameIndex *= 3;
 			frameIndex *= 3;
 			frames[frameIndex] = time;
 			frames[frameIndex] = time;
@@ -657,24 +685,86 @@ namespace Spine {
 			float[] frames = this.frames;
 			float[] frames = this.frames;
 			if (time < frames[0]) return; // Time is before first frame.
 			if (time < frames[0]) return; // Time is before first frame.
 
 
-			IkConstraint ikConstraint = skeleton.ikConstraints.Items[ikConstraintIndex];
+			IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex];
 
 
 			if (time >= frames[frames.Length - 3]) { // Time is after last frame.
 			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 = Animation.binarySearch(frames, time, 3);
+			float frameTime = frames[frame];
+			float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime);
+			percent = GetCurvePercent(frame / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 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];
+		}
+	}
+
+	public class TransformConstraintTimeline : CurveTimeline {
+		private const int PREV_TIME = -5;
+		private const int PREV_ROTATE_MIX = -4;
+		private const int PREV_TRANSLATE_MIX = -3;
+		private const int PREV_SCALE_MIX = -2;
+		private const int PREV_SHEAR_MIX = -1;
+		private const int ROTATE_MIX = 1;
+		private const int TRANSLATE_MIX = 2;
+		private const int SCALE_MIX = 3;
+		private const int SHEAR_MIX = 4;
+
+		internal int transformConstraintIndex;
+		internal float[] frames;
+
+		public int TransformConstraintIndex { get { return transformConstraintIndex; } set { transformConstraintIndex = value; } }
+		public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, scale mix, shear mix, ...
+
+		public TransformConstraintTimeline (int frameCount)
+			: base(frameCount) {
+			frames = new float[frameCount * 5];
+		}
+			
+		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;
+		}
+
+		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
+			float[] frames = this.frames;
+			if (time < frames[0]) return; // Time is before first frame.
+
+			TransformConstraint constraint = skeleton.transformConstraints.Items[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;
 				return;
 			}
 			}
 
 
 			// Interpolate between the previous frame and the current frame.
 			// Interpolate between the previous frame and the current frame.
-			int frameIndex = Animation.binarySearch(frames, time, 3);
-			float prevFrameMix = frames[frameIndex + PREV_FRAME_MIX];
-			float frameTime = frames[frameIndex];
-			float percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime);
-			percent = GetCurvePercent(frameIndex / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 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 = Animation.binarySearch(frames, time, 5);
+			float frameTime = frames[frame];
+			float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime);
+			percent = GetCurvePercent(frame / 5 - 1, percent < 0 ? 0 : (percent > 1 ? 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;
 		}
 		}
 	}
 	}
 }
 }

+ 14 - 11
spine-csharp/src/Bone.cs

@@ -40,7 +40,7 @@ namespace Spine {
 		internal Skeleton skeleton;
 		internal Skeleton skeleton;
 		internal Bone parent;
 		internal Bone parent;
 		internal ExposedList<Bone> children = new ExposedList<Bone>();
 		internal ExposedList<Bone> children = new ExposedList<Bone>();
-		internal float x, y, rotation, scaleX, scaleY;
+		internal float x, y, rotation, scaleX, scaleY, shearX, shearY;
 		internal float appliedRotation, appliedScaleX, appliedScaleY;
 		internal float appliedRotation, appliedScaleX, appliedScaleY;
 
 
 		internal float a, b, worldX;
 		internal float a, b, worldX;
@@ -62,6 +62,8 @@ namespace Spine {
 		public float AppliedScaleY { get { return appliedScaleY; } set { appliedScaleY = value; } }
 		public float AppliedScaleY { get { return appliedScaleY; } set { appliedScaleY = value; } }
 		public float ScaleX { get { return scaleX; } set { scaleX = value; } }
 		public float ScaleX { get { return scaleX; } set { scaleX = value; } }
 		public float ScaleY { get { return scaleY; } set { scaleY = value; } }
 		public float ScaleY { get { return scaleY; } set { scaleY = value; } }
+		public float ShearX { get { return shearX; } set { shearX = value; } }
+		public float ShearY { get { return shearY; } set { shearY = value; } }
 
 
 		public float A { get { return a; } }
 		public float A { get { return a; } }
 		public float B { get { return b; } }
 		public float B { get { return b; } }
@@ -88,22 +90,24 @@ namespace Spine {
 
 
 		/// <summary>Same as {@link #updateWorldTransform()}. This method exists for Bone to implement {@link Updatable}.</summary>
 		/// <summary>Same as {@link #updateWorldTransform()}. This method exists for Bone to implement {@link Updatable}.</summary>
 		public void Update () {
 		public void Update () {
-			UpdateWorldTransform(x, y, rotation, scaleX, scaleY);
+			UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY);
 		}
 		}
 
 
 		/// <summary>Computes the world SRT using the parent bone and this bone's local SRT.</summary>
 		/// <summary>Computes the world SRT using the parent bone and this bone's local SRT.</summary>
 		public void UpdateWorldTransform () {
 		public void UpdateWorldTransform () {
-			UpdateWorldTransform(x, y, rotation, scaleX, scaleY);
+			UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY);
 		}
 		}
 
 
 		/// <summary>Computes the world SRT using the parent bone and the specified local SRT.</summary>
 		/// <summary>Computes the world SRT using the parent bone and the specified local SRT.</summary>
-		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;
 			appliedRotation = rotation;
 			appliedScaleX = scaleX;
 			appliedScaleX = scaleX;
 			appliedScaleY = scaleY;
 			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 = MathUtils.CosDeg(rotation + shearX) * scaleX, lb = MathUtils.CosDeg(rotationY) * scaleY;
+			float lc = MathUtils.SinDeg(rotation + shearX) * scaleX, ld = MathUtils.SinDeg(rotationY) * scaleY;
+
 			Bone parent = this.parent;
 			Bone parent = this.parent;
 			if (parent == null) { // Root bone.
 			if (parent == null) { // Root bone.
 				Skeleton skeleton = this.skeleton;
 				Skeleton skeleton = this.skeleton;
@@ -146,8 +150,7 @@ namespace Spine {
 					pc = 0;
 					pc = 0;
 					pd = 1;
 					pd = 1;
 					do {
 					do {
-						cos = MathUtils.CosDeg(parent.appliedRotation);
-						sin = MathUtils.SinDeg(parent.appliedRotation);
+						float cos = MathUtils.CosDeg(parent.appliedRotation), sin = MathUtils.SinDeg(parent.appliedRotation);
 						float temp = pa * cos + pb * sin;
 						float temp = pa * cos + pb * sin;
 						pb = pa * -sin + pb * cos;
 						pb = pa * -sin + pb * cos;
 						pa = temp;
 						pa = temp;
@@ -168,9 +171,7 @@ namespace Spine {
 					pc = 0;
 					pc = 0;
 					pd = 1;
 					pd = 1;
 					do {
 					do {
-						float r = parent.rotation;
-						cos = MathUtils.CosDeg(r);
-						sin = MathUtils.SinDeg(r);
+						float r = parent.appliedRotation, cos = MathUtils.CosDeg(r), sin = MathUtils.SinDeg(r);
 						float psx = parent.appliedScaleX, psy = parent.appliedScaleY;
 						float psx = parent.appliedScaleX, psy = parent.appliedScaleY;
 						float za = cos * psx, zb = -sin * psy, zc = sin * psx, zd = cos * psy;
 						float za = cos * psx, zb = -sin * psy, zc = sin * psx, zd = cos * psy;
 						float temp = pa * za + pb * zc;
 						float temp = pa * za + pb * zc;
@@ -221,6 +222,8 @@ namespace Spine {
 			rotation = data.rotation;
 			rotation = data.rotation;
 			scaleX = data.scaleX;
 			scaleX = data.scaleX;
 			scaleY = data.scaleY;
 			scaleY = data.scaleY;
+			shearX = data.shearX;
+			shearY = data.shearY;
 		}
 		}
 
 
 		public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) {
 		public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) {

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

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

+ 35 - 41
spine-csharp/src/IkConstraint.cs

@@ -83,14 +83,17 @@ namespace Spine {
 		/// <summary>Adjusts the bone rotation so the tip is as close to the target position as possible. The target is specified
 		/// <summary>Adjusts the bone rotation so the tip is as close to the target position as possible. The target is specified
 		/// in the world coordinate system.</summary>
 		/// in the world coordinate system.</summary>
 		static public void Apply (Bone bone, float targetX, float targetY, float alpha) {
 		static public void Apply (Bone bone, float targetX, float targetY, float alpha) {
-			float parentRotation = bone.parent == null ? 0 : bone.parent.WorldRotationX;
-			float rotation = bone.rotation;
-			float rotationIK = MathUtils.Atan2(targetY - bone.worldY, targetX - bone.worldX) * MathUtils.radDeg - parentRotation;
-			if ((bone.worldSignX != bone.worldSignY) != (bone.skeleton.flipX != (bone.skeleton.flipY != Bone.yDown)))
-				rotationIK = 360 - rotationIK;
-			if (rotationIK > 180) rotationIK -= 360;
+			Bone pp = bone.parent;
+			float id = 1 / (pp.a * pp.d - pp.b * pp.c);
+			float x = targetX - pp.worldX, y = targetY - pp.worldY;
+			float tx = (x * pp.d - y * pp.b) * id - bone.x, ty = (y * pp.a - x * pp.c) * id - bone.y;
+			float rotationIK = MathUtils.Atan2(ty, tx) * MathUtils.radDeg - bone.shearX;
+			if (bone.scaleX < 0) rotationIK += 180;
+			if (rotationIK > 180)
+				rotationIK -= 360;
 			else if (rotationIK < -180) rotationIK += 360;
 			else if (rotationIK < -180) rotationIK += 360;
-			bone.UpdateWorldTransform(bone.x, bone.y, rotation + (rotationIK - rotation) * alpha, bone.appliedScaleX, bone.appliedScaleY);
+			bone.UpdateWorldTransform(bone.x, bone.y, bone.rotation + (rotationIK - bone.rotation) * alpha, bone.appliedScaleX,
+				bone.appliedScaleY, bone.shearX, bone.shearY);
 		}
 		}
 
 
 		/// <summary>Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as
 		/// <summary>Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as
@@ -125,22 +128,12 @@ namespace Spine {
 			} else
 			} else
 				os2 = 0;
 				os2 = 0;
 			Bone pp = parent.parent;
 			Bone pp = parent.parent;
-			float tx, ty, dx, dy;
-			if (pp == null) {
-				tx = targetX - px;
-				ty = targetY - py;
-				dx = child.worldX - px;
-				dy = child.worldY - py;
-			} else {
-				float a = pp.a, b = pp.b, c = pp.c, d = pp.d, invDet = 1 / (a * d - b * c);
-				float wx = pp.worldX, wy = pp.worldY, x = targetX - wx, y = targetY - wy;
-				tx = (x * d - y * b) * invDet - px;
-				ty = (y * a - x * c) * invDet - py;
-				x = child.worldX - wx;
-				y = child.worldY - wy;
-				dx = (x * d - y * b) * invDet - px;
-				dy = (y * a - x * c) * invDet - py;
-			}
+			float ppa = pp.a, ppb = pp.b, ppc = pp.c, ppd = pp.d, id = 1 / (ppa * ppd - ppb * ppc);
+			float x = targetX - pp.worldX, y = targetY - pp.worldY;
+			float tx = (x * ppd - y * ppb) * id - px, ty = (y * ppa - x * ppc) * id - py;
+			x = child.worldX - pp.worldX;
+			y = child.worldY - pp.worldY;
+			float dx = (x * ppd - y * ppb) * id - px, dy = (y * ppa - x * ppc) * id - py;
 			float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2;
 			float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2;
 			if (u) {
 			if (u) {
 				l2 *= psx;
 				l2 *= psx;
@@ -162,40 +155,41 @@ namespace Spine {
 					float r0 = q / c2, r1 = c0 / q;
 					float r0 = q / c2, r1 = c0 / q;
 					float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1;
 					float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1;
 					if (r * r <= dd) {
 					if (r * r <= dd) {
-						float y1 = (float)Math.Sqrt(dd - r * r) * bendDir;
-						a1 = ta - MathUtils.Atan2(y1, r);
-						a2 = MathUtils.Atan2(y1 / psy, (r - l1) / psx);
+						y = (float)Math.Sqrt(dd - r * r) * bendDir;
+						a1 = ta - MathUtils.Atan2(y, r);
+						a2 = MathUtils.Atan2(y / psy, (r - l1) / psx);
 						goto outer;
 						goto outer;
 					}
 					}
 				}
 				}
 				float minAngle = 0, minDist = float.MaxValue, minX = 0, minY = 0;
 				float minAngle = 0, minDist = float.MaxValue, minX = 0, minY = 0;
 				float maxAngle = 0, maxDist = 0, maxX = 0, maxY = 0;
 				float maxAngle = 0, maxDist = 0, maxX = 0, maxY = 0;
-				float x = l1 + a, dist = x * x;
-				if (dist > maxDist) {
+				x = l1 + a;
+				d = x * x;
+				if (d > maxDist) {
 					maxAngle = 0;
 					maxAngle = 0;
-					maxDist = dist;
+					maxDist = d;
 					maxX = x;
 					maxX = x;
 				}
 				}
 				x = l1 - a;
 				x = l1 - a;
-				dist = x * x;
-				if (dist < minDist) {
+				d = x * x;
+				if (d < minDist) {
 					minAngle = MathUtils.PI;
 					minAngle = MathUtils.PI;
-					minDist = dist;
+					minDist = d;
 					minX = x;
 					minX = x;
 				}
 				}
 				float angle = (float)Math.Acos(-a * l1 / (aa - bb));
 				float angle = (float)Math.Acos(-a * l1 / (aa - bb));
 				x = a * MathUtils.Cos(angle) + l1;
 				x = a * MathUtils.Cos(angle) + l1;
-				float y = b * MathUtils.Sin(angle);
-				dist = x * x + y * y;
-				if (dist < minDist) {
+				y = b * MathUtils.Sin(angle);
+				d = x * x + y * y;
+				if (d < minDist) {
 					minAngle = angle;
 					minAngle = angle;
-					minDist = dist;
+					minDist = d;
 					minX = x;
 					minX = x;
 					minY = y;
 					minY = y;
 				}
 				}
-				if (dist > maxDist) {
+				if (d > maxDist) {
 					maxAngle = angle;
 					maxAngle = angle;
-					maxDist = dist;
+					maxDist = d;
 					maxX = x;
 					maxX = x;
 					maxY = y;
 					maxY = y;
 				}
 				}
@@ -210,15 +204,15 @@ namespace Spine {
 		outer:
 		outer:
 			float os = MathUtils.Atan2(cy, cx) * s2;
 			float os = MathUtils.Atan2(cy, cx) * s2;
 			a1 = (a1 - os) * MathUtils.radDeg + os1;
 			a1 = (a1 - os) * MathUtils.radDeg + os1;
-			a2 = (a2 + os) * MathUtils.radDeg * s2 + os2;
+			a2 = ((a2 + os) * MathUtils.radDeg - child.shearX) * s2 + os2;
 			if (a1 > 180) a1 -= 360;
 			if (a1 > 180) a1 -= 360;
 			else if (a1 < -180) a1 += 360;
 			else if (a1 < -180) a1 += 360;
 			if (a2 > 180) a2 -= 360;
 			if (a2 > 180) a2 -= 360;
 			else if (a2 < -180) a2 += 360;
 			else if (a2 < -180) a2 += 360;
 			float rotation = parent.rotation;
 			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, 0, 0);
 			rotation = child.rotation;
 			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);
 		}
 		}
 	}
 	}
 }
 }

+ 1 - 0
spine-csharp/src/MathUtils.cs

@@ -34,6 +34,7 @@ using System;
 namespace Spine {
 namespace Spine {
 	public static class MathUtils {
 	public static class MathUtils {
 		public const float PI = 3.1415927f;
 		public const float PI = 3.1415927f;
+		public const float PI2 = PI * 2;
 		public const float radDeg = 180f / PI;
 		public const float radDeg = 180f / PI;
 		public const float degRad = PI / 180;
 		public const float degRad = PI / 180;
 
 

+ 6 - 5
spine-csharp/src/Skeleton.cs

@@ -127,8 +127,7 @@ namespace Spine {
 			for (int i = 0; i < transformConstraintsCount; i++) {
 			for (int i = 0; i < transformConstraintsCount; i++) {
 				TransformConstraint transformConstraint = transformConstraints.Items[i];
 				TransformConstraint transformConstraint = transformConstraints.Items[i];
 				for (int ii = updateCache.Count - 1; i >= 0; ii--) {
 				for (int ii = updateCache.Count - 1; i >= 0; ii--) {
-					IUpdatable updateable = updateCache.Items[ii];
-					if (updateable == transformConstraint.bone || updateable == transformConstraint.target) {
+					if (updateCache.Items[ii] == transformConstraint.bone) {
 						updateCache.Insert(ii + 1, transformConstraint);
 						updateCache.Insert(ii + 1, transformConstraint);
 						break;
 						break;
 					}
 					}
@@ -165,9 +164,11 @@ namespace Spine {
 			ExposedList<TransformConstraint> transformConstraints = this.transformConstraints;
 			ExposedList<TransformConstraint> transformConstraints = this.transformConstraints;
 			for (int i = 0, n = transformConstraints.Count; i < n; i++) {
 			for (int i = 0, n = transformConstraints.Count; i < n; i++) {
 				TransformConstraint constraint = transformConstraints.Items[i];
 				TransformConstraint constraint = transformConstraints.Items[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.shearMix = data.shearMix;
 			}
 			}
 		}
 		}
 
 

+ 46 - 18
spine-csharp/src/SkeletonBinary.cs

@@ -29,6 +29,10 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
+#if (UNITY_5 || UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7 || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1)
+#define IS_UNITY
+#endif
+
 using System;
 using System;
 using System.IO;
 using System.IO;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -40,11 +44,12 @@ using Windows.Storage;
 
 
 namespace Spine {
 namespace Spine {
 	public class SkeletonBinary {
 	public class SkeletonBinary {
-		public const int TIMELINE_SCALE = 0;
-		public const int TIMELINE_ROTATE = 1;
-		public const int TIMELINE_TRANSLATE = 2;
-		public const int TIMELINE_ATTACHMENT = 3;
-		public const int TIMELINE_COLOR = 4;
+		public const int TIMELINE_ROTATE = 0;
+		public const int TIMELINE_TRANSLATE = 1;
+		public const int TIMELINE_SCALE = 2;
+		public const int TIMELINE_SHEAR = 3;
+		public const int TIMELINE_ATTACHMENT = 4;
+		public const int TIMELINE_COLOR = 5;
 
 
 		public const int CURVE_LINEAR = 0;
 		public const int CURVE_LINEAR = 0;
 		public const int CURVE_STEPPED = 1;
 		public const int CURVE_STEPPED = 1;
@@ -65,10 +70,8 @@ namespace Spine {
 			this.attachmentLoader = attachmentLoader;
 			this.attachmentLoader = attachmentLoader;
 			Scale = 1;
 			Scale = 1;
 		}
 		}
-
-		#if !(UNITY_5 || UNITY_4 || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1)
-		#if WINDOWS_STOREAPP
-
+			
+		#if !ISUNITY && WINDOWS_STOREAPP
 		private async Task<SkeletonData> ReadFile(string path) {
 		private async Task<SkeletonData> ReadFile(string path) {
 			var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
 			var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
 			using (var input = new BufferedStream(await folder.GetFileAsync(path).AsTask().ConfigureAwait(false))) {
 			using (var input = new BufferedStream(await folder.GetFileAsync(path).AsTask().ConfigureAwait(false))) {
@@ -95,7 +98,6 @@ namespace Spine {
 		}
 		}
 
 
 		#endif // WINDOWS_STOREAPP
 		#endif // WINDOWS_STOREAPP
-		#endif // !(UNITY)
 
 
 		public SkeletonData ReadSkeletonData (Stream input) {
 		public SkeletonData ReadSkeletonData (Stream input) {
 			if (input == null) throw new ArgumentNullException("input");
 			if (input == null) throw new ArgumentNullException("input");
@@ -121,14 +123,16 @@ namespace Spine {
 				String name = ReadString(input);
 				String name = ReadString(input);
 				BoneData parent = i == 0 ? null : skeletonData.bones.Items[ReadVarint(input, true)];
 				BoneData parent = i == 0 ? null : skeletonData.bones.Items[ReadVarint(input, true)];
 				BoneData boneData = new BoneData(name, parent);
 				BoneData boneData = new BoneData(name, parent);
+				boneData.rotation = ReadFloat(input);		
 				boneData.x = ReadFloat(input) * scale;
 				boneData.x = ReadFloat(input) * scale;
 				boneData.y = ReadFloat(input) * scale;
 				boneData.y = ReadFloat(input) * scale;
 				boneData.scaleX = ReadFloat(input);
 				boneData.scaleX = ReadFloat(input);
 				boneData.scaleY = ReadFloat(input);
 				boneData.scaleY = ReadFloat(input);
-				boneData.rotation = ReadFloat(input);
+				boneData.shearX = ReadFloat(input);
+				boneData.shearY = ReadFloat(input);
 				boneData.length = ReadFloat(input) * scale;
 				boneData.length = ReadFloat(input) * scale;
-				boneData.inheritScale = ReadBoolean(input);
 				boneData.inheritRotation = ReadBoolean(input);
 				boneData.inheritRotation = ReadBoolean(input);
+				boneData.inheritScale = ReadBoolean(input);
 				if (nonessential) ReadInt(input); // Skip bone color.
 				if (nonessential) ReadInt(input); // Skip bone color.
 				skeletonData.bones.Add(boneData);
 				skeletonData.bones.Add(boneData);
 			}
 			}
@@ -149,9 +153,16 @@ namespace Spine {
 				TransformConstraintData transformConstraintData = new TransformConstraintData(ReadString(input));
 				TransformConstraintData transformConstraintData = new TransformConstraintData(ReadString(input));
 				transformConstraintData.bone = skeletonData.bones.Items[ReadVarint(input, true)];
 				transformConstraintData.bone = skeletonData.bones.Items[ReadVarint(input, true)];
 				transformConstraintData.target = skeletonData.bones.Items[ReadVarint(input, true)];
 				transformConstraintData.target = skeletonData.bones.Items[ReadVarint(input, true)];
+				transformConstraintData.offsetRotation = ReadFloat(input);
+				transformConstraintData.offsetX = ReadFloat(input) * scale;
+				transformConstraintData.offsetY = ReadFloat(input) * scale;
+				transformConstraintData.offsetScaleX = ReadFloat(input);
+				transformConstraintData.offsetScaleY = ReadFloat(input);
+				transformConstraintData.offsetShearY = ReadFloat(input);
+				transformConstraintData.rotateMix = ReadFloat(input);
 				transformConstraintData.translateMix = ReadFloat(input);
 				transformConstraintData.translateMix = ReadFloat(input);
-				transformConstraintData.x = ReadFloat(input) * scale;
-				transformConstraintData.y = ReadFloat(input) * scale;
+				transformConstraintData.scaleMix = ReadFloat(input);
+				transformConstraintData.shearMix = ReadFloat(input);
 				skeletonData.transformConstraints.Add(transformConstraintData);
 				skeletonData.transformConstraints.Add(transformConstraintData);
 			}
 			}
 
 
@@ -247,11 +258,11 @@ namespace Spine {
 			switch (type) {
 			switch (type) {
 			case AttachmentType.region: {
 			case AttachmentType.region: {
 					String path = ReadString(input);
 					String path = ReadString(input);
+					float rotation = ReadFloat(input);		
 					float x = ReadFloat(input);
 					float x = ReadFloat(input);
 					float y = ReadFloat(input);
 					float y = ReadFloat(input);
 					float scaleX = ReadFloat(input);
 					float scaleX = ReadFloat(input);
 					float scaleY = ReadFloat(input);
 					float scaleY = ReadFloat(input);
-					float rotation = ReadFloat(input);
 					float width = ReadFloat(input);
 					float width = ReadFloat(input);
 					float height = ReadFloat(input);
 					float height = ReadFloat(input);
 					int color = ReadInt(input);
 					int color = ReadInt(input);
@@ -508,11 +519,14 @@ namespace Spine {
 							break;
 							break;
 						}
 						}
 					case TIMELINE_TRANSLATE:
 					case TIMELINE_TRANSLATE:
-					case TIMELINE_SCALE: {
+					case TIMELINE_SCALE:
+					case TIMELINE_SHEAR: {
 							TranslateTimeline timeline;
 							TranslateTimeline timeline;
 							float timelineScale = 1;
 							float timelineScale = 1;
 							if (timelineType == TIMELINE_SCALE)
 							if (timelineType == TIMELINE_SCALE)
 								timeline = new ScaleTimeline(frameCount);
 								timeline = new ScaleTimeline(frameCount);
+							else if (timelineType == TIMELINE_SHEAR)
+								timeline = new ShearTimeline(frameCount);
 							else {
 							else {
 								timeline = new TranslateTimeline(frameCount);
 								timeline = new TranslateTimeline(frameCount);
 								timelineScale = scale;
 								timelineScale = scale;
@@ -533,10 +547,10 @@ namespace Spine {
 
 
 			// IK timelines.
 			// IK timelines.
 			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
 			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
-				IkConstraintData ikConstraint = skeletonData.ikConstraints.Items[ReadVarint(input, true)];
+				IkConstraintData constraint = skeletonData.ikConstraints.Items[ReadVarint(input, true)];
 				int frameCount = ReadVarint(input, true);
 				int frameCount = ReadVarint(input, true);
 				IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount);
 				IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount);
-				timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(ikConstraint);
+				timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint);
 				for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
 				for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
 					timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input));
 					timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input));
 					if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
 					if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
@@ -545,6 +559,20 @@ namespace Spine {
 				duration = Math.Max(duration, timeline.frames[frameCount * 3 - 3]);
 				duration = Math.Max(duration, timeline.frames[frameCount * 3 - 3]);
 			}
 			}
 
 
+			// Transform constraint timelines.
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
+				TransformConstraintData constraint = skeletonData.transformConstraints.Items[ReadVarint(input, true)];
+				int frameCount = ReadVarint(input, true);
+				TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount);
+				timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint);
+				for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
+					timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input));
+					if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
+				}
+				timelines.Add(timeline);
+				duration = Math.Max(duration, timeline.frames[frameCount * 5 - 5]);
+			}
+
 			// FFD timelines.
 			// FFD timelines.
 			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
 			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
 				Skin skin = skeletonData.skins.Items[ReadVarint(input, true)];
 				Skin skin = skeletonData.skins.Items[ReadVarint(input, true)];

+ 49 - 16
spine-csharp/src/SkeletonJson.cs

@@ -59,9 +59,7 @@ namespace Spine {
 			Scale = 1;
 			Scale = 1;
 		}
 		}
 
 
-		#if !(IS_UNITY)
-		#if WINDOWS_STOREAPP
-
+		#if !(IS_UNITY) && WINDOWS_STOREAPP
 		private async Task<SkeletonData> ReadFile(string path) {
 		private async Task<SkeletonData> ReadFile(string path) {
 			var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
 			var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
 			var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false);
 			var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false);
@@ -88,9 +86,7 @@ namespace Spine {
 				return skeletonData;
 				return skeletonData;
 			}
 			}
 		}
 		}
-
 		#endif // WINDOWS_STOREAPP
 		#endif // WINDOWS_STOREAPP
-		#endif // !UNITY
 
 
 		public SkeletonData ReadSkeletonData (TextReader reader) {
 		public SkeletonData ReadSkeletonData (TextReader reader) {
 			if (reader == null) throw new ArgumentNullException("reader cannot be null.");
 			if (reader == null) throw new ArgumentNullException("reader cannot be null.");
@@ -125,6 +121,8 @@ namespace Spine {
 				boneData.rotation = GetFloat(boneMap, "rotation", 0);
 				boneData.rotation = GetFloat(boneMap, "rotation", 0);
 				boneData.scaleX = GetFloat(boneMap, "scaleX", 1);
 				boneData.scaleX = GetFloat(boneMap, "scaleX", 1);
 				boneData.scaleY = GetFloat(boneMap, "scaleY", 1);
 				boneData.scaleY = GetFloat(boneMap, "scaleY", 1);
+				boneData.shearX = GetFloat(boneMap, "shearX", 1);
+				boneData.shearY = GetFloat(boneMap, "shearY", 1);
 				boneData.inheritScale = GetBoolean(boneMap, "inheritScale", true);
 				boneData.inheritScale = GetBoolean(boneMap, "inheritScale", true);
 				boneData.inheritRotation = GetBoolean(boneMap, "inheritRotation", true);
 				boneData.inheritRotation = GetBoolean(boneMap, "inheritRotation", true);
 				skeletonData.bones.Add(boneData);
 				skeletonData.bones.Add(boneData);
@@ -165,9 +163,17 @@ namespace Spine {
 					transformConstraintData.target = skeletonData.FindBone(targetName);
 					transformConstraintData.target = skeletonData.FindBone(targetName);
 					if (transformConstraintData.target == null) throw new Exception("Target bone not found: " + targetName);
 					if (transformConstraintData.target == null) throw new Exception("Target bone not found: " + targetName);
 
 
+					transformConstraintData.offsetRotation = GetFloat(transformMap, "rotation", 0);
+					transformConstraintData.offsetX = GetFloat(transformMap, "x", 0) * scale;
+					transformConstraintData.offsetY = GetFloat(transformMap, "y", 0) * scale;
+					transformConstraintData.offsetScaleX = GetFloat(transformMap, "scaleX", 0);
+					transformConstraintData.offsetScaleY = GetFloat(transformMap, "scaleY", 0);
+					transformConstraintData.offsetShearY = GetFloat(transformMap, "shearY", 0);
+
+					transformConstraintData.rotateMix = GetFloat(transformMap, "rotateMix", 1);
 					transformConstraintData.translateMix = GetFloat(transformMap, "translateMix", 1);
 					transformConstraintData.translateMix = GetFloat(transformMap, "translateMix", 1);
-					transformConstraintData.x = GetFloat(transformMap, "x", 0) * scale;
-					transformConstraintData.y = GetFloat(transformMap, "y", 0) * scale;
+					transformConstraintData.scaleMix = GetFloat(transformMap, "scaleMix", 1);
+					transformConstraintData.shearMix = GetFloat(transformMap, "shearMix", 1);
 
 
 					skeletonData.transformConstraints.Add(transformConstraintData);
 					skeletonData.transformConstraints.Add(transformConstraintData);
 				}
 				}
@@ -520,11 +526,13 @@ namespace Spine {
 							timelines.Add(timeline);
 							timelines.Add(timeline);
 							duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 2 - 2]);
 							duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 2 - 2]);
 
 
-						} else if (timelineName == "translate" || timelineName == "scale") {
+						} else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") {
 							TranslateTimeline timeline;
 							TranslateTimeline timeline;
 							float timelineScale = 1;
 							float timelineScale = 1;
 							if (timelineName == "scale")
 							if (timelineName == "scale")
 								timeline = new ScaleTimeline(values.Count);
 								timeline = new ScaleTimeline(values.Count);
+							else if (timelineName == "shear")
+								timeline = new ShearTimeline(values.Count);
 							else {
 							else {
 								timeline = new TranslateTimeline(values.Count);
 								timeline = new TranslateTimeline(values.Count);
 								timelineScale = scale;
 								timelineScale = scale;
@@ -534,8 +542,8 @@ namespace Spine {
 							int frameIndex = 0;
 							int frameIndex = 0;
 							foreach (Dictionary<String, Object> valueMap in values) {
 							foreach (Dictionary<String, Object> valueMap in values) {
 								float time = (float)valueMap["time"];
 								float time = (float)valueMap["time"];
-								float x = valueMap.ContainsKey("x") ? (float)valueMap["x"] : 0;
-								float y = valueMap.ContainsKey("y") ? (float)valueMap["y"] : 0;
+								float x = GetFloat(valueMap, "x", 0);
+								float y = GetFloat(valueMap, "y", 0);
 								timeline.SetFrame(frameIndex, time, (float)x * timelineScale, (float)y * timelineScale);
 								timeline.SetFrame(frameIndex, time, (float)x * timelineScale, (float)y * timelineScale);
 								ReadCurve(timeline, frameIndex, valueMap);
 								ReadCurve(timeline, frameIndex, valueMap);
 								frameIndex++;
 								frameIndex++;
@@ -549,17 +557,18 @@ namespace Spine {
 				}
 				}
 			}
 			}
 
 
+			// IK timelines.
 			if (map.ContainsKey("ik")) {
 			if (map.ContainsKey("ik")) {
-				foreach (KeyValuePair<String, Object> ikMap in (Dictionary<String, Object>)map["ik"]) {
-					IkConstraintData ikConstraint = skeletonData.FindIkConstraint(ikMap.Key);
-					var values = (List<Object>)ikMap.Value;
+				foreach (KeyValuePair<String, Object> constraintMap in (Dictionary<String, Object>)map["ik"]) {
+					IkConstraintData constraint = skeletonData.FindIkConstraint(constraintMap.Key);
+					var values = (List<Object>)constraintMap.Value;
 					var timeline = new IkConstraintTimeline(values.Count);
 					var timeline = new IkConstraintTimeline(values.Count);
-					timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(ikConstraint);
+					timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint);
 					int frameIndex = 0;
 					int frameIndex = 0;
 					foreach (Dictionary<String, Object> valueMap in values) {
 					foreach (Dictionary<String, Object> valueMap in values) {
 						float time = (float)valueMap["time"];
 						float time = (float)valueMap["time"];
-						float mix = valueMap.ContainsKey("mix") ? (float)valueMap["mix"] : 1;
-						bool bendPositive = valueMap.ContainsKey("bendPositive") ? (bool)valueMap["bendPositive"] : true;
+						float mix = GetFloat(valueMap, "mix", 1);
+						bool bendPositive = GetBoolean(valueMap, "bendPositive", true);
 						timeline.SetFrame(frameIndex, time, mix, bendPositive ? 1 : -1);
 						timeline.SetFrame(frameIndex, time, mix, bendPositive ? 1 : -1);
 						ReadCurve(timeline, frameIndex, valueMap);
 						ReadCurve(timeline, frameIndex, valueMap);
 						frameIndex++;
 						frameIndex++;
@@ -569,6 +578,30 @@ namespace Spine {
 				}
 				}
 			}
 			}
 
 
+			// Transform constraint timelines.
+			if (map.ContainsKey("transform")) {
+				foreach (KeyValuePair<String, Object> constraintMap in (Dictionary<String, Object>)map["transform"]) {
+					TransformConstraintData constraint = skeletonData.FindTransformConstraint(constraintMap.Key);
+					var values = (List<Object>)constraintMap.Value;
+					var timeline = new TransformConstraintTimeline(values.Count);
+					timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint);
+					int frameIndex = 0;
+					foreach (Dictionary<String, Object> valueMap in values) {
+						float time = (float)valueMap["time"];
+						float rotateMix = GetFloat(valueMap, "rotateMix", 1);
+						float translateMix = GetFloat(valueMap, "translateMix", 1);
+						float scaleMix = GetFloat(valueMap, "scaleMix", 1);
+						float shearMix = GetFloat(valueMap, "shearMix", 1);
+						timeline.SetFrame(frameIndex, time, rotateMix, translateMix, scaleMix, shearMix);
+						ReadCurve(timeline, frameIndex, valueMap);
+						frameIndex++;
+					}
+					timelines.Add(timeline);
+					duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 5 - 5]);
+				}
+			}
+
+			// FFD timelines.
 			if (map.ContainsKey("ffd")) {
 			if (map.ContainsKey("ffd")) {
 				foreach (KeyValuePair<String, Object> ffdMap in (Dictionary<String, Object>)map["ffd"]) {
 				foreach (KeyValuePair<String, Object> ffdMap in (Dictionary<String, Object>)map["ffd"]) {
 					Skin skin = skeletonData.FindSkin(ffdMap.Key);
 					Skin skin = skeletonData.FindSkin(ffdMap.Key);

+ 67 - 11
spine-csharp/src/TransformConstraint.cs

@@ -36,38 +36,94 @@ namespace Spine {
 	public class TransformConstraint : IUpdatable {
 	public class TransformConstraint : IUpdatable {
 		internal TransformConstraintData data;
 		internal TransformConstraintData data;
 		internal Bone bone, target;
 		internal Bone bone, target;
-		internal float translateMix;
-		internal float x, y;
+		internal float rotateMix, translateMix, scaleMix, shearMix;
+		internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY;
 
 
 		public TransformConstraintData Data { get { return data; } }
 		public TransformConstraintData Data { get { return data; } }
 		public Bone Bone { get { return bone; } set { bone = value; } }
 		public Bone Bone { get { return bone; } set { bone = value; } }
 		public Bone Target { get { return target; } set { target = value; } }
 		public Bone Target { get { return target; } set { target = value; } }
+		public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
 		public float TranslateMix { get { return translateMix; } set { translateMix = value; } }
 		public float TranslateMix { get { return translateMix; } set { translateMix = value; } }
-		public float X { get { return x; } set { x = value; } }
-		public float Y { get { return y; } set { y = value; } }
+		public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } }
+		public float ShearMix { get { return shearMix; } set { shearMix = value; } }
+
+		public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } }
+		public float OffsetX { get { return offsetX; } set { offsetX = value; } }
+		public float OffsetY { get { return offsetY; } set { offsetY = value; } }
+		public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } }
+		public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } }
+		public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } }
 
 
 		public TransformConstraint (TransformConstraintData data, Skeleton skeleton) {
 		public TransformConstraint (TransformConstraintData data, Skeleton skeleton) {
 			if (data == null) throw new ArgumentNullException("data cannot be null.");
 			if (data == null) throw new ArgumentNullException("data cannot be null.");
 			if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null.");
 			if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null.");
 			this.data = data;
 			this.data = data;
 			translateMix = data.translateMix;
 			translateMix = data.translateMix;
-			x = data.x;
-			y = data.y;
+			rotateMix = data.rotateMix;
+			scaleMix = data.scaleMix;
+			shearMix = data.shearMix;
+			offsetX = data.offsetX;
+			offsetY = data.offsetY;
+			offsetScaleX = data.offsetScaleX;
+			offsetScaleY = data.offsetScaleY;
+			offsetShearY = data.offsetShearY;
 
 
 			bone = skeleton.FindBone(data.bone.name);
 			bone = skeleton.FindBone(data.bone.name);
 			target = skeleton.FindBone(data.target.name);
 			target = skeleton.FindBone(data.target.name);
 		}
 		}
 
 
-		public void Update () {
-			Apply();
+		public void Apply () {
+			Update();
 		}
 		}
 
 
-		public void Apply () {
+		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 = MathUtils.Atan2(target.c, target.a) - MathUtils.Atan2(c, a) + offsetRotation * MathUtils.degRad;
+				if (r > MathUtils.PI)
+					r -= MathUtils.PI2;
+				else if (r < -MathUtils.PI) r += MathUtils.PI2;
+				r *= rotateMix;
+				float cos = MathUtils.Cos(r), sin = MathUtils.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 + offsetScaleX) * scaleMix) / bs : 0;
+				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 + offsetScaleY) * scaleMix) / bs : 0;
+				bone.b *= s;
+				bone.d *= s;
+			}
+
+			if (shearMix > 0) {
+				float b = bone.b, d = bone.d;
+				float by = MathUtils.Atan2(d, b);
+				float r = MathUtils.Atan2(target.d, target.b) - MathUtils.Atan2(target.c, target.a) - (by - MathUtils.Atan2(bone.c, bone.a));
+				if (r > MathUtils.PI)
+					r -= MathUtils.PI2;
+				else if (r < -MathUtils.PI) r += MathUtils.PI2;
+				r = by + (r + offsetShearY * MathUtils.degRad) * shearMix;
+				float s = (float)Math.Sqrt(b * b + d * d);
+				bone.b = MathUtils.Cos(r) * s;
+				bone.d = MathUtils.Sin(r) * s;
+			}
+
 			float translateMix = this.translateMix;
 			float translateMix = this.translateMix;
 			if (translateMix > 0) {
 			if (translateMix > 0) {
-				Bone bone = this.bone;
 				float tx, ty;
 				float tx, ty;
-				target.LocalToWorld(x, y, out tx, out ty);
+				target.LocalToWorld(offsetX, offsetY, out tx, out ty);
 				bone.worldX += (tx - bone.worldX) * translateMix;
 				bone.worldX += (tx - bone.worldX) * translateMix;
 				bone.worldY += (ty - bone.worldY) * translateMix;
 				bone.worldY += (ty - bone.worldY) * translateMix;
 			}
 			}

+ 12 - 4
spine-csharp/src/TransformConstraintData.cs

@@ -36,15 +36,23 @@ namespace Spine {
 	public class TransformConstraintData {
 	public class TransformConstraintData {
 		internal String name;
 		internal String name;
 		internal BoneData bone, target;
 		internal BoneData bone, target;
-		internal float translateMix;
-		internal float x, y;
+		internal float rotateMix, translateMix, scaleMix, shearMix;
+		internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY;
 
 
 		public String Name { get { return name; } }
 		public String Name { get { return name; } }
 		public BoneData Bone { get { return bone; } set { bone = value; } }
 		public BoneData Bone { get { return bone; } set { bone = value; } }
 		public BoneData Target { get { return target; } set { target = value; } }
 		public BoneData Target { get { return target; } set { target = value; } }
+		public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
 		public float TranslateMix { get { return translateMix; } set { translateMix = value; } }
 		public float TranslateMix { get { return translateMix; } set { translateMix = value; } }
-		public float X { get { return x; } set { x = value; } }
-		public float Y { get { return y; } set { y = value; } }
+		public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } }
+		public float ShearMix { get { return shearMix; } set { shearMix = value; } }
+
+		public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } }
+		public float OffsetX { get { return offsetX; } set { offsetX = value; } }
+		public float OffsetY { get { return offsetY; } set { offsetY = value; } }
+		public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } }
+		public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } }
+		public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } }
 
 
 		public TransformConstraintData (String name) {
 		public TransformConstraintData (String name) {
 			if (name == null) throw new ArgumentNullException("name cannot be null.");
 			if (name == null) throw new ArgumentNullException("name cannot be null.");

+ 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 version
 
 
-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 works with data exported from the latest version of Spine.
 
 
 spine-unity supports all Spine features.
 spine-unity supports all Spine features.