Эх сурвалжийг харах

[csharp][unity] Port of Transform constraint property mapping and cumulated 4.3 changes. Excluding import scale fixes.

Harald Csaszar 5 сар өмнө
parent
commit
90bfdba422

+ 2 - 2
spine-csharp/src/Animation.cs

@@ -63,7 +63,7 @@ namespace Spine {
 			Timeline[] timelinesItems = timelines.Items;
 			for (int t = 0; t < timelinesCount; ++t)
 				idCount += timelinesItems[t].PropertyIds.Length;
-			string[] propertyIds = new string[idCount];
+			var propertyIds = new string[idCount];
 			int currentId = 0;
 			for (int t = 0; t < timelinesCount; ++t) {
 				string[] ids = timelinesItems[t].PropertyIds;
@@ -320,7 +320,7 @@ namespace Spine {
 		public void Shrink (int bezierCount) {
 			int size = FrameCount + bezierCount * BEZIER_SIZE;
 			if (curves.Length > size) {
-				float[] newCurves = new float[size];
+				var newCurves = new float[size];
 				Array.Copy(curves, 0, newCurves, 0, size);
 				curves = newCurves;
 			}

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

@@ -7,7 +7,7 @@
  * Integration of the Spine Runtimes into software or otherwise creating
  * derivative works of the Spine Runtimes is permitted under the terms and
  * conditions of Section 2 of the Spine Editor License Agreement:
- * http://esotericsoftware.com/spine-editor-license
+ * https://esotericsoftware.com/spine-editor-license
  *
  * Otherwise, it is permitted to integrate the Spine Runtimes into software
  * or otherwise create derivative works of the Spine Runtimes (collectively,
@@ -37,7 +37,7 @@ namespace Spine {
 	/// Applies animations over time, queues animations for later playback, mixes (crossfading) between animations, and applies
 	/// multiple animations on top of each other (layering).</para>
 	/// <para>
-	/// See <a href='http://esotericsoftware.com/spine-applying-animations/'>Applying Animations</a> in the Spine Runtimes Guide.</para>
+	/// See <see href='https://esotericsoftware.com/spine-applying-animations/'>Applying Animations</a> in the Spine Runtimes Guide.</para>
 	/// </summary>
 	public class AnimationState {
 		internal static readonly Animation EmptyAnimation = new Animation("<empty>", new ExposedList<Timeline>(), 0);
@@ -87,7 +87,7 @@ namespace Spine {
 		internal void OnEvent (TrackEntry entry, Event e) { if (Event != null) Event(entry, e); }
 
 		public delegate void TrackEntryDelegate (TrackEntry trackEntry);
-		/// <summary>See <see href="http://esotericsoftware.com/spine-api-reference#AnimationStateListener-Methods">
+		/// <summary>See <see href="https://esotericsoftware.com/spine-api-reference#AnimationStateListener-Methods">
 		/// API Reference documentation pages here</see> for details. Usage in C# and spine-unity is explained
 		/// <see href="https://esotericsoftware.com/spine-unity-main-components#Processing-AnimationState-Events">here</see>
 		/// on the spine-unity documentation pages.</summary>
@@ -726,7 +726,7 @@ namespace Spine {
 			return AddAnimation(trackIndex, animation, loop, delay);
 		}
 
-		/// <summary>Adds an animation to be played after the current or last queued animation for a track. If the track is empty, it is
+		/// <summary>Adds an animation to be played after the current or last queued animation for a track. If the track has no entries, this is
 		/// equivalent to calling <see cref="SetAnimation(int, Animation, bool)"/>.</summary>
 		/// <param name="delay">
 		/// If &gt; 0, sets <see cref="TrackEntry.Delay"/>. If &lt;= 0, the delay set is the duration of the previous track entry
@@ -774,8 +774,10 @@ namespace Spine {
 		/// <see cref="AnimationState.AddAnimation(int, Animation, bool, float)"/> with the desired delay (an empty animation has a duration of 0) and on
 		/// the returned track entry, set the <see cref="TrackEntry.SetMixDuration(float)"/>. Mixing from an empty animation causes the new
 		/// animation to be applied more and more over the mix duration. Properties keyed in the new animation transition from the value
-		/// from lower tracks or from the setup pose value if no lower tracks key the property to the value keyed in the new
-		/// animation.</para></summary>
+		/// from lower tracks or from the setup pose value if no lower tracks key the property to the value keyed in the new animation.</para>
+		/// <para>
+		/// See  <see href='https://esotericsoftware.com/spine-applying-animations/#Empty-animations'>Empty animations</a> in the Spine
+		/// Runtimes Guide.</para></summary>
 		public TrackEntry SetEmptyAnimation (int trackIndex, float mixDuration) {
 			TrackEntry entry = SetAnimation(trackIndex, AnimationState.EmptyAnimation, false);
 			entry.mixDuration = mixDuration;
@@ -785,7 +787,7 @@ namespace Spine {
 
 		/// <summary>
 		/// Adds an empty animation to be played after the current or last queued animation for a track, and sets the track entry's
-		/// <see cref="TrackEntry.MixDuration"/>. If the track is empty, it is equivalent to calling
+		/// <see cref="TrackEntry.MixDuration"/>. If the track has no entries, this is equivalent to calling
 		/// <see cref="AnimationState.SetEmptyAnimation(int, float)"/>.</summary>
 		/// <seealso cref="AnimationState.SetEmptyAnimation(int, float)"/>
 		/// <param name="trackIndex">Track number.</param>
@@ -967,7 +969,7 @@ namespace Spine {
 		public ExposedList<TrackEntry> Tracks { get { return tracks; } }
 
 		override public string ToString () {
-			System.Text.StringBuilder buffer = new System.Text.StringBuilder();
+			var buffer = new System.Text.StringBuilder();
 			TrackEntry[] tracksItems = tracks.Items;
 			for (int i = 0, n = tracks.Count; i < n; i++) {
 				TrackEntry entry = tracksItems[i];
@@ -991,7 +993,7 @@ namespace Spine {
 
 		internal TrackEntry previous, next, mixingFrom, mixingTo;
 		// difference to libgdx reference: delegates are used for event callbacks instead of 'AnimationStateListener listener'.
-		/// <summary>See <see href="http://esotericsoftware.com/spine-api-reference#AnimationStateListener-Methods">
+		/// <summary>See <see href="https://esotericsoftware.com/spine-api-reference#AnimationStateListener-Methods">
 		/// API Reference documentation pages here</see> for details. Usage in C# and spine-unity is explained
 		/// <see href="https://esotericsoftware.com/spine-unity-main-components#Processing-AnimationState-Events">here</see>
 		/// on the spine-unity documentation pages.</summary>

+ 2 - 2
spine-csharp/src/AnimationStateData.cs

@@ -64,7 +64,7 @@ namespace Spine {
 		public void SetMix (Animation from, Animation to, float duration) {
 			if (from == null) throw new ArgumentNullException("from", "from cannot be null.");
 			if (to == null) throw new ArgumentNullException("to", "to cannot be null.");
-			AnimationPair key = new AnimationPair(from, to);
+			var key = new AnimationPair(from, to);
 			animationToMixTime.Remove(key);
 			animationToMixTime.Add(key, duration);
 		}
@@ -76,7 +76,7 @@ namespace Spine {
 		public float GetMix (Animation from, Animation to) {
 			if (from == null) throw new ArgumentNullException("from", "from cannot be null.");
 			if (to == null) throw new ArgumentNullException("to", "to cannot be null.");
-			AnimationPair key = new AnimationPair(from, to);
+			var key = new AnimationPair(from, to);
 			float duration;
 			if (animationToMixTime.TryGetValue(key, out duration)) return duration;
 			return defaultMix;

+ 2 - 2
spine-csharp/src/Attachments/AtlasAttachmentLoader.cs

@@ -53,7 +53,7 @@ namespace Spine {
 		}
 
 		public RegionAttachment NewRegionAttachment (Skin skin, string name, string path, Sequence sequence) {
-			RegionAttachment attachment = new RegionAttachment(name);
+			var attachment = new RegionAttachment(name);
 			if (sequence != null)
 				LoadSequence(name, path, sequence);
 			else {
@@ -66,7 +66,7 @@ namespace Spine {
 		}
 
 		public MeshAttachment NewMeshAttachment (Skin skin, string name, string path, Sequence sequence) {
-			MeshAttachment attachment = new MeshAttachment(name);
+			var attachment = new MeshAttachment(name);
 			if (sequence != null)
 				LoadSequence(name, path, sequence);
 			else {

+ 1 - 1
spine-csharp/src/Attachments/MeshAttachment.cs

@@ -199,7 +199,7 @@ namespace Spine {
 
 		/// <summary>Returns a new mesh with this mesh set as the <see cref="ParentMesh"/>.
 		public MeshAttachment NewLinkedMesh () {
-			MeshAttachment mesh = new MeshAttachment(Name);
+			var mesh = new MeshAttachment(Name);
 
 			mesh.timelineAttachment = timelineAttachment;
 			mesh.region = region;

+ 1 - 1
spine-csharp/src/Attachments/Sequence.cs

@@ -79,7 +79,7 @@ namespace Spine {
 		}
 
 		public string GetPath (string basePath, int index) {
-			StringBuilder buffer = new StringBuilder(basePath.Length + digits);
+			var buffer = new StringBuilder(basePath.Length + digits);
 			buffer.Append(basePath);
 			string frame = (start + index).ToString();
 			for (int i = digits - frame.Length; i > 0; i--)

+ 6 - 6
spine-csharp/src/PathConstraint.cs

@@ -45,7 +45,7 @@ namespace Spine {
 
 		internal readonly PathConstraintData data;
 		internal readonly ExposedList<Bone> bones;
-		internal Slot target;
+		internal Slot slot;
 		internal float position, spacing, mixRotate, mixX, mixY;
 
 		internal bool active;
@@ -63,7 +63,7 @@ namespace Spine {
 			foreach (BoneData boneData in data.bones)
 				bones.Add(skeleton.bones.Items[boneData.index]);
 
-			target = skeleton.slots.Items[data.target.index];
+			slot = skeleton.slots.Items[data.slot.index];
 
 			position = data.position;
 			spacing = data.spacing;
@@ -98,7 +98,7 @@ namespace Spine {
 		}
 
 		public void Update (Physics physics) {
-			PathAttachment attachment = target.Attachment as PathAttachment;
+			PathAttachment attachment = slot.Attachment as PathAttachment;
 			if (attachment == null) return;
 
 			float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY;
@@ -171,7 +171,7 @@ namespace Spine {
 				tip = data.rotateMode == RotateMode.Chain;
 			} else {
 				tip = false;
-				Bone p = target.bone;
+				Bone p = slot.bone;
 				offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.DegRad : -MathUtils.DegRad;
 			}
 			for (int i = 0, p = 3; i < boneCount; i++, p += 3) {
@@ -223,7 +223,7 @@ namespace Spine {
 		}
 
 		float[] ComputeWorldPositions (PathAttachment path, int spacesCount, bool tangents) {
-			Slot target = this.target;
+			Slot target = this.slot;
 			float position = this.position;
 			float[] spaces = this.spaces.Items, output = this.positions.Resize(spacesCount * 3 + 2).Items, world;
 			bool closed = path.Closed;
@@ -506,7 +506,7 @@ namespace Spine {
 		/// <summary>The bones that will be modified by this path constraint.</summary>
 		public ExposedList<Bone> Bones { get { return bones; } }
 		/// <summary>The slot whose path attachment will be used to constrained the bones.</summary>
-		public Slot Target { get { return target; } set { target = value; } }
+		public Slot Target { get { return slot; } set { slot = value; } }
 		public bool Active { get { return active; } }
 		/// <summary>The path constraint's setup pose data.</summary>
 		public PathConstraintData Data { get { return data; } }

+ 2 - 2
spine-csharp/src/PathConstraintData.cs

@@ -32,7 +32,7 @@ using System;
 namespace Spine {
 	public class PathConstraintData : ConstraintData {
 		internal ExposedList<BoneData> bones = new ExposedList<BoneData>();
-		internal SlotData target;
+		internal SlotData slot;
 		internal PositionMode positionMode;
 		internal SpacingMode spacingMode;
 		internal RotateMode rotateMode;
@@ -43,7 +43,7 @@ namespace Spine {
 		}
 
 		public ExposedList<BoneData> Bones { get { return bones; } }
-		public SlotData Target { get { return target; } set { target = value; } }
+		public SlotData Slot { get { return slot; } set { slot = value; } }
 		public PositionMode PositionMode { get { return positionMode; } set { positionMode = value; } }
 		public SpacingMode SpacingMode { get { return spacingMode; } set { spacingMode = value; } }
 		public RotateMode RotateMode { get { return rotateMode; } set { rotateMode = value; } }

+ 1 - 1
spine-csharp/src/PhysicsConstraint.cs

@@ -39,7 +39,7 @@ namespace Spine {
 	/// </summary>
 	public class PhysicsConstraint : IUpdatable {
 		internal readonly PhysicsConstraintData data;
-		public Bone bone;
+		internal Bone bone;
 		internal float inertia, strength, damping, massInverse, wind, gravity, mix;
 
 		bool reset = true;

+ 7 - 9
spine-csharp/src/Skeleton.cs

@@ -289,8 +289,7 @@ namespace Spine {
 				&& (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data)));
 			if (!constraint.active) return;
 
-			Bone target = constraint.target;
-			SortBone(target);
+			SortBone(constraint.target);
 
 			ExposedList<Bone> constrained = constraint.bones;
 			Bone parent = constrained.Items[0];
@@ -311,15 +310,15 @@ namespace Spine {
 		}
 
 		private void SortTransformConstraint (TransformConstraint constraint) {
-			constraint.active = constraint.target.active
+			constraint.active = constraint.source.active
 				&& (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data)));
 			if (!constraint.active) return;
 
-			SortBone(constraint.target);
+			SortBone(constraint.source);
 
 			Bone[] constrained = constraint.bones.Items;
 			int boneCount = constraint.bones.Count;
-			if (constraint.data.local) {
+			if (constraint.data.localSource) {
 				for (int i = 0; i < boneCount; i++) {
 					Bone child = constrained[i];
 					SortBone(child.parent);
@@ -339,19 +338,18 @@ namespace Spine {
 		}
 
 		private void SortPathConstraint (PathConstraint constraint) {
-			constraint.active = constraint.target.bone.active
+			constraint.active = constraint.slot.bone.active
 				&& (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data)));
 			if (!constraint.active) return;
 
-			Slot slot = constraint.target;
+			Slot slot = constraint.slot;
 			int slotIndex = slot.data.index;
 			Bone slotBone = slot.bone;
 			if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone);
 			if (data.defaultSkin != null && data.defaultSkin != skin)
 				SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone);
 
-			Attachment attachment = slot.attachment;
-			if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone);
+			SortPathConstraintAttachment(slot.attachment, slotBone);
 
 			Bone[] constrained = constraint.bones.Items;
 			int boneCount = constraint.bones.Count;

+ 116 - 64
spine-csharp/src/SkeletonBinary.cs

@@ -42,6 +42,22 @@ using Windows.Storage;
 #endif
 
 namespace Spine {
+
+	using FromProperty = TransformConstraintData.FromProperty;
+	using FromRotate = TransformConstraintData.FromRotate;
+	using FromScaleX = TransformConstraintData.FromScaleX;
+	using FromScaleY = TransformConstraintData.FromScaleY;
+	using FromShearY = TransformConstraintData.FromShearY;
+	using FromX = TransformConstraintData.FromX;
+	using FromY = TransformConstraintData.FromY;
+	using ToProperty = TransformConstraintData.ToProperty;
+	using ToRotate = TransformConstraintData.ToRotate;
+	using ToScaleX = TransformConstraintData.ToScaleX;
+	using ToScaleY = TransformConstraintData.ToScaleY;
+	using ToShearY = TransformConstraintData.ToShearY;
+	using ToX = TransformConstraintData.ToX;
+	using ToY = TransformConstraintData.ToY;
+
 	public class SkeletonBinary : SkeletonLoader {
 		public const int BONE_ROTATE = 0;
 		public const int BONE_TRANSLATE = 1;
@@ -122,8 +138,7 @@ namespace Spine {
 		/// <summary>Returns the version string of binary skeleton data.</summary>
 		public static string GetVersionString (Stream file) {
 			if (file == null) throw new ArgumentNullException("file");
-
-			SkeletonInput input = new SkeletonInput(file);
+			var input = new SkeletonInput(file);
 			return input.GetVersionString();
 		}
 
@@ -131,8 +146,8 @@ namespace Spine {
 			if (file == null) throw new ArgumentNullException("file");
 			float scale = this.scale;
 
-			SkeletonData skeletonData = new SkeletonData();
-			SkeletonInput input = new SkeletonInput(file);
+			var skeletonData = new SkeletonData();
+			var input = new SkeletonInput(file);
 
 			long hash = input.ReadLong();
 			skeletonData.hash = hash == 0 ? null : hash.ToString();
@@ -171,7 +186,7 @@ namespace Spine {
 			for (int i = 0; i < n; i++) {
 				String name = input.ReadString();
 				BoneData parent = i == 0 ? null : bones[input.ReadInt(true)];
-				BoneData data = new BoneData(i, name, parent);
+				var data = new BoneData(i, name, parent);
 				data.rotation = input.ReadFloat();
 				data.x = input.ReadFloat() * scale;
 				data.y = input.ReadFloat() * scale;
@@ -196,7 +211,7 @@ namespace Spine {
 				String slotName = input.ReadString();
 
 				BoneData boneData = bones[input.ReadInt(true)];
-				SlotData slotData = new SlotData(i, slotName, boneData);
+				var slotData = new SlotData(i, slotName, boneData);
 				int color = input.ReadInt();
 				slotData.r = ((color & 0xff000000) >> 24) / 255f;
 				slotData.g = ((color & 0x00ff0000) >> 16) / 255f;
@@ -222,7 +237,7 @@ namespace Spine {
 			// IK constraints.
 			o = skeletonData.ikConstraints.Resize(n = input.ReadInt(true)).Items;
 			for (int i = 0, nn; i < n; i++) {
-				IkConstraintData data = new IkConstraintData(input.ReadString());
+				var data = new IkConstraintData(input.ReadString());
 				data.order = input.ReadInt(true);
 				BoneData[] constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items;
 				for (int ii = 0; ii < nn; ii++)
@@ -242,42 +257,72 @@ namespace Spine {
 			// Transform constraints.
 			o = skeletonData.transformConstraints.Resize(n = input.ReadInt(true)).Items;
 			for (int i = 0, nn; i < n; i++) {
-				TransformConstraintData data = new TransformConstraintData(input.ReadString());
+				var data = new TransformConstraintData(input.ReadString());
 				data.order = input.ReadInt(true);
 				BoneData[] constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items;
 				for (int ii = 0; ii < nn; ii++)
 					constraintBones[ii] = bones[input.ReadInt(true)];
-				data.target = bones[input.ReadInt(true)];
+				data.source = bones[input.ReadInt(true)];
 				int flags = input.Read();
 				data.skinRequired = (flags & 1) != 0;
-				data.local = (flags & 2) != 0;
-				data.relative = (flags & 4) != 0;
-				if ((flags & 8) != 0) data.offsetRotation = input.ReadFloat();
-				if ((flags & 16) != 0) data.offsetX = input.ReadFloat() * scale;
-				if ((flags & 32) != 0) data.offsetY = input.ReadFloat() * scale;
-				if ((flags & 64) != 0) data.offsetScaleX = input.ReadFloat();
-				if ((flags & 128) != 0) data.offsetScaleY = input.ReadFloat();
+				data.localSource = (flags & 2) != 0;
+				data.localTarget = (flags & 4) != 0;
+				data.additive = (flags & 8) != 0;
+				data.clamp = (flags & 16) != 0;
+				FromProperty[] froms = data.properties.Resize(nn = flags >> 5).Items;
+				for (int ii = 0, tn; ii < nn; ii++) {
+					FromProperty from;
+					switch (input.ReadSByte()) {
+					case 0: from = new FromRotate(); break;
+					case 1: from = new FromX(); break;
+					case 2: from = new FromY(); break;
+					case 3: from = new FromScaleX(); break;
+					case 4: from = new FromScaleY(); break;
+					case 5: from = new FromShearY(); break;
+					default: from = null; break;
+					};
+					from.offset = input.ReadFloat() * scale;
+					ToProperty[] tos = from.to.Resize(tn = input.ReadSByte()).Items;
+					for (int t = 0; t < tn; t++) {
+						ToProperty to;
+						switch (input.ReadSByte()) {
+						case 0: to = new ToRotate(); break;
+						case 1: to = new ToX(); break;
+						case 2: to = new ToY(); break;
+						case 3: to = new ToScaleX(); break;
+						case 4: to = new ToScaleY(); break;
+						case 5: to = new ToShearY(); break;
+						default: to = null; break;
+						};
+						to.offset = input.ReadFloat() * scale;
+						to.max = input.ReadFloat() * scale;
+						to.scale = input.ReadFloat();
+						tos[t] = to;
+					}
+					froms[ii] = from;
+				}
 				flags = input.Read();
-				if ((flags & 1) != 0) data.offsetShearY = input.ReadFloat();
-				if ((flags & 2) != 0) data.mixRotate = input.ReadFloat();
-				if ((flags & 4) != 0) data.mixX = input.ReadFloat();
-				if ((flags & 8) != 0) data.mixY = input.ReadFloat();
-				if ((flags & 16) != 0) data.mixScaleX = input.ReadFloat();
-				if ((flags & 32) != 0) data.mixScaleY = input.ReadFloat();
-				if ((flags & 64) != 0) data.mixShearY = input.ReadFloat();
+				if ((flags & 1) != 0) data.offsetX = input.ReadFloat();
+				if ((flags & 2) != 0) data.offsetY = input.ReadFloat();
+				if ((flags & 4) != 0) data.mixRotate = input.ReadFloat();
+				if ((flags & 8) != 0) data.mixX = input.ReadFloat();
+				if ((flags & 16) != 0) data.mixY = input.ReadFloat();
+				if ((flags & 32) != 0) data.mixScaleX = input.ReadFloat();
+				if ((flags & 64) != 0) data.mixScaleY = input.ReadFloat();
+				if ((flags & 128) != 0) data.mixShearY = input.ReadFloat();
 				o[i] = data;
 			}
 
 			// Path constraints
 			o = skeletonData.pathConstraints.Resize(n = input.ReadInt(true)).Items;
 			for (int i = 0, nn; i < n; i++) {
-				PathConstraintData data = new PathConstraintData(input.ReadString());
+				var data = new PathConstraintData(input.ReadString());
 				data.order = input.ReadInt(true);
 				data.skinRequired = input.ReadBoolean();
 				BoneData[] constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items;
 				for (int ii = 0; ii < nn; ii++)
 					constraintBones[ii] = bones[input.ReadInt(true)];
-				data.target = slots[input.ReadInt(true)];
+				data.slot = slots[input.ReadInt(true)];
 				int flags = input.Read();
 				data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(flags & 1);
 				data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue((flags >> 1) & 3);
@@ -297,7 +342,7 @@ namespace Spine {
 			// Physics constraints.
 			o = skeletonData.physicsConstraints.Resize(n = input.ReadInt(true)).Items;
 			for (int i = 0; i < n; i++) {
-				PhysicsConstraintData data = new PhysicsConstraintData(input.ReadString());
+				var data = new PhysicsConstraintData(input.ReadString());
 				data.order = input.ReadInt(true);
 				data.bone = bones[input.ReadInt(true)];
 				int flags = input.Read();
@@ -358,7 +403,7 @@ namespace Spine {
 			// Events.
 			o = skeletonData.events.Resize(n = input.ReadInt(true)).Items;
 			for (int i = 0; i < n; i++) {
-				EventData data = new EventData(input.ReadString());
+				var data = new EventData(input.ReadString());
 				data.Int = input.ReadInt(false);
 				data.Float = input.ReadFloat();
 				data.String = input.ReadString();
@@ -547,7 +592,7 @@ namespace Spine {
 				bool closed = (flags & 16) != 0;
 				bool constantSpeed = (flags & 32) != 0;
 				Vertices vertices = ReadVertices(input, (flags & 64) != 0);
-				float[] lengths = new float[vertices.length / 6];
+				var lengths = new float[vertices.length / 6];
 				for (int i = 0, n = lengths.Length; i < n; i++)
 					lengths[i] = input.ReadFloat() * scale;
 				if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0;
@@ -596,7 +641,7 @@ namespace Spine {
 		}
 
 		private Sequence ReadSequence (SkeletonInput input) {
-			Sequence sequence = new Sequence(input.ReadInt(true));
+			var sequence = new Sequence(input.ReadInt(true));
 			sequence.Start = input.ReadInt(true);
 			sequence.Digits = input.ReadInt(true);
 			sequence.SetupIndex = input.ReadInt(true);
@@ -606,14 +651,14 @@ namespace Spine {
 		private Vertices ReadVertices (SkeletonInput input, bool weighted) {
 			float scale = this.scale;
 			int vertexCount = input.ReadInt(true);
-			Vertices vertices = new Vertices();
+			var vertices = new Vertices();
 			vertices.length = vertexCount << 1;
 			if (!weighted) {
 				vertices.vertices = ReadFloatArray(input, vertices.length, scale);
 				return vertices;
 			}
-			ExposedList<float> weights = new ExposedList<float>(vertices.length * 3 * 3);
-			ExposedList<int> bonesArray = new ExposedList<int>(vertices.length * 3);
+			var weights = new ExposedList<float>(vertices.length * 3 * 3);
+			var bonesArray = new ExposedList<int>(vertices.length * 3);
 			for (int i = 0; i < vertexCount; i++) {
 				int boneCount = input.ReadInt(true);
 				bonesArray.Add(boneCount);
@@ -631,7 +676,7 @@ namespace Spine {
 		}
 
 		private float[] ReadFloatArray (SkeletonInput input, int n, float scale) {
-			float[] array = new float[n];
+			var array = new float[n];
 			if (scale == 1) {
 				for (int i = 0; i < n; i++)
 					array[i] = input.ReadFloat();
@@ -643,7 +688,7 @@ namespace Spine {
 		}
 
 		private int[] ReadShortArray (SkeletonInput input, int n) {
-			int[] array = new int[n];
+			var array = new int[n];
 			for (int i = 0; i < n; i++)
 				array[i] = input.ReadInt(true);
 			return array;
@@ -652,7 +697,7 @@ namespace Spine {
 		/// <exception cref="SerializationException">SerializationException will be thrown when a Vertex attachment is not found.</exception>
 		/// <exception cref="IOException">Throws IOException when a read operation fails.</exception>
 		private Animation ReadAnimation (String name, SkeletonInput input, SkeletonData skeletonData) {
-			ExposedList<Timeline> timelines = new ExposedList<Timeline>(input.ReadInt(true));
+			var timelines = new ExposedList<Timeline>(input.ReadInt(true));
 			float scale = this.scale;
 
 			// Slot timelines.
@@ -662,14 +707,14 @@ namespace Spine {
 					int timelineType = input.ReadUByte(), frameCount = input.ReadInt(true), frameLast = frameCount - 1;
 					switch (timelineType) {
 					case SLOT_ATTACHMENT: {
-						AttachmentTimeline timeline = new AttachmentTimeline(frameCount, slotIndex);
+						var timeline = new AttachmentTimeline(frameCount, slotIndex);
 						for (int frame = 0; frame < frameCount; frame++)
 							timeline.SetFrame(frame, input.ReadFloat(), input.ReadStringRef());
 						timelines.Add(timeline);
 						break;
 					}
 					case SLOT_RGBA: {
-						RGBATimeline timeline = new RGBATimeline(frameCount, input.ReadInt(true), slotIndex);
+						var timeline = new RGBATimeline(frameCount, input.ReadInt(true), slotIndex);
 						float time = input.ReadFloat();
 						float r = input.Read() / 255f, g = input.Read() / 255f;
 						float b = input.Read() / 255f, a = input.Read() / 255f;
@@ -700,7 +745,7 @@ namespace Spine {
 						break;
 					}
 					case SLOT_RGB: {
-						RGBTimeline timeline = new RGBTimeline(frameCount, input.ReadInt(true), slotIndex);
+						var timeline = new RGBTimeline(frameCount, input.ReadInt(true), slotIndex);
 						float time = input.ReadFloat();
 						float r = input.Read() / 255f, g = input.Read() / 255f, b = input.Read() / 255f;
 						for (int frame = 0, bezier = 0; ; frame++) {
@@ -727,7 +772,7 @@ namespace Spine {
 						break;
 					}
 					case SLOT_RGBA2: {
-						RGBA2Timeline timeline = new RGBA2Timeline(frameCount, input.ReadInt(true), slotIndex);
+						var timeline = new RGBA2Timeline(frameCount, input.ReadInt(true), slotIndex);
 						float time = input.ReadFloat();
 						float r = input.Read() / 255f, g = input.Read() / 255f;
 						float b = input.Read() / 255f, a = input.Read() / 255f;
@@ -766,7 +811,7 @@ namespace Spine {
 						break;
 					}
 					case SLOT_RGB2: {
-						RGB2Timeline timeline = new RGB2Timeline(frameCount, input.ReadInt(true), slotIndex);
+						var timeline = new RGB2Timeline(frameCount, input.ReadInt(true), slotIndex);
 						float time = input.ReadFloat();
 						float r = input.Read() / 255f, g = input.Read() / 255f, b = input.Read() / 255f;
 						float r2 = input.Read() / 255f, g2 = input.Read() / 255f, b2 = input.Read() / 255f;
@@ -801,7 +846,7 @@ namespace Spine {
 						break;
 					}
 					case SLOT_ALPHA: {
-						AlphaTimeline timeline = new AlphaTimeline(frameCount, input.ReadInt(true), slotIndex);
+						var timeline = new AlphaTimeline(frameCount, input.ReadInt(true), slotIndex);
 						float time = input.ReadFloat(), a = input.Read() / 255f;
 						for (int frame = 0, bezier = 0; ; frame++) {
 							timeline.SetFrame(frame, time, a);
@@ -832,7 +877,7 @@ namespace Spine {
 				for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) {
 					int type = input.ReadUByte(), frameCount = input.ReadInt(true);
 					if (type == BONE_INHERIT) {
-						InheritTimeline timeline = new InheritTimeline(frameCount, boneIndex);
+						var timeline = new InheritTimeline(frameCount, boneIndex);
 						for (int frame = 0; frame < frameCount; frame++)
 							timeline.SetFrame(frame, input.ReadFloat(), InheritEnum.Values[input.ReadUByte()]);
 						timelines.Add(timeline);
@@ -877,7 +922,7 @@ namespace Spine {
 			// IK constraint timelines.
 			for (int i = 0, n = input.ReadInt(true); i < n; i++) {
 				int index = input.ReadInt(true), frameCount = input.ReadInt(true), frameLast = frameCount - 1;
-				IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount, input.ReadInt(true), index);
+				var timeline = new IkConstraintTimeline(frameCount, input.ReadInt(true), index);
 				int flags = input.Read();
 				float time = input.ReadFloat(), mix = (flags & 1) != 0 ? ((flags & 2) != 0 ? input.ReadFloat() : 1) : 0;
 				float softness = (flags & 4) != 0 ? input.ReadFloat() * scale : 0;
@@ -904,7 +949,7 @@ namespace Spine {
 			// Transform constraint timelines.
 			for (int i = 0, n = input.ReadInt(true); i < n; i++) {
 				int index = input.ReadInt(true), frameCount = input.ReadInt(true), frameLast = frameCount - 1;
-				TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount, input.ReadInt(true), index);
+				var timeline = new TransformConstraintTimeline(frameCount, input.ReadInt(true), index);
 				float time = input.ReadFloat(), mixRotate = input.ReadFloat(), mixX = input.ReadFloat(), mixY = input.ReadFloat(),
 				mixScaleX = input.ReadFloat(), mixScaleY = input.ReadFloat(), mixShearY = input.ReadFloat();
 				for (int frame = 0, bezier = 0; ; frame++) {
@@ -943,16 +988,18 @@ namespace Spine {
 				for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) {
 					int type = input.ReadUByte(), frameCount = input.ReadInt(true), bezierCount = input.ReadInt(true);
 					switch (type) {
-					case PATH_POSITION:
+					case PATH_POSITION: {
 						ReadTimeline(input, timelines, new PathConstraintPositionTimeline(frameCount, bezierCount, index),
 							data.positionMode == PositionMode.Fixed ? scale : 1);
 						break;
-					case PATH_SPACING:
+					}
+					case PATH_SPACING: {
 						ReadTimeline(input, timelines, new PathConstraintSpacingTimeline(frameCount, bezierCount, index),
 							data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed ? scale : 1);
 						break;
-					case PATH_MIX:
-						PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount, bezierCount, index);
+					}
+					case PATH_MIX: {
+						var timeline = new PathConstraintMixTimeline(frameCount, bezierCount, index);
 						float time = input.ReadFloat(), mixRotate = input.ReadFloat(), mixX = input.ReadFloat(), mixY = input.ReadFloat();
 						for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 1; ; frame++) {
 							timeline.SetFrame(frame, time, mixRotate, mixX, mixY);
@@ -977,6 +1024,7 @@ namespace Spine {
 						timelines.Add(timeline);
 						break;
 					}
+					}
 				}
 			}
 
@@ -986,36 +1034,40 @@ namespace Spine {
 				for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) {
 					int type = input.ReadUByte(), frameCount = input.ReadInt(true);
 					if (type == PHYSICS_RESET) {
-						PhysicsConstraintResetTimeline timeline = new PhysicsConstraintResetTimeline(frameCount, index);
+						var timeline = new PhysicsConstraintResetTimeline(frameCount, index);
 						for (int frame = 0; frame < frameCount; frame++)
 							timeline.SetFrame(frame, input.ReadFloat());
 						timelines.Add(timeline);
 						continue;
 					}
 					int bezierCount = input.ReadInt(true);
+					PhysicsConstraintTimeline newTimeline;
 					switch (type) {
 					case PHYSICS_INERTIA:
-						ReadTimeline(input, timelines, new PhysicsConstraintInertiaTimeline(frameCount, bezierCount, index), 1);
+						newTimeline = new PhysicsConstraintInertiaTimeline(frameCount, bezierCount, index);
 						break;
 					case PHYSICS_STRENGTH:
-						ReadTimeline(input, timelines, new PhysicsConstraintStrengthTimeline(frameCount, bezierCount, index), 1);
+						newTimeline = new PhysicsConstraintStrengthTimeline(frameCount, bezierCount, index);
 						break;
 					case PHYSICS_DAMPING:
-						ReadTimeline(input, timelines, new PhysicsConstraintDampingTimeline(frameCount, bezierCount, index), 1);
+						newTimeline = new PhysicsConstraintDampingTimeline(frameCount, bezierCount, index);
 						break;
 					case PHYSICS_MASS:
-						ReadTimeline(input, timelines, new PhysicsConstraintMassTimeline(frameCount, bezierCount, index), 1);
+						newTimeline = new PhysicsConstraintMassTimeline(frameCount, bezierCount, index);
 						break;
 					case PHYSICS_WIND:
-						ReadTimeline(input, timelines, new PhysicsConstraintWindTimeline(frameCount, bezierCount, index), 1);
+						newTimeline = new PhysicsConstraintWindTimeline(frameCount, bezierCount, index);
 						break;
 					case PHYSICS_GRAVITY:
-						ReadTimeline(input, timelines, new PhysicsConstraintGravityTimeline(frameCount, bezierCount, index), 1);
+						newTimeline = new PhysicsConstraintGravityTimeline(frameCount, bezierCount, index);
 						break;
 					case PHYSICS_MIX:
-						ReadTimeline(input, timelines, new PhysicsConstraintMixTimeline(frameCount, bezierCount, index), 1);
+						newTimeline = new PhysicsConstraintMixTimeline(frameCount, bezierCount, index);
 						break;
+					default:
+						throw new SerializationException();
 					}
+					ReadTimeline(input, timelines, newTimeline, 1);
 				}
 			}
 
@@ -1037,7 +1089,7 @@ namespace Spine {
 							float[] vertices = vertexAttachment.Vertices;
 							int deformLength = weighted ? (vertices.Length / 3) << 1 : vertices.Length;
 
-							DeformTimeline timeline = new DeformTimeline(frameCount, input.ReadInt(true), slotIndex, vertexAttachment);
+							var timeline = new DeformTimeline(frameCount, input.ReadInt(true), slotIndex, vertexAttachment);
 
 							float time = input.ReadFloat();
 							for (int frame = 0, bezier = 0; ; frame++) {
@@ -1078,7 +1130,7 @@ namespace Spine {
 							break;
 						}
 						case ATTACHMENT_SEQUENCE: {
-							SequenceTimeline timeline = new SequenceTimeline(frameCount, slotIndex, attachment);
+							var timeline = new SequenceTimeline(frameCount, slotIndex, attachment);
 							for (int frame = 0; frame < frameCount; frame++) {
 								float time = input.ReadFloat();
 								int modeAndIndex = input.ReadInt();
@@ -1096,15 +1148,15 @@ namespace Spine {
 			// Draw order timeline.
 			int drawOrderCount = input.ReadInt(true);
 			if (drawOrderCount > 0) {
-				DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount);
+				var timeline = new DrawOrderTimeline(drawOrderCount);
 				int slotCount = skeletonData.slots.Count;
 				for (int i = 0; i < drawOrderCount; i++) {
 					float time = input.ReadFloat();
 					int offsetCount = input.ReadInt(true);
-					int[] drawOrder = new int[slotCount];
+					var drawOrder = new int[slotCount];
 					for (int ii = slotCount - 1; ii >= 0; ii--)
 						drawOrder[ii] = -1;
-					int[] unchanged = new int[slotCount - offsetCount];
+					var unchanged = new int[slotCount - offsetCount];
 					int originalIndex = 0, unchangedIndex = 0;
 					for (int ii = 0; ii < offsetCount; ii++) {
 						int slotIndex = input.ReadInt(true);
@@ -1128,11 +1180,11 @@ namespace Spine {
 			// Event timeline.
 			int eventCount = input.ReadInt(true);
 			if (eventCount > 0) {
-				EventTimeline timeline = new EventTimeline(eventCount);
+				var timeline = new EventTimeline(eventCount);
 				for (int i = 0; i < eventCount; i++) {
 					float time = input.ReadFloat();
 					EventData eventData = skeletonData.events.Items[input.ReadInt(true)];
-					Event e = new Event(time, eventData);
+					var e = new Event(time, eventData);
 					e.intValue = input.ReadInt(false);
 					e.floatValue = input.ReadFloat();
 					e.stringValue = input.ReadString();
@@ -1353,7 +1405,7 @@ namespace Spine {
 				byteCount = ReadInt(true);
 				if (byteCount > 1 && byteCount <= 13) {
 					byteCount--;
-					byte[] buffer = new byte[byteCount];
+					var buffer = new byte[byteCount];
 					ReadFully(buffer, 0, byteCount);
 					return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount);
 				}

+ 226 - 99
spine-csharp/src/SkeletonJson.cs

@@ -42,6 +42,21 @@ using Windows.Storage;
 
 namespace Spine {
 
+	using FromProperty = TransformConstraintData.FromProperty;
+	using FromRotate = TransformConstraintData.FromRotate;
+	using FromScaleX = TransformConstraintData.FromScaleX;
+	using FromScaleY = TransformConstraintData.FromScaleY;
+	using FromShearY = TransformConstraintData.FromShearY;
+	using FromX = TransformConstraintData.FromX;
+	using FromY = TransformConstraintData.FromY;
+	using ToProperty = TransformConstraintData.ToProperty;
+	using ToRotate = TransformConstraintData.ToRotate;
+	using ToScaleX = TransformConstraintData.ToScaleX;
+	using ToScaleY = TransformConstraintData.ToScaleY;
+	using ToShearY = TransformConstraintData.ToShearY;
+	using ToX = TransformConstraintData.ToX;
+	using ToY = TransformConstraintData.ToY;
+
 	/// <summary>
 	/// Loads skeleton data in the Spine JSON format.
 	/// <para>
@@ -94,7 +109,7 @@ namespace Spine {
 			if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null.");
 
 			float scale = this.scale;
-			SkeletonData skeletonData = new SkeletonData();
+			var skeletonData = new SkeletonData();
 
 			Dictionary<string, object> root = Json.Deserialize(reader) as Dictionary<string, Object>;
 			if (root == null) throw new Exception("Invalid JSON.");
@@ -123,7 +138,7 @@ namespace Spine {
 						if (parent == null)
 							throw new Exception("Parent bone not found: " + boneMap["parent"]);
 					}
-					BoneData data = new BoneData(skeletonData.Bones.Count, (string)boneMap["name"], parent);
+					var data = new BoneData(skeletonData.Bones.Count, (string)boneMap["name"], parent);
 					data.length = GetFloat(boneMap, "length", 0) * scale;
 					data.x = GetFloat(boneMap, "x", 0) * scale;
 					data.y = GetFloat(boneMap, "y", 0) * scale;
@@ -148,7 +163,7 @@ namespace Spine {
 					string boneName = (string)slotMap["bone"];
 					BoneData boneData = skeletonData.FindBone(boneName);
 					if (boneData == null) throw new Exception("Slot bone not found: " + boneName);
-					SlotData data = new SlotData(skeletonData.Slots.Count, slotName, boneData);
+					var data = new SlotData(skeletonData.Slots.Count, slotName, boneData);
 
 					if (slotMap.ContainsKey("color")) {
 						string color = (string)slotMap["color"];
@@ -179,7 +194,7 @@ namespace Spine {
 			// IK constraints.
 			if (root.ContainsKey("ik")) {
 				foreach (Dictionary<string, Object> constraintMap in (List<Object>)root["ik"]) {
-					IkConstraintData data = new IkConstraintData((string)constraintMap["name"]);
+					var data = new IkConstraintData((string)constraintMap["name"]);
 					data.order = GetInt(constraintMap, "order", 0);
 					data.skinRequired = GetBoolean(constraintMap, "skin", false);
 
@@ -208,7 +223,7 @@ namespace Spine {
 			// Transform constraints.
 			if (root.ContainsKey("transform")) {
 				foreach (Dictionary<string, Object> constraintMap in (List<Object>)root["transform"]) {
-					TransformConstraintData data = new TransformConstraintData((string)constraintMap["name"]);
+					var data = new TransformConstraintData((string)constraintMap["name"]);
 					data.order = GetInt(constraintMap, "order", 0);
 					data.skinRequired = GetBoolean(constraintMap, "skin", false);
 
@@ -220,26 +235,89 @@ namespace Spine {
 						}
 					}
 
-					string targetName = (string)constraintMap["target"];
-					data.target = skeletonData.FindBone(targetName);
-					if (data.target == null) throw new Exception("Transform constraint target bone not found: " + targetName);
-
-					data.local = GetBoolean(constraintMap, "local", false);
-					data.relative = GetBoolean(constraintMap, "relative", false);
-
-					data.offsetRotation = GetFloat(constraintMap, "rotation", 0);
-					data.offsetX = GetFloat(constraintMap, "x", 0) * scale;
-					data.offsetY = GetFloat(constraintMap, "y", 0) * scale;
-					data.offsetScaleX = GetFloat(constraintMap, "scaleX", 0);
-					data.offsetScaleY = GetFloat(constraintMap, "scaleY", 0);
-					data.offsetShearY = GetFloat(constraintMap, "shearY", 0);
+					string sourceName = (string)constraintMap["source"];
+					data.source = skeletonData.FindBone(sourceName);
+					if (data.source == null) throw new Exception("Transform constraint source bone not found: " + sourceName);
+
+					data.localSource = GetBoolean(constraintMap, "localSource", false);
+					data.additive = GetBoolean(constraintMap, "additive", false);
+					data.clamp = GetBoolean(constraintMap, "clamp", false);
+
+					bool rotate = false, x = false, y = false, scaleX = false, scaleY = false, shearY = false;
+					if (constraintMap.ContainsKey("properties")) {
+						foreach (KeyValuePair<string, Object> fromEntryObject in (Dictionary<string, Object>)constraintMap["properties"]) {
+							var fromEntry = (Dictionary<string, Object>)fromEntryObject.Value;
+							string fromEntryName = fromEntryObject.Key;
+
+							FromProperty from;
+							switch (fromEntryName) {
+							case "rotate": from = new FromRotate(); break;
+							case "x": from = new FromX(); break;
+							case "y": from = new FromY(); break;
+							case "scaleX": from = new FromScaleX(); break;
+							case "scaleY": from = new FromScaleY(); break;
+							case "shearY": from = new FromShearY(); break;
+							default: throw new Exception("Invalid transform constraint from property: " + fromEntryName);
+							};
+
+							from.offset = GetFloat(fromEntry, "offset", 0) * scale;
+							if (fromEntry.ContainsKey("to")) {
+								foreach (KeyValuePair<string, Object> toEntryObject in (Dictionary<string, Object>)fromEntry["to"]) {
+									var toEntry = (Dictionary<string, Object>)toEntryObject.Value;
+									string toEntryName = toEntryObject.Key;
+
+									ToProperty to;
+									switch (toEntryName) {
+									case "rotate": {
+										rotate = true;
+										to = new ToRotate();
+										break;
+									}
+									case "x": {
+										x = true;
+										to = new ToX();
+										break;
+									}
+									case "y": {
+										y = true;
+										to = new ToY();
+										break;
+									}
+									case "scaleX": {
+										scaleX = true;
+										to = new ToScaleX();
+										break;
+									}
+									case "scaleY": {
+										scaleY = true;
+										to = new ToScaleY();
+										break;
+									}
+									case "shearY": {
+										shearY = true;
+										to = new ToShearY();
+										break;
+									}
+									default: throw new Exception("Invalid transform constraint to property: " + toEntryName);
+									}
+									to.offset = GetFloat(toEntry, "offset", 0) * scale;
+									to.max = GetFloat(toEntry, "max", 1) * scale;
+									to.scale = GetFloat(toEntry, "scale");
+									from.to.Add(to);
+								}
+							}
+							if (from.to.Count != 0) data.properties.Add(from);
+						}
+					}
 
-					data.mixRotate = GetFloat(constraintMap, "mixRotate", 1);
-					data.mixX = GetFloat(constraintMap, "mixX", 1);
-					data.mixY = GetFloat(constraintMap, "mixY", data.mixX);
-					data.mixScaleX = GetFloat(constraintMap, "mixScaleX", 1);
-					data.mixScaleY = GetFloat(constraintMap, "mixScaleY", data.mixScaleX);
-					data.mixShearY = GetFloat(constraintMap, "mixShearY", 1);
+					data.offsetX = GetFloat(constraintMap, "x", 0);
+					data.offsetY = GetFloat(constraintMap, "y", 0);
+					if (rotate) data.mixRotate = GetFloat(constraintMap, "mixRotate", 1);
+					if (x) data.mixX = GetFloat(constraintMap, "mixX", 1);
+					if (y) data.mixY = GetFloat(constraintMap, "mixY", data.mixX);
+					if (scaleX) data.mixScaleX = GetFloat(constraintMap, "mixScaleX", 1);
+					if (scaleY) data.mixScaleY = GetFloat(constraintMap, "mixScaleY", data.mixScaleX);
+					if (shearY) data.mixShearY = GetFloat(constraintMap, "mixShearY", 1);
 
 					skeletonData.transformConstraints.Add(data);
 				}
@@ -248,7 +326,7 @@ namespace Spine {
 			// Path constraints.
 			if (root.ContainsKey("path")) {
 				foreach (Dictionary<string, Object> constraintMap in (List<Object>)root["path"]) {
-					PathConstraintData data = new PathConstraintData((string)constraintMap["name"]);
+					var data = new PathConstraintData((string)constraintMap["name"]);
 					data.order = GetInt(constraintMap, "order", 0);
 					data.skinRequired = GetBoolean(constraintMap, "skin", false);
 
@@ -260,9 +338,9 @@ namespace Spine {
 						}
 					}
 
-					string targetName = (string)constraintMap["target"];
-					data.target = skeletonData.FindSlot(targetName);
-					if (data.target == null) throw new Exception("Path target slot not found: " + targetName);
+					string slotName = (string)constraintMap["slot"];
+					data.slot = skeletonData.FindSlot(slotName);
+					if (data.slot == null) throw new Exception("Path slot not found: " + slotName);
 
 					data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true);
 					data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true);
@@ -283,7 +361,7 @@ namespace Spine {
 			// Physics constraints.
 			if (root.ContainsKey("physics")) {
 				foreach (Dictionary<string, Object> constraintMap in (List<Object>)root["physics"]) {
-					PhysicsConstraintData data = new PhysicsConstraintData((string)constraintMap["name"]);
+					var data = new PhysicsConstraintData((string)constraintMap["name"]);
 					data.order = GetInt(constraintMap, "order", 0);
 					data.skinRequired = GetBoolean(constraintMap, "skin", false);
 
@@ -320,7 +398,7 @@ namespace Spine {
 			// Skins.
 			if (root.ContainsKey("skins")) {
 				foreach (Dictionary<string, object> skinMap in (List<object>)root["skins"]) {
-					Skin skin = new Skin((string)skinMap["name"]);
+					var skin = new Skin((string)skinMap["name"]);
 					if (skinMap.ContainsKey("bones")) {
 						foreach (string entryName in (List<Object>)skinMap["bones"]) {
 							BoneData bone = skeletonData.FindBone(entryName);
@@ -393,7 +471,7 @@ namespace Spine {
 			if (root.ContainsKey("events")) {
 				foreach (KeyValuePair<string, Object> entry in (Dictionary<string, Object>)root["events"]) {
 					Dictionary<string, object> entryMap = (Dictionary<string, Object>)entry.Value;
-					EventData data = new EventData(entry.Key);
+					var data = new EventData(entry.Key);
 					data.Int = GetInt(entryMap, "int", 0);
 					data.Float = GetFloat(entryMap, "float", 0);
 					data.String = GetString(entryMap, "string", string.Empty);
@@ -553,7 +631,7 @@ namespace Spine {
 		public static Sequence ReadSequence (object sequenceJson) {
 			Dictionary<string, object> map = sequenceJson as Dictionary<string, Object>;
 			if (map == null) return null;
-			Sequence sequence = new Sequence(GetInt(map, "count"));
+			var sequence = new Sequence(GetInt(map, "count"));
 			sequence.start = GetInt(map, "start", 1);
 			sequence.digits = GetInt(map, "digits", 0);
 			sequence.setupIndex = GetInt(map, "setup", 0);
@@ -573,8 +651,8 @@ namespace Spine {
 				attachment.vertices = vertices;
 				return;
 			}
-			ExposedList<float> weights = new ExposedList<float>(verticesLength * 3 * 3);
-			ExposedList<int> bones = new ExposedList<int>(verticesLength * 3);
+			var weights = new ExposedList<float>(verticesLength * 3 * 3);
+			var bones = new ExposedList<int>(verticesLength * 3);
 			for (int i = 0, n = vertices.Length; i < n;) {
 				int boneCount = (int)vertices[i++];
 				bones.Add(boneCount);
@@ -598,7 +676,7 @@ namespace Spine {
 
 		private void ReadAnimation (Dictionary<string, Object> map, string name, SkeletonData skeletonData) {
 			float scale = this.scale;
-			ExposedList<Timeline> timelines = new ExposedList<Timeline>();
+			var timelines = new ExposedList<Timeline>();
 
 			// Slot timelines.
 			if (map.ContainsKey("slots")) {
@@ -611,16 +689,18 @@ namespace Spine {
 						int frames = values.Count;
 						if (frames == 0) continue;
 						string timelineName = (string)timelineEntry.Key;
-						if (timelineName == "attachment") {
-							AttachmentTimeline timeline = new AttachmentTimeline(frames, slotIndex);
+						switch (timelineName) {
+						case "attachment": {
+							var timeline = new AttachmentTimeline(frames, slotIndex);
 							int frame = 0;
 							foreach (Dictionary<string, Object> keyMap in values) {
 								timeline.SetFrame(frame++, GetFloat(keyMap, "time", 0), GetString(keyMap, "name", null));
 							}
 							timelines.Add(timeline);
-
-						} else if (timelineName == "rgba") {
-							RGBATimeline timeline = new RGBATimeline(frames, frames << 2, slotIndex);
+							break;
+						}
+						case "rgba": {
+							var timeline = new RGBATimeline(frames, frames << 2, slotIndex);
 
 							List<object>.Enumerator keyMapEnumerator = values.GetEnumerator();
 							keyMapEnumerator.MoveNext();
@@ -661,9 +741,10 @@ namespace Spine {
 								keyMap = nextMap;
 							}
 							timelines.Add(timeline);
-
-						} else if (timelineName == "rgb") {
-							RGBTimeline timeline = new RGBTimeline(frames, frames * 3, slotIndex);
+							break;
+						}
+						case "rgb": {
+							var timeline = new RGBTimeline(frames, frames * 3, slotIndex);
 
 							List<object>.Enumerator keyMapEnumerator = values.GetEnumerator();
 							keyMapEnumerator.MoveNext();
@@ -700,14 +781,16 @@ namespace Spine {
 								keyMap = nextMap;
 							}
 							timelines.Add(timeline);
-
-						} else if (timelineName == "alpha") {
+							break;
+						}
+						case "alpha": {
 							List<object>.Enumerator keyMapEnumerator = values.GetEnumerator();
 							keyMapEnumerator.MoveNext();
 							timelines.Add(ReadTimeline(ref keyMapEnumerator, new AlphaTimeline(frames, frames, slotIndex), 0, 1));
-
-						} else if (timelineName == "rgba2") {
-							RGBA2Timeline timeline = new RGBA2Timeline(frames, frames * 7, slotIndex);
+							break;
+						}
+						case "rgba2": {
+							var timeline = new RGBA2Timeline(frames, frames * 7, slotIndex);
 
 							List<object>.Enumerator keyMapEnumerator = values.GetEnumerator();
 							keyMapEnumerator.MoveNext();
@@ -762,9 +845,10 @@ namespace Spine {
 								keyMap = nextMap;
 							}
 							timelines.Add(timeline);
-
-						} else if (timelineName == "rgb2") {
-							RGB2Timeline timeline = new RGB2Timeline(frames, frames * 6, slotIndex);
+							break;
+						}
+						case "rgb2": {
+							var timeline = new RGB2Timeline(frames, frames * 6, slotIndex);
 
 							List<object>.Enumerator keyMapEnumerator = values.GetEnumerator();
 							keyMapEnumerator.MoveNext();
@@ -815,9 +899,11 @@ namespace Spine {
 								keyMap = nextMap;
 							}
 							timelines.Add(timeline);
-
-						} else
+							break;
+						}
+						default:
 							throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")");
+						}
 					}
 				}
 			}
@@ -842,33 +928,46 @@ namespace Spine {
 						if (!keyMapEnumerator.MoveNext()) continue;
 						int frames = values.Count;
 						string timelineName = (string)timelineEntry.Key;
-						if (timelineName == "rotate")
+						switch (timelineName) {
+						case "rotate":
 							timelines.Add(ReadTimeline(ref keyMapEnumerator, new RotateTimeline(frames, frames, boneIndex), 0, 1));
-						else if (timelineName == "translate") {
-							TranslateTimeline timeline = new TranslateTimeline(frames, frames << 1, boneIndex);
-							timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 0, scale));
-						} else if (timelineName == "translatex") {
+							break;
+						case "translate": {
+							timelines.Add(ReadTimeline(ref keyMapEnumerator, new TranslateTimeline(frames, frames << 1, boneIndex), "x", "y", 0, scale));
+							break;
+						}
+						case "translatex": {
 							timelines
 								.Add(ReadTimeline(ref keyMapEnumerator, new TranslateXTimeline(frames, frames, boneIndex), 0, scale));
-						} else if (timelineName == "translatey") {
+							break;
+						}
+						case "translatey": {
 							timelines
 								.Add(ReadTimeline(ref keyMapEnumerator, new TranslateYTimeline(frames, frames, boneIndex), 0, scale));
-						} else if (timelineName == "scale") {
-							ScaleTimeline timeline = new ScaleTimeline(frames, frames << 1, boneIndex);
-							timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 1, 1));
-						} else if (timelineName == "scalex")
+							break;
+						}
+						case "scale": {
+							timelines.Add(ReadTimeline(ref keyMapEnumerator, new ScaleTimeline(frames, frames << 1, boneIndex), "x", "y", 1, 1));
+							break;
+						}
+						case "scalex":
 							timelines.Add(ReadTimeline(ref keyMapEnumerator, new ScaleXTimeline(frames, frames, boneIndex), 1, 1));
-						else if (timelineName == "scaley")
+							break;
+						case "scaley":
 							timelines.Add(ReadTimeline(ref keyMapEnumerator, new ScaleYTimeline(frames, frames, boneIndex), 1, 1));
-						else if (timelineName == "shear") {
-							ShearTimeline timeline = new ShearTimeline(frames, frames << 1, boneIndex);
-							timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, "x", "y", 0, 1));
-						} else if (timelineName == "shearx")
+							break;
+						case "shear": {
+							timelines.Add(ReadTimeline(ref keyMapEnumerator, new ShearTimeline(frames, frames << 1, boneIndex), "x", "y", 0, 1));
+							break;
+						}
+						case "shearx":
 							timelines.Add(ReadTimeline(ref keyMapEnumerator, new ShearXTimeline(frames, frames, boneIndex), 0, 1));
-						else if (timelineName == "sheary")
+							break;
+						case "sheary":
 							timelines.Add(ReadTimeline(ref keyMapEnumerator, new ShearYTimeline(frames, frames, boneIndex), 0, 1));
-						else if (timelineName == "inherit") {
-							InheritTimeline timeline = new InheritTimeline(frames, boneIndex);
+							break;
+						case "inherit": {
+							var timeline = new InheritTimeline(frames, boneIndex);
 							for (int frame = 0; ; frame++) {
 								Dictionary<string, object> keyMap = (Dictionary<string, Object>)keyMapEnumerator.Current;
 								float time = GetFloat(keyMap, "time", 0);
@@ -879,8 +978,11 @@ namespace Spine {
 								}
 							}
 							timelines.Add(timeline);
-						} else
+							break;
+						}
+						default:
 							throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")");
+						}
 					}
 				}
 			}
@@ -893,7 +995,7 @@ namespace Spine {
 					if (!keyMapEnumerator.MoveNext()) continue;
 					Dictionary<string, object> keyMap = (Dictionary<string, Object>)keyMapEnumerator.Current;
 					IkConstraintData constraint = skeletonData.FindIkConstraint(timelineMap.Key);
-					IkConstraintTimeline timeline = new IkConstraintTimeline(values.Count, values.Count << 1,
+					var timeline = new IkConstraintTimeline(values.Count, values.Count << 1,
 						skeletonData.IkConstraints.IndexOf(constraint));
 					float time = GetFloat(keyMap, "time", 0);
 					float mix = GetFloat(keyMap, "mix", 1), softness = GetFloat(keyMap, "softness", 0) * scale;
@@ -929,7 +1031,7 @@ namespace Spine {
 					if (!keyMapEnumerator.MoveNext()) continue;
 					Dictionary<string, object> keyMap = (Dictionary<string, Object>)keyMapEnumerator.Current;
 					TransformConstraintData constraint = skeletonData.FindTransformConstraint(timelineMap.Key);
-					TransformConstraintTimeline timeline = new TransformConstraintTimeline(values.Count, values.Count * 6,
+					var timeline = new TransformConstraintTimeline(values.Count, values.Count * 6,
 						skeletonData.TransformConstraints.IndexOf(constraint));
 					float time = GetFloat(keyMap, "time", 0);
 					float mixRotate = GetFloat(keyMap, "mixRotate", 1), mixShearY = GetFloat(keyMap, "mixShearY", 1);
@@ -982,15 +1084,20 @@ namespace Spine {
 
 						int frames = values.Count;
 						string timelineName = (string)timelineEntry.Key;
-						if (timelineName == "position") {
+						switch (timelineName) {
+						case "position": {
 							CurveTimeline1 timeline = new PathConstraintPositionTimeline(frames, frames, constraintIndex);
 							timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, 0, constraint.positionMode == PositionMode.Fixed ? scale : 1));
-						} else if (timelineName == "spacing") {
+							break;
+						}
+						case "spacing": {
 							CurveTimeline1 timeline = new PathConstraintSpacingTimeline(frames, frames, constraintIndex);
 							timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, 0,
 								constraint.spacingMode == SpacingMode.Length || constraint.spacingMode == SpacingMode.Fixed ? scale : 1));
-						} else if (timelineName == "mix") {
-							PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frames, frames * 3, constraintIndex);
+							break;
+						}
+						case "mix": {
+							var timeline = new PathConstraintMixTimeline(frames, frames * 3, constraintIndex);
 							Dictionary<string, object> keyMap = (Dictionary<string, Object>)keyMapEnumerator.Current;
 							float time = GetFloat(keyMap, "time", 0);
 							float mixRotate = GetFloat(keyMap, "mixRotate", 1);
@@ -1018,6 +1125,8 @@ namespace Spine {
 								keyMap = nextMap;
 							}
 							timelines.Add(timeline);
+							break;
+						}
 						}
 					}
 				}
@@ -1040,33 +1149,41 @@ namespace Spine {
 
 						int frames = values.Count;
 						string timelineName = (string)timelineEntry.Key;
-						if (timelineName == "reset") {
-							PhysicsConstraintResetTimeline timeline1 = new PhysicsConstraintResetTimeline(frames, index);
+						CurveTimeline1 timeline;
+						switch (timelineName) {
+						case "reset": {
+							var resetTimeline = new PhysicsConstraintResetTimeline(frames, index);
 							int frame = 0;
 							foreach (Dictionary<string, Object> keyMap in values) {
-								timeline1.SetFrame(frame++, GetFloat(keyMap, "time", 0));
+								resetTimeline.SetFrame(frame++, GetFloat(keyMap, "time", 0));
 							}
-							timelines.Add(timeline1);
+							timelines.Add(resetTimeline);
 							continue;
 						}
-
-						CurveTimeline1 timeline;
-						if (timelineName == "inertia")
+						case "inertia":
 							timeline = new PhysicsConstraintInertiaTimeline(frames, frames, index);
-						else if (timelineName == "strength")
+							break;
+						case "strength":
 							timeline = new PhysicsConstraintStrengthTimeline(frames, frames, index);
-						else if (timelineName == "damping")
+							break;
+						case "damping":
 							timeline = new PhysicsConstraintDampingTimeline(frames, frames, index);
-						else if (timelineName == "mass")
+							break;
+						case "mass":
 							timeline = new PhysicsConstraintMassTimeline(frames, frames, index);
-						else if (timelineName == "wind")
+							break;
+						case "wind":
 							timeline = new PhysicsConstraintWindTimeline(frames, frames, index);
-						else if (timelineName == "gravity")
+							break;
+						case "gravity":
 							timeline = new PhysicsConstraintGravityTimeline(frames, frames, index);
-						else if (timelineName == "mix") //
+							break;
+						case "mix":
 							timeline = new PhysicsConstraintMixTimeline(frames, frames, index);
-						else
+							break;
+						default:
 							continue;
+						}
 						timelines.Add(ReadTimeline(ref keyMapEnumerator, timeline, 0, 1));
 					}
 				}
@@ -1089,13 +1206,14 @@ namespace Spine {
 								Dictionary<string, object> keyMap = (Dictionary<string, Object>)keyMapEnumerator.Current;
 								int frames = values.Count;
 								string timelineName = timelineMap.Key;
-								if (timelineName == "deform") {
+								switch (timelineName) {
+								case "deform": {
 									VertexAttachment vertexAttachment = (VertexAttachment)attachment;
 									bool weighted = vertexAttachment.bones != null;
 									float[] vertices = vertexAttachment.vertices;
 									int deformLength = weighted ? (vertices.Length / 3) << 1 : vertices.Length;
 
-									DeformTimeline timeline = new DeformTimeline(frames, frames, slot.Index, vertexAttachment);
+									var timeline = new DeformTimeline(frames, frames, slot.Index, vertexAttachment);
 									float time = GetFloat(keyMap, "time", 0);
 									for (int frame = 0, bezier = 0; ; frame++) {
 										float[] deform;
@@ -1132,8 +1250,10 @@ namespace Spine {
 										keyMap = nextMap;
 									}
 									timelines.Add(timeline);
-								} else if (timelineName == "sequence") {
-									SequenceTimeline timeline = new SequenceTimeline(frames, slot.index, attachment);
+									break;
+								}
+								case "sequence": {
+									var timeline = new SequenceTimeline(frames, slot.index, attachment);
 									float lastDelay = 0;
 									for (int frame = 0; keyMap != null; keyMap = keyMapEnumerator.MoveNext() ?
 										(Dictionary<string, Object>)keyMapEnumerator.Current : null, frame++) {
@@ -1146,6 +1266,8 @@ namespace Spine {
 										lastDelay = delay;
 									}
 									timelines.Add(timeline);
+									break;
+								}
 								}
 							}
 						}
@@ -1156,7 +1278,7 @@ namespace Spine {
 			// Draw order timeline.
 			if (map.ContainsKey("drawOrder")) {
 				List<object> values = (List<Object>)map["drawOrder"];
-				DrawOrderTimeline timeline = new DrawOrderTimeline(values.Count);
+				var timeline = new DrawOrderTimeline(values.Count);
 				int slotCount = skeletonData.slots.Count;
 				int frame = 0;
 				foreach (Dictionary<string, Object> keyMap in values) {
@@ -1193,12 +1315,12 @@ namespace Spine {
 			// Event timeline.
 			if (map.ContainsKey("events")) {
 				List<object> eventsMap = (List<Object>)map["events"];
-				EventTimeline timeline = new EventTimeline(eventsMap.Count);
+				var timeline = new EventTimeline(eventsMap.Count);
 				int frame = 0;
 				foreach (Dictionary<string, Object> keyMap in eventsMap) {
 					EventData eventData = skeletonData.FindEvent((string)keyMap["name"]);
 					if (eventData == null) throw new Exception("Event not found: " + keyMap["name"]);
-					Event e = new Event(GetFloat(keyMap, "time", 0), eventData) {
+					var e = new Event(GetFloat(keyMap, "time", 0), eventData) {
 						intValue = GetInt(keyMap, "int", eventData.Int),
 						floatValue = GetFloat(keyMap, "float", eventData.Float),
 						stringValue = GetString(keyMap, "string", eventData.String)
@@ -1319,6 +1441,11 @@ namespace Spine {
 			return (float)map[name];
 		}
 
+		static float GetFloat (Dictionary<string, Object> map, string name) {
+			if (!map.ContainsKey(name)) throw new ArgumentException("Named value not found: " + name);
+			return (float)map[name];
+		}
+
 		static int GetInt (Dictionary<string, Object> map, string name, int defaultValue) {
 			if (!map.ContainsKey(name)) return defaultValue;
 			return (int)(float)map[name];

+ 34 - 193
spine-csharp/src/TransformConstraint.cs

@@ -30,19 +30,21 @@
 using System;
 
 namespace Spine {
+	using FromProperty = TransformConstraintData.FromProperty;
 	using Physics = Skeleton.Physics;
+	using ToProperty = TransformConstraintData.ToProperty;
 
 	/// <summary>
 	/// <para>
 	/// Stores the current pose for a transform constraint. A transform constraint adjusts the world transform of the constrained
-	/// bones to match that of the target bone.</para>
+	/// bones to match that of the source bone.</para>
 	/// <para>
 	/// See <a href="http://esotericsoftware.com/spine-transform-constraints">Transform constraints</a> in the Spine User Guide.</para>
 	/// </summary>
 	public class TransformConstraint : IUpdatable {
 		internal readonly TransformConstraintData data;
 		internal readonly ExposedList<Bone> bones;
-		internal Bone target;
+		internal Bone source;
 		internal float mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY;
 
 		internal bool active;
@@ -56,7 +58,7 @@ namespace Spine {
 			foreach (BoneData boneData in data.bones)
 				bones.Add(skeleton.bones.Items[boneData.index]);
 
-			target = skeleton.bones.Items[data.target.index];
+			source = skeleton.bones.Items[data.source.index];
 
 			mixRotate = data.mixRotate;
 			mixX = data.mixX;
@@ -90,205 +92,44 @@ namespace Spine {
 
 		public void Update (Physics physics) {
 			if (mixRotate == 0 && mixX == 0 && mixY == 0 && mixScaleX == 0 && mixScaleY == 0 && mixShearY == 0) return;
-			if (data.local) {
-				if (data.relative)
-					ApplyRelativeLocal();
-				else
-					ApplyAbsoluteLocal();
-			} else {
-				if (data.relative)
-					ApplyRelativeWorld();
-				else
-					ApplyAbsoluteWorld();
-			}
-		}
-
-		void ApplyAbsoluteWorld () {
-			float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX,
-			mixScaleY = this.mixScaleY, mixShearY = this.mixShearY;
-			bool translate = mixX != 0 || mixY != 0;
-
-			Bone target = this.target;
-			float ta = target.a, tb = target.b, tc = target.c, td = target.d;
-			float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad;
-			float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect;
-
-			Bone[] bones = this.bones.Items;
-			for (int i = 0, n = this.bones.Count; i < n; i++) {
-				Bone bone = bones[i];
-
-				if (mixRotate != 0) {
-					float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
-					float r = MathUtils.Atan2(tc, ta) - MathUtils.Atan2(c, a) + offsetRotation;
-					if (r > MathUtils.PI)
-						r -= MathUtils.PI2;
-					else if (r < -MathUtils.PI) //
-						r += MathUtils.PI2;
-					r *= mixRotate;
-					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 (translate) {
-					float tx, ty; //Vector2 temp = this.temp;
-					target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY));
-					bone.worldX += (tx - bone.worldX) * mixX;
-					bone.worldY += (ty - bone.worldY) * mixY;
-				}
-
-				if (mixScaleX != 0) {
-					float s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c);
-					if (s != 0) s = (s + ((float)Math.Sqrt(ta * ta + tc * tc) - s + data.offsetScaleX) * mixScaleX) / s;
-					bone.a *= s;
-					bone.c *= s;
-				}
-				if (mixScaleY != 0) {
-					float s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d);
-					if (s != 0) s = (s + ((float)Math.Sqrt(tb * tb + td * td) - s + data.offsetScaleY) * mixScaleY) / s;
-					bone.b *= s;
-					bone.d *= s;
-				}
-
-				if (mixShearY > 0) {
-					float b = bone.b, d = bone.d;
-					float by = MathUtils.Atan2(d, b);
-					float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta) - (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) * mixShearY;
-					float s = (float)Math.Sqrt(b * b + d * d);
-					bone.b = MathUtils.Cos(r) * s;
-					bone.d = MathUtils.Sin(r) * s;
-				}
-
-				bone.UpdateAppliedTransform();
-			}
-		}
-
-		void ApplyRelativeWorld () {
-			float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX,
-			mixScaleY = this.mixScaleY, mixShearY = this.mixShearY;
-			bool translate = mixX != 0 || mixY != 0;
-
-			Bone target = this.target;
-			float ta = target.a, tb = target.b, tc = target.c, td = target.d;
-			float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad;
-			float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect;
 
+			TransformConstraintData data = this.data;
+			bool localFrom = data.localSource, localTarget = data.localTarget, additive = data.additive, clamp = data.clamp;
+			Bone source = this.source;
+			FromProperty[] fromItems = data.properties.Items;
+			int fn = data.properties.Count;
 			Bone[] bones = this.bones.Items;
 			for (int i = 0, n = this.bones.Count; i < n; i++) {
-				Bone bone = bones[i];
-
-				if (mixRotate != 0) {
-					float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
-					float r = MathUtils.Atan2(tc, ta) + offsetRotation;
-					if (r > MathUtils.PI)
-						r -= MathUtils.PI2;
-					else if (r < -MathUtils.PI) //
-						r += MathUtils.PI2;
-					r *= mixRotate;
-					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;
+				var bone = bones[i];
+				for (int f = 0; f < fn; f++) {
+					FromProperty from = fromItems[f];
+					float value = from.Value(data, source, localFrom) - from.offset;
+					ToProperty[] toItems = from.to.Items;
+					for (int t = 0, tn = from.to.Count; t < tn; t++) {
+						var to = (ToProperty)toItems[t];
+						if (to.Mix(this) != 0) {
+							float clamped = to.offset + value * to.scale;
+							if (clamp) {
+								if (to.offset < to.max)
+									clamped = MathUtils.Clamp(clamped, to.offset, to.max);
+								else
+									clamped = MathUtils.Clamp(clamped, to.max, to.offset);
+							}
+							to.Apply(this, bone, clamped, localTarget, additive);
+						}
+					}
 				}
-
-				if (translate) {
-					float tx, ty; //Vector2 temp = this.temp;
-					target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY));
-					bone.worldX += tx * mixX;
-					bone.worldY += ty * mixY;
-				}
-
-				if (mixScaleX != 0) {
-					float s = ((float)Math.Sqrt(ta * ta + tc * tc) - 1 + data.offsetScaleX) * mixScaleX + 1;
-					bone.a *= s;
-					bone.c *= s;
-				}
-				if (mixScaleY != 0) {
-					float s = ((float)Math.Sqrt(tb * tb + td * td) - 1 + data.offsetScaleY) * mixScaleY + 1;
-					bone.b *= s;
-					bone.d *= s;
-				}
-
-				if (mixShearY > 0) {
-					float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta);
-					if (r > MathUtils.PI)
-						r -= MathUtils.PI2;
-					else if (r < -MathUtils.PI) //
-						r += MathUtils.PI2;
-					float b = bone.b, d = bone.d;
-					r = MathUtils.Atan2(d, b) + (r - MathUtils.PI / 2 + offsetShearY) * mixShearY;
-					float s = (float)Math.Sqrt(b * b + d * d);
-					bone.b = MathUtils.Cos(r) * s;
-					bone.d = MathUtils.Sin(r) * s;
-				}
-
-				bone.UpdateAppliedTransform();
-			}
-		}
-
-		void ApplyAbsoluteLocal () {
-			float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX,
-			mixScaleY = this.mixScaleY, mixShearY = this.mixShearY;
-
-			Bone target = this.target;
-
-			Bone[] bones = this.bones.Items;
-			for (int i = 0, n = this.bones.Count; i < n; i++) {
-				Bone bone = bones[i];
-
-				float rotation = bone.arotation;
-				if (mixRotate != 0) rotation += (target.arotation - rotation + data.offsetRotation) * mixRotate;
-
-				float x = bone.ax, y = bone.ay;
-				x += (target.ax - x + data.offsetX) * mixX;
-				y += (target.ay - y + data.offsetY) * mixY;
-
-				float scaleX = bone.ascaleX, scaleY = bone.ascaleY;
-				if (mixScaleX != 0 && scaleX != 0)
-					scaleX = (scaleX + (target.ascaleX - scaleX + data.offsetScaleX) * mixScaleX) / scaleX;
-				if (mixScaleY != 0 && scaleY != 0)
-					scaleY = (scaleY + (target.ascaleY - scaleY + data.offsetScaleY) * mixScaleY) / scaleY;
-
-				float shearY = bone.ashearY;
-				if (mixShearY != 0) shearY += (target.ashearY - shearY + data.offsetShearY) * mixShearY;
-
-				bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY);
-			}
-		}
-
-		void ApplyRelativeLocal () {
-			float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY, mixScaleX = this.mixScaleX,
-			mixScaleY = this.mixScaleY, mixShearY = this.mixShearY;
-
-			Bone target = this.target;
-
-			Bone[] bones = this.bones.Items;
-			for (int i = 0, n = this.bones.Count; i < n; i++) {
-				Bone bone = bones[i];
-
-				float rotation = bone.arotation + (target.arotation + data.offsetRotation) * mixRotate;
-				float x = bone.ax + (target.ax + data.offsetX) * mixX;
-				float y = bone.ay + (target.ay + data.offsetY) * mixY;
-				float scaleX = bone.ascaleX * (((target.ascaleX - 1 + data.offsetScaleX) * mixScaleX) + 1);
-				float scaleY = bone.ascaleY * (((target.ascaleY - 1 + data.offsetScaleY) * mixScaleY) + 1);
-				float shearY = bone.ashearY + (target.ashearY + data.offsetShearY) * mixShearY;
-
-				bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY);
+				if (localTarget)
+					bone.Update(Skeleton.Physics.None); // note: reference implementation passes null, ignored parameter
+				else
+					bone.UpdateAppliedTransform();
 			}
 		}
 
 		/// <summary>The bones that will be modified by this transform constraint.</summary>
 		public ExposedList<Bone> Bones { get { return bones; } }
-		/// <summary>The target bone whose world transform will be copied to the constrained bones.</summary>
-		public Bone Target { get { return target; } set { target = value; } }
+		/// <summary>The bone whose world transform will be copied to the constrained bones.</summary>
+		public Bone Source { get { return source; } set { source = value; } }
 		/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained rotation.</summary>
 		public float MixRotate { get { return mixRotate; } set { mixRotate = value; } }
 		/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained translation X.</summary>

+ 235 - 14
spine-csharp/src/TransformConstraintData.cs

@@ -31,14 +31,31 @@ using System;
 
 namespace Spine {
 	public class TransformConstraintData : ConstraintData {
-		internal ExposedList<BoneData> bones = new ExposedList<BoneData>();
-		internal BoneData target;
-		internal float mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY;
-		internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY;
-		internal bool relative, local;
+		internal readonly ExposedList<BoneData> bones = new ExposedList<BoneData>();
+		internal BoneData source;
+		internal float offsetX, offsetY, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY;
+		internal bool localSource, localTarget, additive, clamp;
+		internal readonly ExposedList<FromProperty> properties = new ExposedList<FromProperty>();
+
+		public TransformConstraintData (string name) : base(name) {
+		}
 
 		public ExposedList<BoneData> Bones { get { return bones; } }
-		public BoneData Target { get { return target; } set { target = value; } }
+
+		/// <summary>The bone whose world transform will be copied to the constrained bones.</summary>
+		public BoneData Source {
+			get { return source; }
+			set {
+				if (source == null) throw new ArgumentNullException("Source", "source cannot be null.");
+				source = value;
+			}
+		}
+
+		/// <summary>The mapping of transform properties to other transform properties.</summary>
+		public ExposedList<FromProperty> Properties {
+			get { return properties; }
+		}
+
 		/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained rotation.</summary>
 		public float MixRotate { get { return mixRotate; } set { mixRotate = value; } }
 		/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained translation X.</summary>
@@ -51,18 +68,222 @@ namespace Spine {
 		public float MixScaleY { get { return mixScaleY; } set { mixScaleY = value; } }
 		/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained shear Y.</summary>
 		public float MixShearY { get { return mixShearY; } set { mixShearY = value; } }
-
-		public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } }
+		/// <summary>An offset added to the constrained bone X translation.</summary>
 		public float OffsetX { get { return offsetX; } set { offsetX = value; } }
+		/// <summary>An offset added to the constrained bone Y translation.</summary>
 		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; } }
+		/// <summary>Reads the source bone's local transform instead of its world transform.</summary>
+		public bool LocalSource { get { return localSource; } set { localSource = value; } }
+		/// <summary>Sets the constrained bones' local transforms instead of their world transforms.</summary>
+		public bool LocalTarget { get { return localTarget; } set { localTarget = value; } }
+		/// <summary>Adds the source bone transform to the constrained bones instead of setting it absolutely.</summary>
+		public bool Additive { get { return additive; } set { additive = value; } }
+		/// <summary>Prevents constrained bones from exceeding the ranged defined by <see cref="ToProperty.offset"/> and
+		/// <see cref="ToProperty.max"/>.</summary>
+		public bool Clamp { get { return clamp; } set { clamp = value; } }
 
-		public bool Relative { get { return relative; } set { relative = value; } }
-		public bool Local { get { return local; } set { local = value; } }
+		/// <summary>Source property for a <see cref="TransformConstraint"/>.</summary>
+		abstract public class FromProperty {
+			/// <summary>The value of this property that corresponds to <see cref="ToProperty.offset"/>.</summary>
+			public float offset;
 
-		public TransformConstraintData (string name) : base(name) {
+			/// <summary>Constrained properties.</summary>
+			public readonly ExposedList<ToProperty> to = new ExposedList<ToProperty>();
+
+			/// <summary>Reads this property from the specified bone.</summary>
+			abstract public float Value (TransformConstraintData data, Bone source, bool local);
+		}
+
+		///<summary>Constrained property for a <see cref="TransformConstraint"/>.</summary>
+		abstract public class ToProperty {
+			/// <summary>The value of this property that corresponds to <see cref="FromProperty.offset"/>.</summary>
+			public float offset;
+
+			/// <summary>The maximum value of this property when <see cref="TransformConstraintData.clamp"/> clamped.</summary>
+			public float max;
+
+			/// <summary>The scale of the <see cref="FromProperty"/> value in relation to this property.</summary>
+			public float scale;
+
+			/// <summary>Reads the mix for this property from the specified constraint.</summary>
+			public abstract float Mix (TransformConstraint constraint);
+
+			/// <summary>Applies the value to this property.</summary>
+			public abstract void Apply (TransformConstraint constraint, Bone bone, float value, bool local, bool additive);
+		}
+
+		public class FromRotate : FromProperty {
+			public override float Value (TransformConstraintData data, Bone source, bool local) {
+				return local ? source.arotation : MathUtils.Atan2(source.c, source.a) * MathUtils.RadDeg;
+			}
+		}
+
+		public class ToRotate : ToProperty {
+			public override float Mix (TransformConstraint constraint) {
+				return constraint.mixRotate;
+			}
+
+			public override void Apply (TransformConstraint constraint, Bone bone, float value, bool local, bool additive) {
+				if (local) {
+					if (!additive) value -= bone.arotation;
+					bone.arotation += value * constraint.mixRotate;
+				} else {
+					float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
+					value *= MathUtils.DegRad;
+					if (!additive) value -= MathUtils.Atan2(c, a);
+					if (value > MathUtils.PI)
+						value -= MathUtils.PI2;
+					else if (value < -MathUtils.PI) //
+						value += MathUtils.PI2;
+					value *= constraint.mixRotate;
+					float cos = MathUtils.Cos(value), sin = MathUtils.Sin(value);
+					bone.a = cos * a - sin * c;
+					bone.b = cos * b - sin * d;
+					bone.c = sin * a + cos * c;
+					bone.d = sin * b + cos * d;
+				}
+			}
+		}
+
+		public class FromX : FromProperty {
+			public override float Value (TransformConstraintData data, Bone source, bool local) {
+				return local ? source.ax + data.offsetX : data.offsetX * source.a + data.offsetY * source.b + source.worldX;
+			}
+		}
+
+		public class ToX : ToProperty {
+			public override float Mix (TransformConstraint constraint) {
+				return constraint.mixX;
+			}
+
+			public override void Apply (TransformConstraint constraint, Bone bone, float value, bool local, bool additive) {
+				if (local) {
+					if (!additive) value -= bone.ax;
+					bone.ax += value * constraint.mixX;
+				} else {
+					if (!additive) value -= bone.worldX;
+					bone.worldX += value * constraint.mixX;
+				}
+			}
+		}
+
+		public class FromY : FromProperty {
+			public override float Value (TransformConstraintData data, Bone source, bool local) {
+				return local ? source.ay + data.offsetY : data.offsetX * source.c + data.offsetY * source.d + source.worldY;
+			}
+		}
+
+		public class ToY : ToProperty {
+			public override float Mix (TransformConstraint constraint) {
+				return constraint.mixY;
+			}
+
+			public override void Apply (TransformConstraint constraint, Bone bone, float value, bool local, bool additive) {
+				if (local) {
+					if (!additive) value -= bone.ay;
+					bone.ay += value * constraint.mixY;
+				} else {
+					if (!additive) value -= bone.worldY;
+					bone.worldY += value * constraint.mixY;
+				}
+			}
+		}
+
+		public class FromScaleX : FromProperty {
+			public override float Value (TransformConstraintData data, Bone source, bool local) {
+				return local ? source.ascaleX : (float)Math.Sqrt(source.a * source.a + source.c * source.c);
+			}
+		}
+
+		public class ToScaleX : ToProperty {
+			public override float Mix (TransformConstraint constraint) {
+				return constraint.mixScaleX;
+			}
+
+			public override void Apply (TransformConstraint constraint, Bone bone, float value, bool local, bool additive) {
+				if (local) {
+					if (additive)
+						bone.ascaleX *= 1 + ((value - 1) * constraint.mixScaleX);
+					else if (bone.ascaleX != 0) //
+						bone.ascaleX = 1 + (value / bone.ascaleX - 1) * constraint.mixScaleX;
+				} else {
+					float s;
+					if (additive)
+						s = 1 + (value - 1) * constraint.mixScaleX;
+					else {
+						s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c);
+						if (s != 0) s = 1 + (value / s - 1) * constraint.mixScaleX;
+					}
+					bone.a *= s;
+					bone.c *= s;
+				}
+			}
+		}
+
+		public class FromScaleY : FromProperty {
+			public override float Value (TransformConstraintData data, Bone source, bool local) {
+				return local ? source.ascaleY : (float)Math.Sqrt(source.b * source.b + source.d * source.d);
+			}
+		}
+
+		public class ToScaleY : ToProperty {
+			public override float Mix (TransformConstraint constraint) {
+				return constraint.mixScaleY;
+			}
+
+			public override void Apply (TransformConstraint constraint, Bone bone, float value, bool local, bool additive) {
+				if (local) {
+					if (additive)
+						bone.ascaleY *= 1 + ((value - 1) * constraint.mixScaleY);
+					else if (bone.ascaleY != 0) //
+						bone.ascaleY = 1 + (value / bone.ascaleY - 1) * constraint.mixScaleY;
+				} else {
+					float s;
+					if (additive)
+						s = 1 + (value - 1) * constraint.mixScaleY;
+					else {
+						s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d);
+						if (s != 0) s = 1 + (value / s - 1) * constraint.mixScaleY;
+					}
+					bone.b *= s;
+					bone.d *= s;
+				}
+			}
+		}
+
+		public class FromShearY : FromProperty {
+			public override float Value (TransformConstraintData data, Bone source, bool local) {
+				return local ? source.ashearY : (MathUtils.Atan2(source.d, source.b) - MathUtils.Atan2(source.c, source.a)) * MathUtils.RadDeg - 90;
+			}
+		}
+
+		public class ToShearY : ToProperty {
+			public override float Mix (TransformConstraint constraint) {
+				return constraint.mixShearY;
+			}
+
+			public override void Apply (TransformConstraint constraint, Bone bone, float value, bool local, bool additive) {
+				if (local) {
+					if (!additive) value -= bone.ashearY;
+					bone.ashearY += value * constraint.mixShearY;
+				} else {
+					float b = bone.b, d = bone.d, by = MathUtils.Atan2(d, b);
+					value = (value + 90) * MathUtils.DegRad;
+					if (additive)
+						value -= MathUtils.PI / 2;
+					else {
+						value -= by - MathUtils.Atan2(bone.c, bone.a);
+						if (value > MathUtils.PI)
+							value -= MathUtils.PI2;
+						else if (value < -MathUtils.PI) //
+							value += MathUtils.PI2;
+					}
+					value = by + value * constraint.mixShearY;
+					float s = (float)Math.Sqrt(b * b + d * d);
+					bone.b = MathUtils.Cos(value) * s;
+					bone.d = MathUtils.Sin(value) * s;
+				}
+			}
 		}
 	}
 }

+ 3 - 3
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineHandles.cs

@@ -397,8 +397,8 @@ namespace Spine.Unity.Editor {
 			// Transform Constraints
 			handleColor = SpineHandles.TransformContraintColor;
 			foreach (TransformConstraint tc in skeleton.TransformConstraints) {
-				Bone targetBone = tc.Target;
-				targetPos = targetBone.GetWorldPosition(transform, skeletonRenderScale, offset);
+				Bone sourceBone = tc.Source;
+				targetPos = sourceBone.GetWorldPosition(transform, skeletonRenderScale, offset);
 
 				if (tc.MixX > 0 || tc.MixY > 0) {
 					if ((tc.MixX > 0 && tc.MixX != 1f) ||
@@ -411,7 +411,7 @@ namespace Spine.Unity.Editor {
 					}
 					SpineHandles.DrawBoneCircle(targetPos, handleColor, normal, 1.3f * skeletonRenderScale);
 					Handles.color = handleColor;
-					SpineHandles.DrawCrosshairs(targetPos, 0.2f, targetBone.A, targetBone.B, targetBone.C, targetBone.D, transform, skeletonRenderScale);
+					SpineHandles.DrawCrosshairs(targetPos, 0.2f, sourceBone.A, sourceBone.B, sourceBone.C, sourceBone.D, transform, skeletonRenderScale);
 				}
 			}
 

+ 2 - 2
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SkeletonDebugWindow.cs

@@ -415,7 +415,7 @@ namespace Spine.Unity.Editor {
 									foreach (TransformConstraint c in skeleton.TransformConstraints) {
 										EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(c.Data.Name, Icons.constraintTransform));
 										EditorGUI.BeginDisabledGroup(true);
-										FalseDropDown("Goal", c.Data.Target.Name, Icons.bone);
+										FalseDropDown("Source", c.Data.Source.Name, Icons.bone);
 										EditorGUI.EndDisabledGroup();
 
 										EditorGUI.BeginChangeCheck();
@@ -469,7 +469,7 @@ namespace Spine.Unity.Editor {
 									foreach (PathConstraint c in skeleton.PathConstraints) {
 										EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(c.Data.Name, Icons.constraintPath));
 										EditorGUI.BeginDisabledGroup(true);
-										FalseDropDown("Path Slot", c.Data.Target.Name, Icons.slot);
+										FalseDropDown("Path Slot", c.Data.Slot.Name, Icons.slot);
 										Attachment activeAttachment = c.Target.Attachment;
 										FalseDropDown("Active Path", activeAttachment != null ? activeAttachment.Name : "<None>", activeAttachment is PathAttachment ? Icons.path : null);
 										EditorGUILayout.LabelField("PositionMode." + c.Data.PositionMode);

+ 11 - 11
spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotionBase.cs

@@ -446,8 +446,8 @@ namespace Spine.Unity {
 			if (useLastConstraintPos)
 				constraintPos = transformConstraintLastPos[GetConstraintLastPosIndex(constraintIndex)];
 			else {
-				Bone targetBone = constraint.Target;
-				constraintPos = new Vector2(targetBone.X, targetBone.Y);
+				Bone sourceBone = constraint.Source;
+				constraintPos = new Vector2(sourceBone.X, sourceBone.Y);
 			}
 			pos = new Vector2(
 				pos.x * invMixXY.x + constraintPos.x * mixXY.x,
@@ -465,8 +465,8 @@ namespace Spine.Unity {
 			if (useLastConstraintRotation)
 				constraintRotation = transformConstraintLastRotation[GetConstraintLastPosIndex(constraintIndex)];
 			else {
-				Bone targetBone = constraint.Target;
-				constraintRotation = targetBone.Rotation;
+				Bone sourceBone = constraint.Source;
+				constraintRotation = sourceBone.Rotation;
 			}
 			rotation = rotation * invMixRotate + constraintRotation * mixRotate;
 		}
@@ -474,16 +474,16 @@ namespace Spine.Unity {
 		void UpdateLastConstraintPos (TransformConstraint[] transformConstraintsItems) {
 			foreach (int constraintIndex in this.transformConstraintIndices) {
 				TransformConstraint constraint = transformConstraintsItems[constraintIndex];
-				Bone targetBone = constraint.Target;
-				transformConstraintLastPos[GetConstraintLastPosIndex(constraintIndex)] = new Vector2(targetBone.X, targetBone.Y);
+				Bone sourceBone = constraint.Source;
+				transformConstraintLastPos[GetConstraintLastPosIndex(constraintIndex)] = new Vector2(sourceBone.X, sourceBone.Y);
 			}
 		}
 
 		void UpdateLastConstraintRotation (TransformConstraint[] transformConstraintsItems) {
 			foreach (int constraintIndex in this.transformConstraintIndices) {
 				TransformConstraint constraint = transformConstraintsItems[constraintIndex];
-				Bone targetBone = constraint.Target;
-				transformConstraintLastRotation[GetConstraintLastPosIndex(constraintIndex)] = targetBone.Rotation;
+				Bone sourceBone = constraint.Source;
+				transformConstraintLastRotation[GetConstraintLastPosIndex(constraintIndex)] = sourceBone.Rotation;
 			}
 		}
 
@@ -524,10 +524,10 @@ namespace Spine.Unity {
 				TransformConstraint constraint = constraintsItems[i];
 				if (constraint.Bones.Contains(rootMotionBone)) {
 					transformConstraintIndices.Add(i);
-					Bone targetBone = constraint.Target;
-					Vector2 constraintPos = new Vector2(targetBone.X, targetBone.Y);
+					Bone sourceBone = constraint.Source;
+					Vector2 constraintPos = new Vector2(sourceBone.X, sourceBone.Y);
 					transformConstraintLastPos.Add(constraintPos);
-					transformConstraintLastRotation.Add(targetBone.Rotation);
+					transformConstraintLastRotation.Add(sourceBone.Rotation);
 				}
 			}
 		}

+ 1 - 1
spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonUtility/SkeletonUtility.cs

@@ -360,7 +360,7 @@ namespace Spine.Unity {
 
 				ExposedList<TransformConstraint> transformConstraints = skeleton.TransformConstraints;
 				for (int i = 0, n = transformConstraints.Count; i < n; i++)
-					constraintTargets.Add(transformConstraints.Items[i].Target);
+					constraintTargets.Add(transformConstraints.Items[i].Source);
 
 				List<SkeletonUtilityBone> boneComponents = this.boneComponents;
 				for (int i = 0, n = boneComponents.Count; i < n; i++) {