Quellcode durchsuchen

[csharp][unity] Port of 4.3 changes until (including) 2d9f86a (4.3.37-beta).

Harald Csaszar vor 2 Monaten
Ursprung
Commit
ec757bf9b7
100 geänderte Dateien mit 4045 neuen und 3254 gelöschten Zeilen
  1. 293 338
      spine-csharp/src/Animation.cs
  2. 19 17
      spine-csharp/src/AnimationState.cs
  3. 10 6
      spine-csharp/src/Attachments/IHasTextureRegion.cs
  4. 30 16
      spine-csharp/src/Attachments/MeshAttachment.cs
  5. 2 2
      spine-csharp/src/Attachments/PointAttachment.cs
  6. 27 11
      spine-csharp/src/Attachments/RegionAttachment.cs
  7. 1 1
      spine-csharp/src/Attachments/Sequence.cs
  8. 11 11
      spine-csharp/src/Attachments/VertexAttachment.cs
  9. 17 406
      spine-csharp/src/Bone.cs
  10. 25 49
      spine-csharp/src/BoneData.cs
  11. 74 0
      spine-csharp/src/BoneLocal.cs
  12. 2 0
      spine-csharp/src/BoneLocal.cs.meta
  13. 381 0
      spine-csharp/src/BonePose.cs
  14. 2 0
      spine-csharp/src/BonePose.cs.meta
  15. 86 0
      spine-csharp/src/Color.cs
  16. 2 0
      spine-csharp/src/Color.cs.meta
  17. 64 0
      spine-csharp/src/Constraint.cs
  18. 2 0
      spine-csharp/src/Constraint.cs.meta
  19. 11 23
      spine-csharp/src/ConstraintData.cs
  20. 1 0
      spine-csharp/src/Event.cs
  21. 36 0
      spine-csharp/src/IPose.cs
  22. 2 0
      spine-csharp/src/IPose.cs.meta
  23. 2 12
      spine-csharp/src/IUpdate.cs
  24. 0 0
      spine-csharp/src/IUpdate.cs.meta
  25. 76 143
      spine-csharp/src/IkConstraint.cs
  26. 11 46
      spine-csharp/src/IkConstraintData.cs
  27. 87 0
      spine-csharp/src/IkConstraintPose.cs
  28. 2 0
      spine-csharp/src/IkConstraintPose.cs.meta
  29. 6 0
      spine-csharp/src/MathUtils.cs
  30. 96 93
      spine-csharp/src/PathConstraint.cs
  31. 7 11
      spine-csharp/src/PathConstraintData.cs
  32. 59 0
      spine-csharp/src/PathConstraintPose.cs
  33. 2 0
      spine-csharp/src/PathConstraintPose.cs.meta
  34. 46 0
      spine-csharp/src/Physics.cs
  35. 2 0
      spine-csharp/src/Physics.cs.meta
  36. 58 93
      spine-csharp/src/PhysicsConstraint.cs
  37. 8 12
      spine-csharp/src/PhysicsConstraintData.cs
  38. 59 0
      spine-csharp/src/PhysicsConstraintPose.cs
  39. 2 0
      spine-csharp/src/PhysicsConstraintPose.cs.meta
  40. 95 0
      spine-csharp/src/Posed.cs
  41. 2 0
      spine-csharp/src/Posed.cs.meta
  42. 58 0
      spine-csharp/src/PosedActive.cs
  43. 2 0
      spine-csharp/src/PosedActive.cs.meta
  44. 74 0
      spine-csharp/src/PosedData.cs
  45. 2 0
      spine-csharp/src/PosedData.cs.meta
  46. 261 442
      spine-csharp/src/Skeleton.cs
  47. 367 319
      spine-csharp/src/SkeletonBinary.cs
  48. 2 2
      spine-csharp/src/SkeletonBounds.cs
  49. 2 2
      spine-csharp/src/SkeletonClipping.cs
  50. 9 73
      spine-csharp/src/SkeletonData.cs
  51. 443 411
      spine-csharp/src/SkeletonJson.cs
  52. 6 7
      spine-csharp/src/Skin.cs
  53. 115 0
      spine-csharp/src/Slider.cs
  54. 2 0
      spine-csharp/src/Slider.cs.meta
  55. 65 0
      spine-csharp/src/SliderData.cs
  56. 2 0
      spine-csharp/src/SliderData.cs.meta
  57. 45 0
      spine-csharp/src/SliderPose.cs
  58. 2 0
      spine-csharp/src/SliderPose.cs.meta
  59. 34 145
      spine-csharp/src/Slot.cs
  60. 19 30
      spine-csharp/src/SlotData.cs
  61. 143 0
      spine-csharp/src/SlotPose.cs
  62. 2 0
      spine-csharp/src/SlotPose.cs.meta
  63. 51 74
      spine-csharp/src/TransformConstraint.cs
  64. 87 87
      spine-csharp/src/TransformConstraintData.cs
  65. 64 0
      spine-csharp/src/TransformConstraintPose.cs
  66. 2 0
      spine-csharp/src/TransformConstraintPose.cs.meta
  67. 1 1
      spine-unity/Assets/Spine Examples/Scripts/Getting Started Scripts/BasicPlatformerController.cs
  68. 2 2
      spine-unity/Assets/Spine Examples/Scripts/Goblins.cs
  69. 27 17
      spine-unity/Assets/Spine Examples/Scripts/MecanimAnimationMatchModifier/AnimationMatchModifierAsset.cs
  70. 1 1
      spine-unity/Assets/Spine Examples/Scripts/Mix and Match Character Customize/EquipsVisualsComponentExample.cs
  71. 2 2
      spine-unity/Assets/Spine Examples/Scripts/Mix and Match Character Customize/MixAndMatchSkinsExample.cs
  72. 1 1
      spine-unity/Assets/Spine Examples/Scripts/MixAndMatch.cs
  73. 2 2
      spine-unity/Assets/Spine Examples/Scripts/MixAndMatchGraphic.cs
  74. 2 2
      spine-unity/Assets/Spine Examples/Scripts/RuntimeLoadFromExportsExample.cs
  75. 5 4
      spine-unity/Assets/Spine Examples/Scripts/Sample Components/BoneLocalOverride.cs
  76. 1 1
      spine-unity/Assets/Spine Examples/Scripts/Sample Components/CombinedSkin.cs
  77. 5 4
      spine-unity/Assets/Spine Examples/Scripts/Sample Components/Legacy/AtlasRegionAttacher.cs
  78. 2 2
      spine-unity/Assets/Spine Examples/Scripts/Sample Components/Legacy/SpriteAttacher.cs
  79. 1 1
      spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonAnimationMulti/SkeletonAnimationMulti.cs
  80. 1 1
      spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonColorInitialize.cs
  81. 51 43
      spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/SkeletonRagdoll.cs
  82. 51 43
      spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/SkeletonRagdoll2D.cs
  83. 5 4
      spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/SkeletonUtilityGroundConstraint.cs
  84. 1 1
      spine-unity/Assets/Spine Examples/Scripts/SpawnSkeletonGraphicExample.cs
  85. 2 2
      spine-unity/Assets/Spine Examples/Scripts/SpineGauge.cs
  86. 3 3
      spine-unity/Assets/Spine Examples/Scripts/SpineboyBodyTilt.cs
  87. 4 4
      spine-unity/Assets/Spine Examples/Scripts/SpineboyFacialExpression.cs
  88. 21 8
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SkeletonDataAssetInspector.cs
  89. 1 1
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonAnimationInspector.cs
  90. 2 2
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicInspector.cs
  91. 2 2
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimInspector.cs
  92. 1 1
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRendererInspector.cs
  93. 6 5
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonUtilityBoneInspector.cs
  94. 3 1
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonUtilityInspector.cs
  95. 12 15
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineAttributeDrawers.cs
  96. 2 2
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/AssetUtility.cs
  97. 64 45
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineHandles.cs
  98. 110 91
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SkeletonBaker.cs
  99. 60 44
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SkeletonDebugWindow.cs
  100. 14 14
      spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoneFollower.cs

Datei-Diff unterdrückt, da er zu groß ist
+ 293 - 338
spine-csharp/src/Animation.cs


+ 19 - 17
spine-csharp/src/AnimationState.cs

@@ -264,8 +264,10 @@ namespace Spine {
 						Timeline timeline = timelines[ii];
 						if (timeline is AttachmentTimeline)
 							ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, blend, attachments);
-						else
-							timeline.Apply(skeleton, animationLast, applyTime, applyEvents, alpha, blend, MixDirection.In);
+						else {
+							timeline.Apply(skeleton, animationLast, applyTime, applyEvents, alpha, blend, MixDirection.In,
+								false);
+						}
 					}
 				} else {
 					int[] timelineMode = current.timelineMode.Items;
@@ -285,7 +287,7 @@ namespace Spine {
 						else if (timeline is AttachmentTimeline)
 							ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, applyTime, blend, attachments);
 						else
-							timeline.Apply(skeleton, animationLast, applyTime, applyEvents, alpha, timelineBlend, MixDirection.In);
+							timeline.Apply(skeleton, animationLast, applyTime, applyEvents, alpha, timelineBlend, MixDirection.In, false);
 					}
 				}
 				QueueEvents(current, animationTime);
@@ -303,7 +305,7 @@ namespace Spine {
 				Slot slot = slots[i];
 				if (slot.attachmentState == setupState) {
 					string attachmentName = slot.data.attachmentName;
-					slot.Attachment = (attachmentName == null ? null : skeleton.GetAttachment(slot.data.index, attachmentName));
+					slot.pose.Attachment = (attachmentName == null ? null : skeleton.GetAttachment(slot.data.index, attachmentName));
 				}
 			}
 			unkeyedState += 2; // Increasing after each use avoids the need to reset attachmentState for every slot.
@@ -339,7 +341,7 @@ namespace Spine {
 					for (int ii = 0; ii < timelineCount; ii++) {
 						Timeline timeline = timelines[ii];
 						if (timeline is EventTimeline)
-							timeline.Apply(skeleton, animationLast, animationTime, events, 1.0f, MixBlend.Setup, MixDirection.In);
+							timeline.Apply(skeleton, animationLast, animationTime, events, 1.0f, MixBlend.Setup, MixDirection.In, false);
 					}
 					QueueEvents(current, animationTime);
 					events.Clear(false);
@@ -381,7 +383,7 @@ namespace Spine {
 
 			if (blend == MixBlend.Add) {
 				for (int i = 0; i < timelineCount; i++)
-					timelines[i].Apply(skeleton, animationLast, applyTime, events, alphaMix, blend, MixDirection.Out);
+					timelines[i].Apply(skeleton, animationLast, applyTime, events, alphaMix, blend, MixDirection.Out, false);
 			} else {
 				int[] timelineMode = from.timelineMode.Items;
 				TrackEntry[] timelineHoldMix = from.timelineHoldMix.Items;
@@ -432,7 +434,7 @@ namespace Spine {
 					} else {
 						if (drawOrder && timeline is DrawOrderTimeline && timelineBlend == MixBlend.Setup)
 							direction = MixDirection.In;
-						timeline.Apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction);
+						timeline.Apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction, false);
 					}
 				}
 			}
@@ -472,7 +474,7 @@ namespace Spine {
 				for (int i = 0; i < timelineCount; i++) {
 					Timeline timeline = timelines[i];
 					if (timeline is EventTimeline)
-						timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, 0, MixBlend.Setup, MixDirection.Out);
+						timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, 0, MixBlend.Setup, MixDirection.Out, false);
 				}
 
 				if (to.mixDuration > 0) QueueEvents(from, animationTime);
@@ -506,7 +508,7 @@ namespace Spine {
 		}
 
 		private void SetAttachment (Skeleton skeleton, Slot slot, String attachmentName, bool attachments) {
-			slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slot.data.index, attachmentName);
+			slot.pose.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slot.data.index, attachmentName);
 			if (attachments) slot.attachmentState = unkeyedState + Current;
 		}
 
@@ -519,30 +521,30 @@ namespace Spine {
 			if (firstFrame) timelinesRotation[i] = 0;
 
 			if (alpha == 1) {
-				timeline.Apply(skeleton, 0, time, null, 1, blend, MixDirection.In);
+				timeline.Apply(skeleton, 0, time, null, 1, blend, MixDirection.In, false);
 				return;
 			}
 
 			Bone bone = skeleton.bones.Items[timeline.BoneIndex];
 			if (!bone.active) return;
-
+			BoneLocal pose = bone.pose, setup = bone.data.setup;
 			float[] frames = timeline.frames;
 			float r1, r2;
 			if (time < frames[0]) { // Time is before first frame.
 				switch (blend) {
 				case MixBlend.Setup:
-					bone.rotation = bone.data.rotation;
+					pose.rotation = setup.rotation;
 					goto default; // Fall through.
 				default:
 					return;
 				case MixBlend.First:
-					r1 = bone.rotation;
-					r2 = bone.data.rotation;
+					r1 = pose.rotation;
+					r2 = setup.rotation;
 					break;
 				}
 			} else {
-				r1 = blend == MixBlend.Setup ? bone.data.rotation : bone.rotation;
-				r2 = bone.data.rotation + timeline.GetCurveValue(time);
+				r1 = blend == MixBlend.Setup ? setup.rotation : pose.rotation;
+				r2 = setup.rotation + timeline.GetCurveValue(time);
 			}
 
 			// Mix between rotations using the direction of the shortest route on the first frame.
@@ -575,7 +577,7 @@ namespace Spine {
 				timelinesRotation[i] = total;
 			}
 			timelinesRotation[i + 1] = diff;
-			bone.rotation = r1 + total * alpha;
+			pose.rotation = r1 + total * alpha;
 		}
 
 		private void QueueEvents (TrackEntry entry, float animationTime) {

+ 10 - 6
spine-csharp/src/Attachments/IHasTextureRegion.cs

@@ -27,10 +27,15 @@
  * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
 
-using System;
-using System.Text;
+#if (UNITY_5 || UNITY_5_3_OR_NEWER || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1)
+#define IS_UNITY
+#endif
 
 namespace Spine {
+#if IS_UNITY
+	using Color = UnityEngine.Color;
+#endif
+
 	public interface IHasTextureRegion {
 		/// <summary>The name used to find the <see cref="Region"/></summary>
 		string Path { get; set; }
@@ -46,10 +51,9 @@ namespace Spine {
 		/// </summary>
 		void UpdateRegion ();
 
-		float R { get; set; }
-		float G { get; set; }
-		float B { get; set; }
-		float A { get; set; }
+		public abstract Color GetColor ();
+		public abstract void SetColor (Color color);
+		public abstract void SetColor (float r, float g, float b, float a);
 
 		Sequence Sequence { get; set; }
 	}

+ 30 - 16
spine-csharp/src/Attachments/MeshAttachment.cs

@@ -27,16 +27,28 @@
  * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
 
+#if (UNITY_5 || UNITY_5_3_OR_NEWER || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1)
+#define IS_UNITY
+#endif
+
 using System;
 
 namespace Spine {
+#if IS_UNITY
+	using Color = UnityEngine.Color;
+#endif
+
 	/// <summary>Attachment that displays a texture region using a mesh.</summary>
 	public class MeshAttachment : VertexAttachment, IHasTextureRegion {
 		internal TextureRegion region;
 		internal string path;
 		internal float[] regionUVs, uvs;
 		internal int[] triangles;
-		internal float r = 1, g = 1, b = 1, a = 1;
+		// Color is a struct, set to protected to prevent
+		// Color color = slot.color; color.a = 0.5;
+		// modifying just a copy of the struct instead of the original
+		// object as in reference implementation.
+		protected Color color = new Color(1, 1, 1, 1);
 		internal int hullLength;
 		private MeshAttachment parentMesh;
 		private Sequence sequence;
@@ -55,10 +67,17 @@ namespace Spine {
 		public float[] UVs { get { return uvs; } set { uvs = value; } }
 		public int[] Triangles { get { return triangles; } set { triangles = value; } }
 
-		public float R { get { return r; } set { r = value; } }
-		public float G { get { return g; } set { g = value; } }
-		public float B { get { return b; } set { b = value; } }
-		public float A { get { return a; } set { a = value; } }
+		public Color GetColor () {
+			return color;
+		}
+
+		public void SetColor (Color color) {
+			this.color = color;
+		}
+
+		public void SetColor (float r, float g, float b, float a) {
+			color = new Color(r, g, b, a);
+		}
 
 		public string Path { get { return path; } set { path = value; } }
 		public Sequence Sequence { get { return sequence; } set { sequence = value; } }
@@ -98,10 +117,7 @@ namespace Spine {
 
 			region = other.region;
 			path = other.path;
-			r = other.r;
-			g = other.g;
-			b = other.b;
-			a = other.a;
+			color = other.color;
 
 			regionUVs = new float[other.regionUVs.Length];
 			Array.Copy(other.regionUVs, 0, regionUVs, 0, regionUVs.Length);
@@ -192,9 +208,10 @@ namespace Spine {
 		}
 
 		/// <summary>If the attachment has a <see cref="Sequence"/>, the region may be changed.</summary>
-		override public void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) {
-			if (sequence != null) sequence.Apply(slot, this);
-			base.ComputeWorldVertices(slot, start, count, worldVertices, offset, stride);
+		override public void ComputeWorldVertices (Skeleton skeleton, Slot slot, int start, int count, float[] worldVertices, int offset,
+			int stride = 2) {
+			if (sequence != null) sequence.Apply(slot.AppliedPose, this);
+			base.ComputeWorldVertices(skeleton, slot, start, count, worldVertices, offset, stride);
 		}
 
 		/// <summary>Returns a new mesh with this mesh set as the <see cref="ParentMesh"/>.
@@ -204,10 +221,7 @@ namespace Spine {
 			mesh.timelineAttachment = timelineAttachment;
 			mesh.region = region;
 			mesh.path = path;
-			mesh.r = r;
-			mesh.g = g;
-			mesh.b = b;
-			mesh.a = a;
+			mesh.color = color;
 			mesh.ParentMesh = parentMesh != null ? parentMesh : this;
 			if (mesh.Region != null) mesh.UpdateRegion();
 			return mesh;

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

@@ -55,11 +55,11 @@ namespace Spine {
 			rotation = other.rotation;
 		}
 
-		public void ComputeWorldPosition (Bone bone, out float ox, out float oy) {
+		public void ComputeWorldPosition (BonePose bone, out float ox, out float oy) {
 			bone.LocalToWorld(this.x, this.y, out ox, out oy);
 		}
 
-		public float ComputeWorldRotation (Bone bone) {
+		public float ComputeWorldRotation (BonePose bone) {
 			float r = rotation * MathUtils.DegRad, cos = (float)Math.Cos(r), sin = (float)Math.Sin(r);
 			float x = cos * bone.a + sin * bone.b;
 			float y = cos * bone.c + sin * bone.d;

+ 27 - 11
spine-csharp/src/Attachments/RegionAttachment.cs

@@ -27,9 +27,17 @@
  * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
 
+#if (UNITY_5 || UNITY_5_3_OR_NEWER || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1)
+#define IS_UNITY
+#endif
+
 using System;
 
 namespace Spine {
+#if IS_UNITY
+	using Color = UnityEngine.Color;
+#endif
+
 	/// <summary>Attachment that displays a texture region.</summary>
 	public class RegionAttachment : Attachment, IHasTextureRegion {
 		public const int BLX = 0, BLY = 1;
@@ -40,7 +48,11 @@ namespace Spine {
 		internal TextureRegion region;
 		internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height;
 		internal float[] offset = new float[8], uvs = new float[8];
-		internal float r = 1, g = 1, b = 1, a = 1;
+		// Color is a struct, set to protected to prevent
+		// Color color = slot.color; color.a = 0.5;
+		// modifying just a copy of the struct instead of the original
+		// object as in reference implementation.
+		protected Color color = new Color(1, 1, 1, 1);
 		internal Sequence sequence;
 
 		public float X { get { return x; } set { x = value; } }
@@ -51,10 +63,17 @@ namespace Spine {
 		public float Width { get { return width; } set { width = value; } }
 		public float Height { get { return height; } set { height = value; } }
 
-		public float R { get { return r; } set { r = value; } }
-		public float G { get { return g; } set { g = value; } }
-		public float B { get { return b; } set { b = value; } }
-		public float A { get { return a; } set { a = value; } }
+		public Color GetColor () {
+			return color;
+		}
+
+		public void SetColor (Color color) {
+			this.color = color;
+		}
+
+		public void SetColor (float r, float g, float b, float a) {
+			color = new Color(r, g, b, a);
+		}
 
 		public string Path { get; set; }
 		public TextureRegion Region { get { return region; } set { region = value; } }
@@ -83,10 +102,7 @@ namespace Spine {
 			height = other.height;
 			Array.Copy(other.uvs, 0, uvs, 0, 8);
 			Array.Copy(other.offset, 0, offset, 0, 8);
-			r = other.r;
-			g = other.g;
-			b = other.b;
-			a = other.a;
+			color = other.color;
 			sequence = other.sequence == null ? null : new Sequence(other.sequence);
 		}
 
@@ -179,10 +195,10 @@ namespace Spine {
 		/// <param name="offset">The worldVertices index to begin writing values.</param>
 		/// <param name="stride">The number of worldVertices entries between the value pairs written.</param>
 		public void ComputeWorldVertices (Slot slot, float[] worldVertices, int offset, int stride = 2) {
-			if (sequence != null) sequence.Apply(slot, this);
+			if (sequence != null) sequence.Apply(slot.AppliedPose, this);
 
 			float[] vertexOffset = this.offset;
-			Bone bone = slot.Bone;
+			BonePose bone = slot.Bone.AppliedPose;
 			float bwx = bone.worldX, bwy = bone.worldY;
 			float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
 			float offsetX, offsetY;

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

@@ -67,7 +67,7 @@ namespace Spine {
 			setupIndex = other.setupIndex;
 		}
 
-		public void Apply (Slot slot, IHasTextureRegion attachment) {
+		public void Apply (SlotPose slot, IHasTextureRegion attachment) {
 			int index = slot.SequenceIndex;
 			if (index == -1) index = setupIndex;
 			if (index >= regions.Length) index = regions.Length - 1;

+ 11 - 11
spine-csharp/src/Attachments/VertexAttachment.cs

@@ -31,7 +31,7 @@ using System;
 
 namespace Spine {
 	/// <summary>>An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's
-	/// <see cref="Slot.Deform"/>.</summary>
+	/// <see cref="SlotPose.Deform"/>.</summary>
 	public abstract class VertexAttachment : Attachment {
 		static int nextID = 0;
 		static readonly Object nextIdLock = new Object();
@@ -83,13 +83,13 @@ namespace Spine {
 			worldVerticesLength = other.worldVerticesLength;
 		}
 
-		public void ComputeWorldVertices (Slot slot, float[] worldVertices) {
-			ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0);
+		public void ComputeWorldVertices (Skeleton skeleton, Slot slot, float[] worldVertices) {
+			ComputeWorldVertices(skeleton, slot, 0, worldVerticesLength, worldVertices, 0);
 		}
 
 		/// <summary>
-		/// Transforms the attachment's local <see cref="Vertices"/> to world coordinates. If the slot's <see cref="Slot.Deform"/> is
-		/// not empty, it is used to deform the vertices.
+		/// Transforms the attachment's local <see cref="Vertices"/> to world coordinates. If the slot's <see cref="SlotPose.Deform"/>
+		/// is not empty, it is used to deform the vertices.
 		/// <para />
 		/// See <a href="http://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
 		/// Runtimes Guide.
@@ -99,14 +99,14 @@ namespace Spine {
 		/// <param name="worldVertices">The output world vertices. Must have a length greater than or equal to <paramref name="offset"/> + <paramref name="count"/>.</param>
 		/// <param name="offset">The <paramref name="worldVertices"/> index to begin writing values.</param>
 		/// <param name="stride">The number of <paramref name="worldVertices"/> entries between the value pairs written.</param>
-		public virtual void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) {
+		public virtual void ComputeWorldVertices (Skeleton skeleton, Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) {
 			count = offset + (count >> 1) * stride;
-			ExposedList<float> deformArray = slot.deform;
+			ExposedList<float> deformArray = slot.AppliedPose.deform;
 			float[] vertices = this.vertices;
 			int[] bones = this.bones;
 			if (bones == null) {
 				if (deformArray.Count > 0) vertices = deformArray.Items;
-				Bone bone = slot.bone;
+				BonePose bone = slot.bone.AppliedPose;
 				float x = bone.worldX, y = bone.worldY;
 				float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
 				for (int vv = start, w = offset; w < count; vv += 2, w += stride) {
@@ -122,14 +122,14 @@ namespace Spine {
 				v += n + 1;
 				skip += n;
 			}
-			Bone[] skeletonBones = slot.bone.skeleton.bones.Items;
+			Bone[] skeletonBones = skeleton.bones.Items;
 			if (deformArray.Count == 0) {
 				for (int w = offset, b = skip * 3; w < count; w += stride) {
 					float wx = 0, wy = 0;
 					int n = bones[v++];
 					n += v;
 					for (; v < n; v++, b += 3) {
-						Bone bone = skeletonBones[bones[v]];
+						BonePose bone = skeletonBones[bones[v]].AppliedPose;
 						float vx = vertices[b], vy = vertices[b + 1], weight = vertices[b + 2];
 						wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight;
 						wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight;
@@ -144,7 +144,7 @@ namespace Spine {
 					int n = bones[v++];
 					n += v;
 					for (; v < n; v++, b += 3, f += 2) {
-						Bone bone = skeletonBones[bones[v]];
+						BonePose bone = skeletonBones[bones[v]].AppliedPose;
 						float vx = vertices[b] + deform[f], vy = vertices[b + 1] + deform[f + 1], weight = vertices[b + 2];
 						wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight;
 						wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight;

+ 17 - 406
spine-csharp/src/Bone.cs

@@ -30,429 +30,40 @@
 using System;
 
 namespace Spine {
-	using Physics = Skeleton.Physics;
 
 	/// <summary>
-	/// Stores a bone's current pose.
+	/// The current pose for a bone, before constraints are applied.
 	/// <para>
 	/// A bone has a local transform which is used to compute its world transform. A bone also has an applied transform, which is a
 	/// local transform that can be applied to compute the world transform. The local transform and applied transform may differ if a
 	/// constraint or application code modifies the world transform after it was computed from the local transform.
 	/// </para>
 	/// </summary>
-	public class Bone : IUpdatable {
+	public class Bone : PosedActive<BoneData, BoneLocal, BonePose> {
 		static public bool yDown;
 
-		internal BoneData data;
-		internal Skeleton skeleton;
 		internal Bone parent;
-		internal ExposedList<Bone> children = new ExposedList<Bone>();
-		internal float x, y, rotation, scaleX, scaleY, shearX, shearY;
-		internal float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY;
+		internal ExposedList<Bone> children = new ExposedList<Bone>(4);
+		
+		internal bool sorted;
 
-		internal float a, b, worldX;
-		internal float c, d, worldY;
-		internal Inherit inherit;
-
-		internal bool sorted, active;
-
-		public BoneData Data { get { return data; } }
-		public Skeleton Skeleton { get { return skeleton; } }
-		public Bone Parent { get { return parent; } }
-		public ExposedList<Bone> Children { get { return children; } }
-		public bool Active { get { return active; } }
-		/// <summary>The local X translation.</summary>
-		public float X { get { return x; } set { x = value; } }
-		/// <summary>The local Y translation.</summary>
-		public float Y { get { return y; } set { y = value; } }
-		/// <summary>The local rotation.</summary>
-		public float Rotation { get { return rotation; } set { rotation = value; } }
-
-		/// <summary>The local scaleX.</summary>
-		public float ScaleX { get { return scaleX; } set { scaleX = value; } }
-
-		/// <summary>The local scaleY.</summary>
-		public float ScaleY { get { return scaleY; } set { scaleY = value; } }
-
-		/// <summary>The local shearX.</summary>
-		public float ShearX { get { return shearX; } set { shearX = value; } }
-
-		/// <summary>The local shearY.</summary>
-		public float ShearY { get { return shearY; } set { shearY = value; } }
-
-		/// <summary>Controls how parent world transforms affect this bone.</summary>
-		public Inherit Inherit { get { return inherit; } set { inherit = value; } }
-
-		/// <summary>The rotation, as calculated by any constraints.</summary>
-		public float AppliedRotation { get { return arotation; } set { arotation = value; } }
-
-		/// <summary>The applied local x translation.</summary>
-		public float AX { get { return ax; } set { ax = value; } }
-
-		/// <summary>The applied local y translation.</summary>
-		public float AY { get { return ay; } set { ay = value; } }
-
-		/// <summary>The applied local scaleX.</summary>
-		public float AScaleX { get { return ascaleX; } set { ascaleX = value; } }
-
-		/// <summary>The applied local scaleY.</summary>
-		public float AScaleY { get { return ascaleY; } set { ascaleY = value; } }
-
-		/// <summary>The applied local shearX.</summary>
-		public float AShearX { get { return ashearX; } set { ashearX = value; } }
-
-		/// <summary>The applied local shearY.</summary>
-		public float AShearY { get { return ashearY; } set { ashearY = value; } }
-
-		/// <summary>Part of the world transform matrix for the X axis. If changed, <see cref="UpdateAppliedTransform()"/> should be called.</summary>
-		public float A { get { return a; } set { a = value; } }
-		/// <summary>Part of the world transform matrix for the Y axis. If changed, <see cref="UpdateAppliedTransform()"/> should be called.</summary>
-		public float B { get { return b; } set { b = value; } }
-		/// <summary>Part of the world transform matrix for the X axis. If changed, <see cref="UpdateAppliedTransform()"/> should be called.</summary>
-		public float C { get { return c; } set { c = value; } }
-		/// <summary>Part of the world transform matrix for the Y axis. If changed, <see cref="UpdateAppliedTransform()"/> should be called.</summary>
-		public float D { get { return d; } set { d = value; } }
-
-		/// <summary>The world X position. If changed, <see cref="UpdateAppliedTransform()"/> should be called.</summary>
-		public float WorldX { get { return worldX; } set { worldX = value; } }
-		/// <summary>The world Y position. If changed, <see cref="UpdateAppliedTransform()"/> should be called.</summary>
-		public float WorldY { get { return worldY; } set { worldY = value; } }
-		/// <summary>The world rotation for the X axis, calculated using <see cref="a"/> and <see cref="c"/>.</summary>
-		public float WorldRotationX { get { return MathUtils.Atan2Deg(c, a); } }
-		/// <summary>The world rotation for the Y axis, calculated using <see cref="b"/> and <see cref="d"/>.</summary>
-		public float WorldRotationY { get { return MathUtils.Atan2Deg(d, b); } }
-
-		/// <summary>Returns the magnitide (always positive) of the world scale X.</summary>
-		public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } }
-		/// <summary>Returns the magnitide (always positive) of the world scale Y.</summary>
-		public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } }
-
-		public Bone (BoneData data, Skeleton skeleton, Bone parent) {
-			if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
-			if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
-			this.data = data;
-			this.skeleton = skeleton;
-			this.parent = parent;
-			SetToSetupPose();
-		}
-
-		/// <summary>Copy constructor. Does not copy the <see cref="Children"/> bones.</summary>
-		/// <param name="parent">May be null.</param>
-		public Bone (Bone bone, Skeleton skeleton, Bone parent) {
-			if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null.");
-			if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
-			this.skeleton = skeleton;
+		public Bone (BoneData data, Bone parent)
+			: base(data, new BonePose(), new BonePose()) {
 			this.parent = parent;
-			data = bone.data;
-			x = bone.x;
-			y = bone.y;
-			rotation = bone.rotation;
-			scaleX = bone.scaleX;
-			scaleY = bone.scaleY;
-			shearX = bone.shearX;
-			shearY = bone.shearY;
-			inherit = bone.inherit;
-		}
-
-		/// <summary>Computes the world transform using the parent bone and this bone's local applied transform.</summary>
-		public void Update (Physics physics) {
-			UpdateWorldTransform(ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY);
-		}
-
-		/// <summary>Computes the world transform using the parent bone and this bone's local transform.</summary>
-		public void UpdateWorldTransform () {
-			UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY);
-		}
-
-		/// <summary>Computes the world transform using the parent bone and the specified local transform. The applied transform is set to the
-		/// specified local transform. Child bones are not updated.
-		/// <para>
-		/// See <a href="http://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
-		/// Runtimes Guide.</para></summary>
-		public void UpdateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) {
-			ax = x;
-			ay = y;
-			arotation = rotation;
-			ascaleX = scaleX;
-			ascaleY = scaleY;
-			ashearX = shearX;
-			ashearY = shearY;
-
-			Bone parent = this.parent;
-			if (parent == null) { // Root bone.
-				Skeleton skeleton = this.skeleton;
-				float sx = skeleton.scaleX, sy = skeleton.ScaleY;
-				float rx = (rotation + shearX) * MathUtils.DegRad;
-				float ry = (rotation + 90 + shearY) * MathUtils.DegRad;
-				a = (float)Math.Cos(rx) * scaleX * sx;
-				b = (float)Math.Cos(ry) * scaleY * sx;
-				c = (float)Math.Sin(rx) * scaleX * sy;
-				d = (float)Math.Sin(ry) * scaleY * sy;
-				worldX = x * sx + skeleton.x;
-				worldY = y * sy + skeleton.y;
-				return;
-			}
-
-			float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
-			worldX = pa * x + pb * y + parent.worldX;
-			worldY = pc * x + pd * y + parent.worldY;
-
-			switch (inherit) {
-			case Inherit.Normal: {
-				float rx = (rotation + shearX) * MathUtils.DegRad;
-				float ry = (rotation + 90 + shearY) * MathUtils.DegRad;
-				float la = (float)Math.Cos(rx) * scaleX;
-				float lb = (float)Math.Cos(ry) * scaleY;
-				float lc = (float)Math.Sin(rx) * scaleX;
-				float ld = (float)Math.Sin(ry) * scaleY;
-				a = pa * la + pb * lc;
-				b = pa * lb + pb * ld;
-				c = pc * la + pd * lc;
-				d = pc * lb + pd * ld;
-				return;
-			}
-			case Inherit.OnlyTranslation: {
-				float rx = (rotation + shearX) * MathUtils.DegRad;
-				float ry = (rotation + 90 + shearY) * MathUtils.DegRad;
-				a = (float)Math.Cos(rx) * scaleX;
-				b = (float)Math.Cos(ry) * scaleY;
-				c = (float)Math.Sin(rx) * scaleX;
-				d = (float)Math.Sin(ry) * scaleY;
-				break;
-			}
-			case Inherit.NoRotationOrReflection: {
-				float sx = 1 / skeleton.scaleX, sy = 1 / skeleton.ScaleY;
-				pa *= sx;
-				pc *= sy;
-				float s = pa * pa + pc * pc, prx;
-				if (s > 0.0001f) {
-					s = Math.Abs(pa * pd * sy - pb * sx * pc) / s;
-					pb = pc * s;
-					pd = pa * s;
-					prx = MathUtils.Atan2Deg(pc, pa);
-				} else {
-					pa = 0;
-					pc = 0;
-					prx = 90 - MathUtils.Atan2Deg(pd, pb);
-				}
-				float rx = (rotation + shearX - prx) * MathUtils.DegRad;
-				float ry = (rotation + shearY - prx + 90) * MathUtils.DegRad;
-				float la = (float)Math.Cos(rx) * scaleX;
-				float lb = (float)Math.Cos(ry) * scaleY;
-				float lc = (float)Math.Sin(rx) * scaleX;
-				float ld = (float)Math.Sin(ry) * scaleY;
-				a = pa * la - pb * lc;
-				b = pa * lb - pb * ld;
-				c = pc * la + pd * lc;
-				d = pc * lb + pd * ld;
-				break;
-			}
-			case Inherit.NoScale:
-			case Inherit.NoScaleOrReflection: {
-				rotation *= MathUtils.DegRad;
-				float cos = (float)Math.Cos(rotation), sin = (float)Math.Sin(rotation);
-				float za = (pa * cos + pb * sin) / skeleton.scaleX;
-				float zc = (pc * cos + pd * sin) / skeleton.ScaleY;
-				float s = (float)Math.Sqrt(za * za + zc * zc);
-				if (s > 0.00001f) s = 1 / s;
-				za *= s;
-				zc *= s;
-				s = (float)Math.Sqrt(za * za + zc * zc);
-				if (inherit == Inherit.NoScale && (pa * pd - pb * pc < 0) != (skeleton.scaleX < 0 != skeleton.ScaleY < 0)) s = -s;
-				rotation = MathUtils.PI / 2 + MathUtils.Atan2(zc, za);
-				float zb = (float)Math.Cos(rotation) * s;
-				float zd = (float)Math.Sin(rotation) * s;
-				shearX *= MathUtils.DegRad;
-				shearY = (90 + shearY) * MathUtils.DegRad;
-				float la = (float)Math.Cos(shearX) * scaleX;
-				float lb = (float)Math.Cos(shearY) * scaleY;
-				float lc = (float)Math.Sin(shearX) * scaleX;
-				float ld = (float)Math.Sin(shearY) * scaleY;
-				a = za * la + zb * lc;
-				b = za * lb + zb * ld;
-				c = zc * la + zd * lc;
-				d = zc * lb + zd * ld;
-				break;
-			}
-			}
-			a *= skeleton.scaleX;
-			b *= skeleton.scaleX;
-			c *= skeleton.ScaleY;
-			d *= skeleton.ScaleY;
-		}
-
-		/// <summary>Sets this bone's local transform to the setup pose.</summary>
-		public void SetToSetupPose () {
-			BoneData data = this.data;
-			x = data.x;
-			y = data.y;
-			rotation = data.rotation;
-			scaleX = data.scaleX;
-			scaleY = data.ScaleY;
-			shearX = data.shearX;
-			shearY = data.shearY;
-			inherit = data.inherit;
+			applied.bone = this;
+			constrained.bone = this;
 		}
 
 		/// <summary>
-		/// Computes the applied transform values from the world transform.
-		/// <para>
-		/// If the world transform is modified (by a constraint, <see cref="RotateWorld(float)"/>, etc) then this method should be called so
-		/// the applied transform matches the world transform. The applied transform may be needed by other code (eg to apply another
-		/// constraint).
-		/// </para><para>
-		///  Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. The applied transform after
-		/// calling this method is equivalent to the local transform used to compute the world transform, but may not be identical.
-		/// </para></summary>
-		public void UpdateAppliedTransform () {
-			Bone parent = this.parent;
-			if (parent == null) {
-				ax = worldX - skeleton.x;
-				ay = worldY - skeleton.y;
-				float a = this.a, b = this.b, c = this.c, d = this.d;
-				arotation = MathUtils.Atan2Deg(c, a);
-				ascaleX = (float)Math.Sqrt(a * a + c * c);
-				ascaleY = (float)Math.Sqrt(b * b + d * d);
-				ashearX = 0;
-				ashearY = MathUtils.Atan2Deg(a * b + c * d, a * d - b * c);
-				return;
-			}
-
-			float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
-			float pid = 1 / (pa * pd - pb * pc);
-			float ia = pd * pid, ib = pb * pid, ic = pc * pid, id = pa * pid;
-			float dx = worldX - parent.worldX, dy = worldY - parent.worldY;
-			ax = (dx * ia - dy * ib);
-			ay = (dy * id - dx * ic);
-
-			float ra, rb, rc, rd;
-			if (inherit == Inherit.OnlyTranslation) {
-				ra = a;
-				rb = b;
-				rc = c;
-				rd = d;
-			} else {
-				switch (inherit) {
-				case Inherit.NoRotationOrReflection: {
-					float s = Math.Abs(pa * pd - pb * pc) / (pa * pa + pc * pc);
-					float skeletonScaleY = skeleton.ScaleY;
-					pb = -pc * skeleton.scaleX * s / skeletonScaleY;
-					pd = pa * skeletonScaleY * s / skeleton.scaleX;
-					pid = 1 / (pa * pd - pb * pc);
-					ia = pd * pid;
-					ib = pb * pid;
-					break;
-				}
-				case Inherit.NoScale:
-				case Inherit.NoScaleOrReflection: {
-					float r = rotation * MathUtils.DegRad, cos = (float)Math.Cos(r), sin = (float)Math.Sin(r);
-					pa = (pa * cos + pb * sin) / skeleton.scaleX;
-					pc = (pc * cos + pd * sin) / skeleton.ScaleY;
-					float s = (float)Math.Sqrt(pa * pa + pc * pc);
-					if (s > 0.00001f) s = 1 / s;
-					pa *= s;
-					pc *= s;
-					s = (float)Math.Sqrt(pa * pa + pc * pc);
-					if (inherit == Inherit.NoScale && pid < 0 != (skeleton.scaleX < 0 != skeleton.ScaleY < 0)) s = -s;
-					r = MathUtils.PI / 2 + MathUtils.Atan2(pc, pa);
-					pb = (float)Math.Cos(r) * s;
-					pd = (float)Math.Sin(r) * s;
-					pid = 1 / (pa * pd - pb * pc);
-					ia = pd * pid;
-					ib = pb * pid;
-					ic = pc * pid;
-					id = pa * pid;
-					break;
-				}
-				}
-				ra = ia * a - ib * c;
-				rb = ia * b - ib * d;
-				rc = id * c - ic * a;
-				rd = id * d - ic * b;
-			}
-
-			ashearX = 0;
-			ascaleX = (float)Math.Sqrt(ra * ra + rc * rc);
-			if (ascaleX > 0.0001f) {
-				float det = ra * rd - rb * rc;
-				ascaleY = det / ascaleX;
-				ashearY = -MathUtils.Atan2Deg(ra * rb + rc * rd, det);
-				arotation = MathUtils.Atan2Deg(rc, ra);
-			} else {
-				ascaleX = 0;
-				ascaleY = (float)Math.Sqrt(rb * rb + rd * rd);
-				ashearY = 0;
-				arotation = 90 - MathUtils.Atan2Deg(rd, rb);
-			}
-		}
-
-		/// <summary>Transforms a point from world coordinates to the bone's local coordinates.</summary>
-		public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) {
-			float a = this.a, b = this.b, c = this.c, d = this.d;
-			float det = a * d - b * c;
-			float x = worldX - this.worldX, y = worldY - this.worldY;
-			localX = (x * d - y * b) / det;
-			localY = (y * a - x * c) / det;
+		/// Copy constructor. Does not copy the <see cref="Children"/> bones.
+		/// </summary>
+		public Bone (Bone bone, Bone parent)
+			: this(bone.data, parent) {
+			pose.Set(bone.pose);
 		}
 
-		/// <summary>Transforms a point from the bone's local coordinates to world coordinates.</summary>
-		public void LocalToWorld (float localX, float localY, out float worldX, out float worldY) {
-			worldX = localX * a + localY * b + this.worldX;
-			worldY = localX * c + localY * d + this.worldY;
-		}
-
-		/// <summary>Transforms a point from world coordinates to the parent bone's local coordinates.</summary>
-		public void WorldToParent (float worldX, float worldY, out float parentX, out float parentY) {
-			if (parent == null) {
-				parentX = worldX;
-				parentY = worldY;
-			} else {
-				parent.WorldToLocal(worldX, worldY, out parentX, out parentY);
-			}
-		}
-
-		/// <summary>Transforms a point from the parent bone's coordinates to world coordinates.</summary>
-		public void ParentToWorld (float parentX, float parentY, out float worldX, out float worldY) {
-			if (parent == null) {
-				worldX = parentX;
-				worldY = parentY;
-			} else {
-				parent.LocalToWorld(parentX, parentY, out worldX, out worldY);
-			}
-		}
-
-		/// <summary>Transforms a world rotation to a local rotation.</summary>
-		public float WorldToLocalRotation (float worldRotation) {
-			worldRotation *= MathUtils.DegRad;
-			float sin = (float)Math.Sin(worldRotation), cos = (float)Math.Cos(worldRotation);
-			return MathUtils.Atan2Deg(a * sin - c * cos, d * cos - b * sin) + rotation - shearX;
-		}
-
-		/// <summary>Transforms a local rotation to a world rotation.</summary>
-		public float LocalToWorldRotation (float localRotation) {
-			localRotation = (localRotation - rotation - shearX) * MathUtils.DegRad;
-			float sin = (float)Math.Sin(localRotation), cos = (float)Math.Cos(localRotation);
-			return MathUtils.Atan2Deg(cos * c + sin * d, cos * a + sin * b);
-		}
-
-		/// <summary>
-		/// Rotates the world transform the specified amount.
-		/// <para>
-		/// After changes are made to the world transform, <see cref="UpdateAppliedTransform()"/> should be called and
-		/// <see cref="Update(Skeleton.Physics)"/> will need to be called on any child bones, recursively.
-		/// </para></summary>
-		public void RotateWorld (float degrees) {
-			degrees *= MathUtils.DegRad;
-			float sin = (float)Math.Sin(degrees), cos = (float)Math.Cos(degrees);
-			float ra = a, rb = b;
-			a = cos * ra - sin * c;
-			b = cos * rb - sin * d;
-			c = sin * ra + cos * c;
-			d = sin * rb + cos * d;
-		}
-
-		override public string ToString () {
-			return data.name;
-		}
+		public Bone Parent { get { return parent; } }
+		public ExposedList<Bone> Children { get { return children; } }
+		
 	}
 }

+ 25 - 49
spine-csharp/src/BoneData.cs

@@ -30,69 +30,45 @@
 using System;
 
 namespace Spine {
-	public class BoneData {
+
+	/// <summary>
+	/// The setup pose for a bone.
+	/// </summary>
+	public class BoneData : PosedData<BoneLocal> {
 		internal int index;
-		internal string name;
 		internal BoneData parent;
 		internal float length;
-		internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY;
-		internal Inherit inherit = Inherit.Normal;
-		internal bool skinRequired;
-
-		/// <summary>The index of the bone in Skeleton.Bones</summary>
-		public int Index { get { return index; } }
-
-		/// <summary>The name of the bone, which is unique across all bones in the skeleton.</summary>
-		public string Name { get { return name; } }
-
-		/// <summary>May be null.</summary>
-		public BoneData Parent { get { return parent; } }
-
-		public float Length { get { return length; } set { length = value; } }
-
-		/// <summary>Local X translation.</summary>
-		public float X { get { return x; } set { x = value; } }
-
-		/// <summary>Local Y translation.</summary>
-		public float Y { get { return y; } set { y = value; } }
-
-		/// <summary>Local rotation in degrees, counter clockwise.</summary>
-		public float Rotation { get { return rotation; } set { rotation = value; } }
-
-		/// <summary>Local scaleX.</summary>
-		public float ScaleX { get { return scaleX; } set { scaleX = value; } }
-
-		/// <summary>Local scaleY.</summary>
-		public float ScaleY { get { return scaleY; } set { scaleY = value; } }
-
-		/// <summary>Local shearX.</summary>
-		public float ShearX { get { return shearX; } set { shearX = value; } }
-
-		/// <summary>Local shearY.</summary>
-		public float ShearY { get { return shearY; } set { shearY = value; } }
-
-		/// <summary>Determines how parent world transforms affect this bone.</summary>
-		public Inherit Inherit { get { return inherit; } set { inherit = value; } }
-
-		/// <summary>When true, <see cref="Skeleton.UpdateWorldTransform(Skeleton.Physics)"/> only updates this bone if the <see cref="Skeleton.Skin"/> contains
-		/// this bone.</summary>
-		/// <seealso cref="Skin.Bones"/>
-		public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } }
 
 		/// <param name="parent">May be null.</param>
-		public BoneData (int index, string name, BoneData parent) {
+		public BoneData (int index, string name, BoneData parent)
+			: base(name, new BoneLocal()) {
+
 			if (index < 0) throw new ArgumentException("index must be >= 0", "index");
 			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
 			this.index = index;
-			this.name = name;
 			this.parent = parent;
 		}
 
-		override public string ToString () {
-			return name;
+		/// <summary>Copy constructor.</summary>
+		/// <param name="parent">May be null.</param>
+		public BoneData (BoneData data, BoneData parent)
+			: this(data.index, data.name, parent) {
+			length = data.length;
+			setup.Set(data.setup);
 		}
+
+		/// <summary>The index of the bone in Skeleton.Bones</summary>
+		public int Index { get { return index; } }
+
+		/// <summary>May be null.</summary>
+		public BoneData Parent { get { return parent; } }
+
+		public float Length { get { return length; } set { length = value; } }
 	}
 
+	/// <summary>
+	/// Determines how a bone inherits world transforms from parent bones.
+	/// </summary>
 	public enum Inherit {
 		Normal,
 		OnlyTranslation,

+ 74 - 0
spine-csharp/src/BoneLocal.cs

@@ -0,0 +1,74 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated April 5, 2025. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2025, Esoteric Software LLC
+ *
+ * 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
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+	/// <summary>
+	/// Stores a bone's local pose.
+	/// </summary>
+	public class BoneLocal : IPose<BoneLocal> {
+		internal float x, y, rotation, scaleX, scaleY, shearX, shearY;
+		internal Inherit inherit;
+
+		public void Set (BoneLocal pose) {
+			if (pose == null) throw new ArgumentNullException("pose", "pose cannot be null.");
+			x = pose.x;
+			y = pose.y;
+			rotation = pose.rotation;
+			scaleX = pose.scaleX;
+			scaleY = pose.scaleY;
+			shearX = pose.shearX;
+			shearY = pose.shearY;
+			inherit = pose.inherit;
+		}
+
+		/// <summary>The local X translation.</summary>
+		public float X { get { return x; } set { x = value; } }
+		/// <summary>The local Y translation.</summary>
+		public float Y { get { return y; } set { y = value; } }
+		/// <summary>The local rotation.</summary>
+		public float Rotation { get { return rotation; } set { rotation = value; } }
+
+		/// <summary>The local scaleX.</summary>
+		public float ScaleX { get { return scaleX; } set { scaleX = value; } }
+
+		/// <summary>The local scaleY.</summary>
+		public float ScaleY { get { return scaleY; } set { scaleY = value; } }
+
+		/// <summary>The local shearX.</summary>
+		public float ShearX { get { return shearX; } set { shearX = value; } }
+
+		/// <summary>The local shearY.</summary>
+		public float ShearY { get { return shearY; } set { shearY = value; } }
+
+		/// <summary>Determines how parent world transforms affect this bone.</summary>
+		public Inherit Inherit { get { return inherit; } set { inherit = value; } }
+	}
+}

+ 2 - 0
spine-csharp/src/BoneLocal.cs.meta

@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 94686e11f12e69248ae7322de74373c9

+ 381 - 0
spine-csharp/src/BonePose.cs

@@ -0,0 +1,381 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated April 5, 2025. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2025, Esoteric Software LLC
+ *
+ * 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
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+
+	/// <summary>
+	/// The applied pose for a bone. This is the <see cref="Bone"/> pose with constraints applied and the world transform computed by
+	/// <see cref="Skeleton.UpdateWorldTransform(Physics)"/>.
+	/// </summary>
+	public class BonePose : BoneLocal, IUpdate {
+		public Bone bone;
+		internal float a, b, worldX;
+		internal float c, d, worldY;
+		internal int world, local;
+
+		/// <summary>
+		/// Called by <see cref="Skeleton.UpdateCache()"/> to compute the world transform, if needed.
+		/// </summary>
+		public void Update (Skeleton skeleton, Physics physics) {
+			if (world != skeleton.update) UpdateWorldTransform(skeleton);
+		}
+
+		/// <summary>Computes the world transform using the parent bone's applied pose and this pose. Child bones are not updated.
+		/// <para>
+		/// See <a href="http://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
+		/// Runtimes Guide.</para></summary>
+		public void UpdateWorldTransform (Skeleton skeleton) {
+			if (local == skeleton.update)
+				UpdateLocalTransform(skeleton);
+			else
+				world = skeleton.update;
+
+			if (bone.parent == null) { // Root bone.
+				float sx = skeleton.scaleX, sy = skeleton.ScaleY;
+				float rx = (rotation + shearX) * MathUtils.DegRad;
+				float ry = (rotation + 90 + shearY) * MathUtils.DegRad;
+				a = (float)Math.Cos(rx) * scaleX * sx;
+				b = (float)Math.Cos(ry) * scaleY * sx;
+				c = (float)Math.Sin(rx) * scaleX * sy;
+				d = (float)Math.Sin(ry) * scaleY * sy;
+				worldX = x * sx + skeleton.x;
+				worldY = y * sy + skeleton.y;
+				return;
+			}
+
+			BonePose parent = bone.parent.applied;
+			float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
+			worldX = pa * x + pb * y + parent.worldX;
+			worldY = pc * x + pd * y + parent.worldY;
+
+			switch (inherit) {
+			case Inherit.Normal: {
+				float rx = (rotation + shearX) * MathUtils.DegRad;
+				float ry = (rotation + 90 + shearY) * MathUtils.DegRad;
+				float la = (float)Math.Cos(rx) * scaleX;
+				float lb = (float)Math.Cos(ry) * scaleY;
+				float lc = (float)Math.Sin(rx) * scaleX;
+				float ld = (float)Math.Sin(ry) * scaleY;
+				a = pa * la + pb * lc;
+				b = pa * lb + pb * ld;
+				c = pc * la + pd * lc;
+				d = pc * lb + pd * ld;
+				return;
+			}
+			case Inherit.OnlyTranslation: {
+				float rx = (rotation + shearX) * MathUtils.DegRad;
+				float ry = (rotation + 90 + shearY) * MathUtils.DegRad;
+				a = (float)Math.Cos(rx) * scaleX;
+				b = (float)Math.Cos(ry) * scaleY;
+				c = (float)Math.Sin(rx) * scaleX;
+				d = (float)Math.Sin(ry) * scaleY;
+				break;
+			}
+			case Inherit.NoRotationOrReflection: {
+				float sx = 1 / skeleton.scaleX, sy = 1 / skeleton.ScaleY;
+				pa *= sx;
+				pc *= sy;
+				float s = pa * pa + pc * pc, prx;
+				if (s > 0.0001f) {
+					s = Math.Abs(pa * pd * sy - pb * sx * pc) / s;
+					pb = pc * s;
+					pd = pa * s;
+					prx = MathUtils.Atan2Deg(pc, pa);
+				} else {
+					pa = 0;
+					pc = 0;
+					prx = 90 - MathUtils.Atan2Deg(pd, pb);
+				}
+				float rx = (rotation + shearX - prx) * MathUtils.DegRad;
+				float ry = (rotation + shearY - prx + 90) * MathUtils.DegRad;
+				float la = (float)Math.Cos(rx) * scaleX;
+				float lb = (float)Math.Cos(ry) * scaleY;
+				float lc = (float)Math.Sin(rx) * scaleX;
+				float ld = (float)Math.Sin(ry) * scaleY;
+				a = pa * la - pb * lc;
+				b = pa * lb - pb * ld;
+				c = pc * la + pd * lc;
+				d = pc * lb + pd * ld;
+				break;
+			}
+			case Inherit.NoScale:
+			case Inherit.NoScaleOrReflection: {
+				rotation *= MathUtils.DegRad;
+				float cos = (float)Math.Cos(rotation), sin = (float)Math.Sin(rotation);
+				float za = (pa * cos + pb * sin) / skeleton.scaleX;
+				float zc = (pc * cos + pd * sin) / skeleton.ScaleY;
+				float s = (float)Math.Sqrt(za * za + zc * zc);
+				if (s > 0.00001f) s = 1 / s;
+				za *= s;
+				zc *= s;
+				s = (float)Math.Sqrt(za * za + zc * zc);
+				if (inherit == Inherit.NoScale && (pa * pd - pb * pc < 0) != (skeleton.scaleX < 0 != skeleton.ScaleY < 0)) s = -s;
+				rotation = MathUtils.PI / 2 + MathUtils.Atan2(zc, za);
+				float zb = (float)Math.Cos(rotation) * s;
+				float zd = (float)Math.Sin(rotation) * s;
+				shearX *= MathUtils.DegRad;
+				shearY = (90 + shearY) * MathUtils.DegRad;
+				float la = (float)Math.Cos(shearX) * scaleX;
+				float lb = (float)Math.Cos(shearY) * scaleY;
+				float lc = (float)Math.Sin(shearX) * scaleX;
+				float ld = (float)Math.Sin(shearY) * scaleY;
+				a = za * la + zb * lc;
+				b = za * lb + zb * ld;
+				c = zc * la + zd * lc;
+				d = zc * lb + zd * ld;
+				break;
+			}
+			}
+			a *= skeleton.scaleX;
+			b *= skeleton.scaleX;
+			c *= skeleton.ScaleY;
+			d *= skeleton.ScaleY;
+		}
+
+		/// <summary>
+		/// Computes the local transform values from the world transform.
+		/// <para>
+		/// If the world transform is modified (by a constraint, <see cref="RotateWorld(float)"/>, etc) then this method should be called so
+		/// the local transform matches the world transform. The local transform may be needed by other code (eg to apply another
+		/// constraint). </para>
+		/// <para>
+		/// Some information is ambiguous in the world transform, such as - 1,-1 scale versus 180 rotation.The local transform after
+		/// calling this method is equivalent to the local transform used to compute the world transform, but may not be identical.
+		/// </para></summary>
+		public void UpdateLocalTransform (Skeleton skeleton) {
+			local = 0;
+			world = skeleton.update;
+
+			if (bone.parent == null) {
+				x = worldX - skeleton.x;
+				y = worldY - skeleton.y;
+				float a = this.a, b = this.b, c = this.c, d = this.d;
+				rotation = MathUtils.Atan2Deg(c, a);
+				scaleX = (float)Math.Sqrt(a * a + c * c);
+				scaleY = (float)Math.Sqrt(b * b + d * d);
+				shearX = 0;
+				shearY = MathUtils.Atan2Deg(a * b + c * d, a * d - b * c);
+				return;
+			}
+
+			BonePose parent = bone.parent.applied;
+			float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
+			float pid = 1 / (pa * pd - pb * pc);
+			float ia = pd * pid, ib = pb * pid, ic = pc * pid, id = pa * pid;
+			float dx = worldX - parent.worldX, dy = worldY - parent.worldY;
+			x = (dx * ia - dy * ib);
+			y = (dy * id - dx * ic);
+
+			float ra, rb, rc, rd;
+			if (inherit == Inherit.OnlyTranslation) {
+				ra = a;
+				rb = b;
+				rc = c;
+				rd = d;
+			} else {
+				switch (inherit) {
+				case Inherit.NoRotationOrReflection: {
+					float s = Math.Abs(pa * pd - pb * pc) / (pa * pa + pc * pc);
+					pb = -pc * skeleton.scaleX * s / skeleton.ScaleY;
+					pd = pa * skeleton.ScaleY * s / skeleton.scaleX;
+					pid = 1 / (pa * pd - pb * pc);
+					ia = pd * pid;
+					ib = pb * pid;
+					break;
+				}
+				case Inherit.NoScale:
+				case Inherit.NoScaleOrReflection: {
+					float r = rotation * MathUtils.DegRad, cos = (float)Math.Cos(r), sin = (float)Math.Sin(r);
+					pa = (pa * cos + pb * sin) / skeleton.scaleX;
+					pc = (pc * cos + pd * sin) / skeleton.ScaleY;
+					float s = (float)Math.Sqrt(pa * pa + pc * pc);
+					if (s > 0.00001f) s = 1 / s;
+					pa *= s;
+					pc *= s;
+					s = (float)Math.Sqrt(pa * pa + pc * pc);
+					if (inherit == Inherit.NoScale && pid < 0 != (skeleton.scaleX < 0 != skeleton.ScaleY < 0)) s = -s;
+					r = MathUtils.PI / 2 + MathUtils.Atan2(pc, pa);
+					pb = (float)Math.Cos(r) * s;
+					pd = (float)Math.Sin(r) * s;
+					pid = 1 / (pa * pd - pb * pc);
+					ia = pd * pid;
+					ib = pb * pid;
+					ic = pc * pid;
+					id = pa * pid;
+					break;
+				}
+				}
+				ra = ia * a - ib * c;
+				rb = ia * b - ib * d;
+				rc = id * c - ic * a;
+				rd = id * d - ic * b;
+			}
+
+			shearX = 0;
+			scaleX = (float)Math.Sqrt(ra * ra + rc * rc);
+			if (scaleX > 0.0001f) {
+				float det = ra * rd - rb * rc;
+				scaleY = det / scaleX;
+				shearY = -MathUtils.Atan2Deg(ra * rb + rc * rd, det);
+				rotation = MathUtils.Atan2Deg(rc, ra);
+			} else {
+				scaleX = 0;
+				scaleY = (float)Math.Sqrt(rb * rb + rd * rd);
+				shearY = 0;
+				rotation = 90 - MathUtils.Atan2Deg(rd, rb);
+			}
+		}
+
+		/// <summary>
+		/// If the world transform has been modified and the local transform no longer matches, <see cref="UpdateLocalTransform(Skeleton)"/>
+		/// is called.
+		/// </summary>
+		public void ValidateLocalTransform (Skeleton skeleton) {
+			if (local == skeleton.update) UpdateLocalTransform(skeleton);
+		}
+
+		internal void ModifyLocal (Skeleton skeleton) {
+			if (local == skeleton.update) UpdateLocalTransform(skeleton);
+			world = 0;
+			ResetWorld(skeleton.update);
+		}
+
+		internal void ModifyWorld (int update) {
+			local = update;
+			world = update;
+			ResetWorld(update);
+		}
+
+		internal void ResetWorld (int update) {
+			Bone[] children = bone.children.Items;
+			for (int i = 0, n = bone.children.Count; i < n; i++) {
+				BonePose child = children[i].applied;
+				if (child.world == update) {
+					child.world = 0;
+					child.local = 0;
+					child.ResetWorld(update);
+				}
+			}
+		}
+
+		/// <summary>Part of the world transform matrix for the X axis. If changed, <see cref="UpdateLocalTransform(Skeleton)"/> should be called.</summary>
+		public float A { get { return a; } set { a = value; } }
+		/// <summary>Part of the world transform matrix for the Y axis. If changed, <see cref="UpdateLocalTransform(Skeleton)"/> should be called.</summary>
+		public float B { get { return b; } set { b = value; } }
+		/// <summary>Part of the world transform matrix for the X axis. If changed, <see cref="UpdateLocalTransform(Skeleton)"/> should be called.</summary>
+		public float C { get { return c; } set { c = value; } }
+		/// <summary>Part of the world transform matrix for the Y axis. If changed, <see cref="UpdateLocalTransform(Skeleton)"/> should be called.</summary>
+		public float D { get { return d; } set { d = value; } }
+
+		/// <summary>The world X position. If changed, <see cref="UpdateLocalTransform(Skeleton)"/> should be called.</summary>
+		public float WorldX { get { return worldX; } set { worldX = value; } }
+		/// <summary>The world Y position. If changed, <see cref="UpdateLocalTransform(Skeleton)"/> should be called.</summary>
+		public float WorldY { get { return worldY; } set { worldY = value; } }
+		/// <summary>The world rotation for the X axis, calculated using <see cref="a"/> and <see cref="c"/>.</summary>
+		public float WorldRotationX { get { return MathUtils.Atan2Deg(c, a); } }
+		/// <summary>The world rotation for the Y axis, calculated using <see cref="b"/> and <see cref="d"/>.</summary>
+		public float WorldRotationY { get { return MathUtils.Atan2Deg(d, b); } }
+
+		/// <summary>Returns the magnitude (always positive) of the world scale X, calculated using <see cref="a"/> and <see cref="c"/>.</summary>
+		public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } }
+		/// <summary>Returns the magnitude (always positive) of the world scale Y, calculated using <see cref="b"/> and <see cref="d"/>.</summary>
+		public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } }
+
+		/// <summary>Transforms a point from world coordinates to the bone's local coordinates.</summary>
+		public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) {
+			float a = this.a, b = this.b, c = this.c, d = this.d;
+			float det = a * d - b * c;
+			float x = worldX - this.worldX, y = worldY - this.worldY;
+			localX = (x * d - y * b) / det;
+			localY = (y * a - x * c) / det;
+		}
+
+		/// <summary>Transforms a point from the bone's local coordinates to world coordinates.</summary>
+		public void LocalToWorld (float localX, float localY, out float worldX, out float worldY) {
+			worldX = localX * a + localY * b + this.worldX;
+			worldY = localX * c + localY * d + this.worldY;
+		}
+
+		/// <summary>Transforms a point from world coordinates to the parent bone's local coordinates.</summary>
+		public void WorldToParent (float worldX, float worldY, out float parentX, out float parentY) {
+			if (bone.parent == null) {
+				parentX = worldX;
+				parentY = worldY;
+			} else {
+				bone.parent.applied.WorldToLocal(worldX, worldY, out parentX, out parentY);
+			}
+		}
+
+		/// <summary>Transforms a point from the parent bone's coordinates to world coordinates.</summary>
+		public void ParentToWorld (float parentX, float parentY, out float worldX, out float worldY) {
+			if (bone.parent == null) {
+				worldX = parentX;
+				worldY = parentY;
+			} else {
+				bone.parent.applied.LocalToWorld(parentX, parentY, out worldX, out worldY);
+			}
+		}
+
+		/// <summary>Transforms a world rotation to a local rotation.</summary>
+		public float WorldToLocalRotation (float worldRotation) {
+			worldRotation *= MathUtils.DegRad;
+			float sin = (float)Math.Sin(worldRotation), cos = (float)Math.Cos(worldRotation);
+			return MathUtils.Atan2Deg(a * sin - c * cos, d * cos - b * sin) + rotation - shearX;
+		}
+
+		/// <summary>Transforms a local rotation to a world rotation.</summary>
+		public float LocalToWorldRotation (float localRotation) {
+			localRotation = (localRotation - rotation - shearX) * MathUtils.DegRad;
+			float sin = (float)Math.Sin(localRotation), cos = (float)Math.Cos(localRotation);
+			return MathUtils.Atan2Deg(cos * c + sin * d, cos * a + sin * b);
+		}
+
+		/// <summary>
+		/// Rotates the world transform the specified amount.
+		/// <para>
+		/// After changes are made to the world transform, <see cref="UpdateLocalTransform(Skeleton)"/> should be called on this bone and any
+		/// child bones, recursively.
+		/// </para></summary>
+		public void RotateWorld (float degrees) {
+			degrees *= MathUtils.DegRad;
+			float sin = (float)Math.Sin(degrees), cos = (float)Math.Cos(degrees);
+			float ra = a, rb = b;
+			a = cos * ra - sin * c;
+			b = cos * rb - sin * d;
+			c = sin * ra + cos * c;
+			d = sin * rb + cos * d;
+		}
+
+		override public string ToString () {
+			return bone.data.name;
+		}
+	}
+}

+ 2 - 0
spine-csharp/src/BonePose.cs.meta

@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 024ad131f32a77e45bc91fc1599a8e0f

+ 86 - 0
spine-csharp/src/Color.cs

@@ -0,0 +1,86 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated April 5, 2025. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2025, Esoteric Software LLC
+ *
+ * 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
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+#if UNITY_5_3_OR_NEWER
+#define IS_UNITY
+#endif
+
+namespace Spine {
+#if IS_UNITY
+	using Color = UnityEngine.Color;
+#else
+	public struct Color {
+		public float r, g, b, a;
+
+		public Color (float r, float g, float b, float a = 1.0f) {
+			this.r = r;
+			this.g = g;
+			this.b = b;
+			this.a = a;
+		}
+
+		public override string ToString () {
+			return string.Format("RGBA({0}, {1}, {2}, {3})", r, g, b, a);
+		}
+
+		public static Color operator + (Color c1, Color c2) {
+			return new Color(c1.r + c2.r, c1.g + c2.g, c1.b + c2.b, c1.a + c2.a);
+		}
+
+		public static Color operator - (Color c1, Color c2) {
+			return new Color(c1.r - c2.r, c1.g - c2.g, c1.b - c2.b, c1.a - c2.a);
+		}
+	}
+#endif
+
+	static class ColorExtensions {
+		public static Color Clamp (this Color color) {
+			color.r = MathUtils.Clamp(color.r, 0, 1);
+			color.g = MathUtils.Clamp(color.g, 0, 1);
+			color.b = MathUtils.Clamp(color.b, 0, 1);
+			color.a = MathUtils.Clamp(color.a, 0, 1);
+			return color;
+		}
+
+		public static Color RGBA8888ToColor(this uint rgba8888) {
+			float r = ((rgba8888 & 0xff000000) >> 24) / 255f;
+			float g = ((rgba8888 & 0x00ff0000) >> 16) / 255f;
+			float b = ((rgba8888 & 0x0000ff00) >> 8) / 255f;
+			float a = ((rgba8888 & 0x000000ff)) / 255f;
+			return new Color(r, g, b, a);
+		}
+
+		public static Color XRGB888ToColor (this uint xrgb888) {
+			float r = ((xrgb888 & 0x00ff0000) >> 16) / 255f;
+			float g = ((xrgb888 & 0x0000ff00) >> 8) / 255f;
+			float b = ((xrgb888 & 0x000000ff)) / 255f;
+			return new Color(r, g, b);
+		}
+	}
+}

+ 2 - 0
spine-csharp/src/Color.cs.meta

@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 445e5522c6d723a4bb98798781daa7d0

+ 64 - 0
spine-csharp/src/Constraint.cs

@@ -0,0 +1,64 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated April 5, 2025. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2025, Esoteric Software LLC
+ *
+ * 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
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+	public interface IConstraint : IPosedActive, IPosed {
+		IConstraintData IData { get; }
+		IConstraint Copy (Skeleton skeleton);
+		bool IsSourceActive { get; }
+		void Sort (Skeleton skeleton);
+		void Update (Skeleton skeleton, Physics physics);
+	}
+
+	/// <summary>
+	/// <para>
+	/// Stores the current pose for an IK constraint. An IK constraint adjusts the rotation of 1 or 2 constrained bones so the tip of
+	/// the last bone is as close to the target bone as possible.</para>
+	/// <para>
+	/// See <a href="http://esotericsoftware.com/spine-ik-constraints">IK constraints</a> in the Spine User Guide.</para>
+	/// </summary>
+	public abstract class Constraint<T, D, P> : PosedActive<D, P, P>, IUpdate, IConstraint
+		where T : Constraint<T, D, P>
+		where D : ConstraintData<T, P>
+		where P : IPose<P> {
+
+		public Constraint (D data, P pose, P constrained)
+			: base(data, pose, constrained) {
+		}
+
+		public D Data { get { return data; } }
+		public IConstraintData IData { get { return data; } }
+		abstract public IConstraint Copy (Skeleton skeleton);
+		abstract public void Sort (Skeleton skeleton);
+		public virtual bool IsSourceActive { get { return true; } }
+		abstract public void Update (Skeleton skeleton, Physics physics);
+	}
+}

+ 2 - 0
spine-csharp/src/Constraint.cs.meta

@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 66115e19768098240ace7f8ad9cb4d4c

+ 11 - 23
spine-csharp/src/ConstraintData.cs

@@ -31,31 +31,19 @@ using System;
 using System.Collections.Generic;
 
 namespace Spine {
-	/// <summary>The base class for all constraint datas.</summary>
-	public abstract class ConstraintData {
-		internal readonly string name;
-		internal int order;
-		internal bool skinRequired;
 
-		public ConstraintData (string name) {
-			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
-			this.name = name;
-		}
-
-		/// <summary> The constraint's name, which is unique across all constraints in the skeleton of the same type.</summary>
-		public string Name { get { return name; } }
-
-		/// <summary>The ordinal of this constraint for the order a skeleton's constraints will be applied by
-		/// <see cref="Skeleton.UpdateWorldTransform(Skeleton.Physics)"/>.</summary>
-		public int Order { get { return order; } set { order = value; } }
-
-		/// <summary>When true, <see cref="Skeleton.UpdateWorldTransform(Skeleton.Physics)"/> only updates this constraint if the <see cref="Skeleton.Skin"/>
-		/// contains this constraint.</summary>
-		/// <seealso cref="Skin.Constraints"/>
-		public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } }
+	public interface IConstraintData : IPosedData {
+		public string Name { get; }
+		public IConstraint Create (Skeleton skeleton);
+	}
 
-		override public string ToString () {
-			return name;
+	public abstract class ConstraintData<T, P> : PosedData<P>, IConstraintData
+		where T : IConstraint
+		where P : IPose<P> {
+		public ConstraintData (string name, P setup)
+			: base(name, setup) {
 		}
+
+		abstract public IConstraint Create (Skeleton skeleton);
 	}
 }

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

@@ -40,6 +40,7 @@ namespace Spine {
 		internal float volume;
 		internal float balance;
 
+		/// <summary>The event's setup pose data.</summary>
 		public EventData Data { get { return data; } }
 		/// <summary>The animation time this event was keyed.</summary>
 		public float Time { get { return time; } }

+ 36 - 0
spine-csharp/src/IPose.cs

@@ -0,0 +1,36 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated April 5, 2025. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2025, Esoteric Software LLC
+ *
+ * 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
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+	public interface IPose<P> {
+		public void Set (P pose);
+	}
+}

+ 2 - 0
spine-csharp/src/IPose.cs.meta

@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 114be1b77921d6645ac5974d5b33f2eb

+ 2 - 12
spine-csharp/src/IUpdatable.cs → spine-csharp/src/IUpdate.cs

@@ -28,20 +28,10 @@
  *****************************************************************************/
 
 namespace Spine {
-	using Physics = Skeleton.Physics;
 
 	/// <summary>The interface for items updated by <see cref="Skeleton.UpdateWorldTransform(Physics)"/>.</summary>
-	public interface IUpdatable {
+	public interface IUpdate {
 		/// <param name="physics">Determines how physics and other non-deterministic updates are applied.</param>
-		void Update (Physics physics);
-
-		/// <summary>Returns false when this item won't be updated by
-		/// <see cref="Skeleton.UpdateWorldTransform(Skeleton.Physics)"/> because a skin is required and the
-		/// <see cref="Skeleton.Skin">active skin</see> does not contain this item.</summary>
-		/// <seealso cref="Skin.Bones"/>
-		/// <seealso cref="Skin.Constraints"/>
-		/// <seealso cref="BoneData.SkinRequired"/>
-		/// <seealso cref="ConstraintData.SkinRequired"/>
-		bool Active { get; }
+		void Update (Skeleton skeleton, Physics physics);
 	}
 }

+ 0 - 0
spine-csharp/src/IUpdatable.cs.meta → spine-csharp/src/IUpdate.cs.meta


+ 76 - 143
spine-csharp/src/IkConstraint.cs

@@ -30,7 +30,6 @@
 using System;
 
 namespace Spine {
-	using Physics = Skeleton.Physics;
 
 	/// <summary>
 	/// <para>
@@ -39,69 +38,59 @@ namespace Spine {
 	/// <para>
 	/// See <a href="http://esotericsoftware.com/spine-ik-constraints">IK constraints</a> in the Spine User Guide.</para>
 	/// </summary>
-	public class IkConstraint : IUpdatable {
-		internal readonly IkConstraintData data;
-		internal readonly ExposedList<Bone> bones = new ExposedList<Bone>();
+	public class IkConstraint : Constraint<IkConstraint, IkConstraintData, IkConstraintPose> {
+		internal readonly ExposedList<BonePose> bones;
 		internal Bone target;
-		internal int bendDirection;
-		internal bool compress, stretch;
-		internal float mix = 1, softness;
 
-		internal bool active;
+		public IkConstraint (IkConstraintData data, Skeleton skeleton)
+			: base(data, new IkConstraintPose(), new IkConstraintPose()) {
+			if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
 
-		public IkConstraint (IkConstraintData data, Skeleton skeleton) {
-			if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
-			this.data = data;
-
-			bones = new ExposedList<Bone>(data.bones.Count);
+			bones = new ExposedList<BonePose>(data.bones.Count);
 			foreach (BoneData boneData in data.bones)
-				bones.Add(skeleton.bones.Items[boneData.index]);
+				bones.Add(skeleton.bones.Items[boneData.index].constrained);
 
 			target = skeleton.bones.Items[data.target.index];
-
-			mix = data.mix;
-			softness = data.softness;
-			bendDirection = data.bendDirection;
-			compress = data.compress;
-			stretch = data.stretch;
 		}
 
-		/// <summary>Copy constructor.</summary>
-		public IkConstraint (IkConstraint constraint, Skeleton skeleton)
-			: this(constraint.data, skeleton) {
-
-			mix = constraint.mix;
-			softness = constraint.softness;
-			bendDirection = constraint.bendDirection;
-			compress = constraint.compress;
-			stretch = constraint.stretch;
+		override public IConstraint Copy (Skeleton skeleton) {
+			var copy = new IkConstraint(data, skeleton);
+			copy.pose.Set(pose);
+			return copy;
 		}
 
-		public void SetToSetupPose () {
-			IkConstraintData data = this.data;
-			mix = data.mix;
-			softness = data.softness;
-			bendDirection = data.bendDirection;
-			compress = data.compress;
-			stretch = data.stretch;
-		}
-
-		public void Update (Physics physics) {
-			if (mix == 0) return;
-			Bone target = this.target;
-			Bone[] bones = this.bones.Items;
+		/// <summary>Applies the constraint to the constrained bones.</summary>
+		override public void Update (Skeleton skeleton, Physics physics) {
+			IkConstraintPose p = applied;
+			if (p.mix == 0) return;
+			BonePose target = this.target.applied;
+			BonePose[] bones = this.bones.Items;
 			switch (this.bones.Count) {
 			case 1:
-				Apply(bones[0], target.worldX, target.worldY, compress, stretch, data.uniform, mix);
+				Apply(skeleton, bones[0], target.worldX, target.worldY, p.compress, p.stretch, data.uniform, p.mix);
 				break;
 			case 2:
-				Apply(bones[0], bones[1], target.worldX, target.worldY, bendDirection, stretch, data.uniform, softness, mix);
+				Apply(skeleton, bones[0], bones[1], target.worldX, target.worldY, p.bendDirection, p.stretch, data.uniform,
+					p.softness, p.mix);
 				break;
 			}
 		}
 
+		override public void Sort (Skeleton skeleton) {
+			skeleton.SortBone(target);
+			Bone parent = bones.Items[0].bone;
+			skeleton.SortBone(parent);
+			skeleton.updateCache.Add(this);
+			parent.sorted = false;
+			skeleton.SortReset(parent.children);
+			skeleton.Constrained(parent);
+			if (bones.Count > 1) skeleton.Constrained(bones.Items[1].bone);
+		}
+
+		override public bool IsSourceActive { get { return target.active; } }
+
 		/// <summary>The bones that will be modified by this IK constraint.</summary>
-		public ExposedList<Bone> Bones {
+		public ExposedList<BonePose> Bones {
 			get { return bones; }
 		}
 
@@ -111,79 +100,26 @@ namespace Spine {
 			set { target = value; }
 		}
 
-		/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained rotation.
-		/// <para>
-		/// For two bone IK: if the parent bone has local nonuniform scale, the child bone's local Y translation is set to 0.
-		/// </para></summary>
-		public float Mix {
-			get { return mix; }
-			set { mix = value; }
-		}
-
-		/// <summary>For two bone IK, the target bone's distance from the maximum reach of the bones where rotation begins to slow. The bones
-		/// will not straighten completely until the target is this far out of range.</summary>
-		public float Softness {
-			get { return softness; }
-			set { softness = value; }
-		}
-
-		/// <summary>For two bone IK, controls the bend direction of the IK bones, either 1 or -1.</summary>
-		public int BendDirection {
-			get { return bendDirection; }
-			set { bendDirection = value; }
-		}
-
-		/// <summary>For one bone IK, when true and the target is too close, the bone is scaled to reach it.</summary>
-		public bool Compress {
-			get { return compress; }
-			set { compress = value; }
-		}
-
-		/// <summary>When true and the target is out of range, the parent bone is scaled to reach it.
-		/// <para>
-		/// For two bone IK: 1) the child bone's local Y translation is set to 0,
-		/// 2) stretch is not applied if <see cref="Softness"/> is > 0,
-		/// and 3) if the parent bone has local nonuniform scale, stretch is not applied.
-		/// </para></summary>
-		public bool Stretch {
-			get { return stretch; }
-			set { stretch = value; }
-		}
-
-		public bool Active {
-			get { return active; }
-		}
-
-		/// <summary>The IK constraint's setup pose data.</summary>
-		public IkConstraintData Data {
-			get { return data; }
-		}
-
-		override public string ToString () {
-			return data.name;
-		}
-
 		/// <summary>Applies 1 bone IK. The target is specified in the world coordinate system.</summary>
-		static public void Apply (Bone bone, float targetX, float targetY, bool compress, bool stretch, bool uniform,
-								float alpha) {
+		static public void Apply (Skeleton skeleton, BonePose bone, float targetX, float targetY, bool compress, bool stretch,
+			bool uniform, float mix) {
 			if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null.");
-			Bone p = bone.parent;
+			bone.ModifyLocal(skeleton);
+			BonePose p = bone.bone.parent.applied;
 
 			float pa = p.a, pb = p.b, pc = p.c, pd = p.d;
-			float rotationIK = -bone.ashearX - bone.arotation;
-			float tx = 0, ty = 0;
-
+			float rotationIK = -bone.shearX - bone.rotation, tx, ty;
 			switch (bone.inherit) {
 			case Inherit.OnlyTranslation:
-				tx = (targetX - bone.worldX) * Math.Sign(bone.skeleton.ScaleX);
-				ty = (targetY - bone.worldY) * Math.Sign(bone.skeleton.ScaleY);
+				tx = (targetX - bone.worldX) * Math.Sign(skeleton.ScaleX);
+				ty = (targetY - bone.worldY) * Math.Sign(skeleton.ScaleY);
 				break;
 			case Inherit.NoRotationOrReflection: {
 				float s = Math.Abs(pa * pd - pb * pc) / Math.Max(0.0001f, pa * pa + pc * pc);
-				float sa = pa / bone.skeleton.scaleX;
-				float sc = pc / bone.skeleton.ScaleY;
-				pb = -sc * s * bone.skeleton.scaleX;
-				pd = sa * s * bone.skeleton.ScaleY;
+				float sa = pa / skeleton.scaleX;
+				float sc = pc / skeleton.ScaleY;
+				pb = -sc * s * skeleton.scaleX;
+				pd = sa * s * skeleton.ScaleY;
 				rotationIK += MathUtils.Atan2Deg(sc, sa);
 				goto default; // Fall through.
 			}
@@ -194,21 +130,20 @@ namespace Spine {
 					tx = 0;
 					ty = 0;
 				} else {
-					tx = (x * pd - y * pb) / d - bone.ax;
-					ty = (y * pa - x * pc) / d - bone.ay;
+					tx = (x * pd - y * pb) / d - bone.x;
+					ty = (y * pa - x * pc) / d - bone.y;
 				}
 				break;
 			}
 			}
 
 			rotationIK += MathUtils.Atan2Deg(ty, tx);
-			if (bone.ascaleX < 0) rotationIK += 180;
+			if (bone.scaleX < 0) rotationIK += 180;
 			if (rotationIK > 180)
 				rotationIK -= 360;
 			else if (rotationIK < -180) //
 				rotationIK += 360;
-
-			float sx = bone.ascaleX, sy = bone.ascaleY;
+			bone.rotation += rotationIK * mix;
 			if (compress || stretch) {
 				switch (bone.inherit) {
 				case Inherit.NoScale:
@@ -217,27 +152,28 @@ namespace Spine {
 					ty = targetY - bone.worldY;
 					break;
 				}
-				float b = bone.data.length * sx;
+				float b = bone.bone.data.length * bone.scaleX;
 				if (b > 0.0001f) {
 					float dd = tx * tx + ty * ty;
 					if ((compress && dd < b * b) || (stretch && dd > b * b)) {
-						float s = ((float)Math.Sqrt(dd) / b - 1) * alpha + 1;
-						sx *= s;
-						if (uniform) sy *= s;
+						float s = ((float)Math.Sqrt(dd) / b - 1) * mix + 1;
+						bone.scaleX *= s;
+						if (uniform) bone.scaleY *= s;
 					}
 				}
 			}
-			bone.UpdateWorldTransform(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, sx, sy, bone.ashearX, bone.ashearY);
 		}
 
 		/// <summary>Applies 2 bone IK. The target is specified in the world coordinate system.</summary>
 		/// <param name="child">A direct descendant of the parent bone.</param>
-		static public void Apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, bool stretch, bool uniform,
-			float softness, float alpha) {
+		static public void Apply (Skeleton skeleton, BonePose parent, BonePose child, float targetX, float targetY, int bendDir,
+			bool stretch, bool uniform, float softness, float mix) {
 			if (parent == null) throw new ArgumentNullException("parent", "parent cannot be null.");
 			if (child == null) throw new ArgumentNullException("child", "child cannot be null.");
 			if (parent.inherit != Inherit.Normal || child.inherit != Inherit.Normal) return;
-			float px = parent.ax, py = parent.ay, psx = parent.ascaleX, psy = parent.ascaleY, sx = psx, sy = psy, csx = child.ascaleX;
+			parent.ModifyLocal(skeleton);
+			child.ModifyLocal(skeleton);
+			float px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX;
 			int os1, os2, s2;
 			if (psx < 0) {
 				psx = -psx;
@@ -256,18 +192,17 @@ namespace Spine {
 				os2 = 180;
 			} else
 				os2 = 0;
-			float cx = child.ax, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d;
+			float cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d;
 			bool u = Math.Abs(psx - psy) <= 0.0001f;
 			if (!u || stretch) {
-				cy = 0;
-				cwx = a * cx + parent.worldX;
-				cwy = c * cx + parent.worldY;
+				child.y = 0;
+				cwx = a * child.x + parent.worldX;
+				cwy = c * child.x + parent.worldY;
 			} else {
-				cy = child.ay;
-				cwx = a * cx + b * cy + parent.worldX;
-				cwy = c * cx + d * cy + parent.worldY;
+				cwx = a * child.x + b * child.y + parent.worldX;
+				cwy = c * child.x + d * child.y + parent.worldY;
 			}
-			Bone pp = parent.parent;
+			BonePose pp = parent.bone.parent.applied;
 			a = pp.a;
 			b = pp.b;
 			c = pp.c;
@@ -275,10 +210,10 @@ namespace Spine {
 			float id = a * d - b * c, x = cwx - pp.worldX, y = cwy - pp.worldY;
 			id = Math.Abs(id) <= 0.0001f ? 0 : 1 / id;
 			float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * 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.bone.data.length * csx, a1, a2;
 			if (l1 < 0.0001f) {
-				Apply(parent, targetX, targetY, false, stretch, false, alpha);
-				child.UpdateWorldTransform(cx, cy, 0, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY);
+				Apply(skeleton, parent, targetX, targetY, false, stretch, false, mix);
+				child.rotation = 0;
 				return;
 			}
 			x = targetX - pp.worldX;
@@ -306,9 +241,9 @@ namespace Spine {
 					cos = 1;
 					a2 = 0;
 					if (stretch) {
-						a = ((float)Math.Sqrt(dd) / (l1 + l2) - 1) * alpha + 1;
-						sx *= a;
-						if (uniform) sy *= a;
+						a = ((float)Math.Sqrt(dd) / (l1 + l2) - 1) * mix + 1;
+						parent.scaleX *= a;
+						if (uniform) parent.scaleY *= a;
 					}
 				} else
 					a2 = (float)Math.Acos(cos) * bendDir;
@@ -366,21 +301,19 @@ namespace Spine {
 				}
 			}
 			break_outer:
-			float os = (float)Math.Atan2(cy, cx) * s2;
-			float rotation = parent.arotation;
-			a1 = (a1 - os) * MathUtils.RadDeg + os1 - rotation;
+			float os = (float)Math.Atan2(child.y, child.x) * s2;
+			a1 = (a1 - os) * MathUtils.RadDeg + os1 - parent.rotation;
 			if (a1 > 180)
 				a1 -= 360;
 			else if (a1 < -180)
 				a1 += 360;
-			parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, sx, sy, 0, 0);
-			rotation = child.arotation;
-			a2 = ((a2 + os) * MathUtils.RadDeg - child.ashearX) * s2 + os2 - rotation;
+			parent.rotation += a1 * mix;
+			a2 = ((a2 + os) * MathUtils.RadDeg - child.shearX) * s2 + os2 - child.rotation;
 			if (a2 > 180)
 				a2 -= 360;
 			else if (a2 < -180)
 				a2 += 360;
-			child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY);
+			child.rotation += a2 * mix;
 		}
 	}
 }

+ 11 - 46
spine-csharp/src/IkConstraintData.cs

@@ -32,14 +32,17 @@ using System.Collections.Generic;
 
 namespace Spine {
 	/// <summary>Stores the setup pose for an IkConstraint.</summary>
-	public class IkConstraintData : ConstraintData {
-		internal ExposedList<BoneData> bones = new ExposedList<BoneData>();
+	public class IkConstraintData : ConstraintData<IkConstraint, IkConstraintPose> {
+		internal ExposedList<BoneData> bones = new ExposedList<BoneData>(2);
 		internal BoneData target;
-		internal int bendDirection;
-		internal bool compress, stretch, uniform;
-		internal float mix, softness;
+		internal bool uniform;
 
-		public IkConstraintData (string name) : base(name) {
+		public IkConstraintData (string name)
+			: base(name, new IkConstraintPose()) {
+		}
+
+		override public IConstraint Create (Skeleton skeleton) {
+			return new IkConstraint(this, skeleton);
 		}
 
 		/// <summary>The bones that are constrained by this IK Constraint.</summary>
@@ -54,46 +57,8 @@ namespace Spine {
 		}
 
 		/// <summary>
-		/// A percentage (0-1) that controls the mix between the constrained and unconstrained rotation.
-		/// <para>
-		/// For two bone IK: if the parent bone has local nonuniform scale, the child bone's local Y translation is set to 0.
-		/// </para></summary>
-		public float Mix {
-			get { return mix; }
-			set { mix = value; }
-		}
-
-		/// <summary>For two bone IK, the target bone's distance from the maximum reach of the bones where rotation begins to slow. The bones
-		/// will not straighten completely until the target is this far out of range.</summary>
-		public float Softness {
-			get { return softness; }
-			set { softness = value; }
-		}
-
-		/// <summary>For two bone IK, controls the bend direction of the IK bones, either 1 or -1.</summary>
-		public int BendDirection {
-			get { return bendDirection; }
-			set { bendDirection = value; }
-		}
-
-		/// <summary>For one bone IK, when true and the target is too close, the bone is scaled to reach it.</summary>
-		public bool Compress {
-			get { return compress; }
-			set { compress = value; }
-		}
-
-		/// <summary>When true and the target is out of range, the parent bone is scaled to reach it.
-		/// <para>
-		/// For two bone IK: 1) the child bone's local Y translation is set to 0,
-		/// 2) stretch is not applied if <see cref="Softness"/> is > 0,
-		/// and 3) if the parent bone has local nonuniform scale, stretch is not applied.</para></summary>
-		public bool Stretch {
-			get { return stretch; }
-			set { stretch = value; }
-		}
-
-		/// <summary>
-		/// When true and <see cref="Compress"/> or <see cref="Stretch"/> is used, the bone is scaled on both the X and Y axes.
+		/// When true and <see cref="IkConstraintPose.Compress"/> or <see cref="IkConstraintPose.Stretch"/> is used, the bone is scaled
+		/// on both the X and Y axes.
 		/// </summary>
 		public bool Uniform {
 			get { return uniform; }

+ 87 - 0
spine-csharp/src/IkConstraintPose.cs

@@ -0,0 +1,87 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated April 5, 2025. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2025, Esoteric Software LLC
+ *
+ * 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
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+
+	/// <summary>Stores the current pose for an IK constraint.</summary>
+	public class IkConstraintPose : IPose<IkConstraintPose> {
+		internal int bendDirection;
+		internal bool compress, stretch;
+		internal float mix = 1, softness;
+		
+		public void Set (IkConstraintPose pose) {
+			mix = pose.mix;
+			softness = pose.softness;
+			bendDirection = pose.bendDirection;
+			compress = pose.compress;
+			stretch = pose.stretch;
+		}
+
+		/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained rotation.
+		/// <para>
+		/// For two bone IK: if the parent bone has local nonuniform scale, the child bone's local Y translation is set to 0.
+		/// </para></summary>
+		public float Mix {
+			get { return mix; }
+			set { mix = value; }
+		}
+
+		/// <summary>For two bone IK, the target bone's distance from the maximum reach of the bones where rotation begins to slow. The bones
+		/// will not straighten completely until the target is this far out of range.</summary>
+		public float Softness {
+			get { return softness; }
+			set { softness = value; }
+		}
+
+		/// <summary>For two bone IK, controls the bend direction of the IK bones, either 1 or -1.</summary>
+		public int BendDirection {
+			get { return bendDirection; }
+			set { bendDirection = value; }
+		}
+
+		/// <summary>For one bone IK, when true and the target is too close, the bone is scaled to reach it.</summary>
+		public bool Compress {
+			get { return compress; }
+			set { compress = value; }
+		}
+
+		/// <summary>When true and the target is out of range, the parent bone is scaled to reach it.
+		/// <para>
+		/// For two bone IK: 1) the child bone's local Y translation is set to 0,
+		/// 2) stretch is not applied if <see cref="Softness"/> is > 0,
+		/// and 3) if the parent bone has local nonuniform scale, stretch is not applied.
+		/// </para></summary>
+		public bool Stretch {
+			get { return stretch; }
+			set { stretch = value; }
+		}
+	}
+}

+ 2 - 0
spine-csharp/src/IkConstraintPose.cs.meta

@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: f7b8113cda0e502458c2084ec31e0cca

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

@@ -137,6 +137,12 @@ namespace Spine {
 			return value;
 		}
 
+		static public float Clamp01 (float value) {
+			if (value < 0) return 0;
+			if (value > 1) return 1;
+			return value;
+		}
+
 		static public float RandomTriangle (float min, float max) {
 			return RandomTriangle(min, max, (min + max) * 0.5f);
 		}

+ 96 - 93
spine-csharp/src/PathConstraint.cs

@@ -30,7 +30,6 @@
 using System;
 
 namespace Spine {
-	using Physics = Skeleton.Physics;
 
 	/// <summary>
 	/// <para>
@@ -39,48 +38,32 @@ namespace Spine {
 	/// <para>
 	/// See <a href="http://esotericsoftware.com/spine-path-constraints">Path constraints</a> in the Spine User Guide.</para>
 	/// </summary>
-	public class PathConstraint : IUpdatable {
+	public class PathConstraint : Constraint<PathConstraint, PathConstraintData, PathConstraintPose> {
 		const int NONE = -1, BEFORE = -2, AFTER = -3;
 		const float Epsilon = 0.00001f;
 
-		internal readonly PathConstraintData data;
-		internal readonly ExposedList<Bone> bones;
+		internal readonly ExposedList<BonePose> bones;
 		internal Slot slot;
-		internal float position, spacing, mixRotate, mixX, mixY;
-
-		internal bool active;
 
 		internal readonly ExposedList<float> spaces = new ExposedList<float>(), positions = new ExposedList<float>();
 		internal readonly ExposedList<float> world = new ExposedList<float>(), curves = new ExposedList<float>(), lengths = new ExposedList<float>();
 		internal readonly float[] segments = new float[10];
 
-		public PathConstraint (PathConstraintData data, Skeleton skeleton) {
-			if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
+		public PathConstraint (PathConstraintData data, Skeleton skeleton)
+			: base(data, new PathConstraintPose (), new PathConstraintPose ()) {
 			if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
-			this.data = data;
 
-			bones = new ExposedList<Bone>(data.Bones.Count);
+			bones = new ExposedList<BonePose>(data.Bones.Count);
 			foreach (BoneData boneData in data.bones)
-				bones.Add(skeleton.bones.Items[boneData.index]);
+				bones.Add(skeleton.bones.Items[boneData.index].constrained);
 
 			slot = skeleton.slots.Items[data.slot.index];
-
-			position = data.position;
-			spacing = data.spacing;
-			mixRotate = data.mixRotate;
-			mixX = data.mixX;
-			mixY = data.mixY;
 		}
 
-		/// <summary>Copy constructor.</summary>
-		public PathConstraint (PathConstraint constraint, Skeleton skeleton)
-			: this(constraint.data, skeleton) {
-
-			position = constraint.position;
-			spacing = constraint.spacing;
-			mixRotate = constraint.mixRotate;
-			mixX = constraint.mixX;
-			mixY = constraint.mixY;
+		override public IConstraint Copy (Skeleton skeleton) {
+			var copy = new PathConstraint(data, skeleton);
+			copy.pose.Set(pose);
+			return copy;
 		}
 
 		public static void ArraysFill (float[] a, int fromIndex, int toIndex, float val) {
@@ -88,34 +71,26 @@ namespace Spine {
 				a[i] = val;
 		}
 
-		public void SetToSetupPose () {
-			PathConstraintData data = this.data;
-			position = data.position;
-			spacing = data.spacing;
-			mixRotate = data.mixRotate;
-			mixX = data.mixX;
-			mixY = data.mixY;
-		}
-
-		public void Update (Physics physics) {
-			PathAttachment attachment = slot.Attachment as PathAttachment;
-			if (attachment == null) return;
+		override public void Update (Skeleton skeleton, Physics physics) {
+			PathAttachment pathAttachment = slot.applied.Attachment as PathAttachment;
+			if (pathAttachment == null) return;
 
-			float mixRotate = this.mixRotate, mixX = this.mixX, mixY = this.mixY;
+			PathConstraintPose p = applied;
+			float mixRotate = p.mixRotate, mixX = p.mixX, mixY = p.mixY;
 			if (mixRotate == 0 && mixX == 0 && mixY == 0) return;
 
 			PathConstraintData data = this.data;
 			bool tangents = data.rotateMode == RotateMode.Tangent, scale = data.rotateMode == RotateMode.ChainScale;
 			int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1;
-			Bone[] bonesItems = this.bones.Items;
+			BonePose[] bonesItems = this.bones.Items;
 			float[] spaces = this.spaces.Resize(spacesCount).Items, lengths = scale ? this.lengths.Resize(boneCount).Items : null;
-			float spacing = this.spacing;
+			float spacing = p.spacing;
 			switch (data.spacingMode) {
 			case SpacingMode.Percent:
 				if (scale) {
 					for (int i = 0, n = spacesCount - 1; i < n; i++) {
-						Bone bone = bonesItems[i];
-						float setupLength = bone.data.length;
+						BonePose bone = bonesItems[i];
+						float setupLength = bone.bone.data.length;
 						float x = setupLength * bone.a, y = setupLength * bone.c;
 						lengths[i] = (float)Math.Sqrt(x * x + y * y);
 					}
@@ -125,9 +100,9 @@ namespace Spine {
 			case SpacingMode.Proportional: {
 				float sum = 0;
 				for (int i = 0, n = spacesCount - 1; i < n;) {
-					Bone bone = bonesItems[i];
-					float setupLength = bone.data.length;
-					if (setupLength < PathConstraint.Epsilon) {
+					BonePose bone = bonesItems[i];
+					float setupLength = bone.bone.data.length;
+					if (setupLength < Epsilon) {
 						if (scale) lengths[i] = 0;
 						spaces[++i] = spacing;
 					} else {
@@ -148,8 +123,8 @@ namespace Spine {
 			default: {
 				bool lengthSpacing = data.spacingMode == SpacingMode.Length;
 				for (int i = 0, n = spacesCount - 1; i < n;) {
-					Bone bone = bonesItems[i];
-					float setupLength = bone.data.length;
+					BonePose bone = bonesItems[i];
+					float setupLength = bone.bone.data.length;
 					if (setupLength < PathConstraint.Epsilon) {
 						if (scale) lengths[i] = 0;
 						spaces[++i] = spacing;
@@ -157,31 +132,31 @@ namespace Spine {
 						float x = setupLength * bone.a, y = setupLength * bone.c;
 						float length = (float)Math.Sqrt(x * x + y * y);
 						if (scale) lengths[i] = length;
-						spaces[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength;
+						spaces[++i] = (lengthSpacing ? Math.Max(0, setupLength + spacing) : spacing) * length / setupLength;
 					}
 				}
 				break;
 			}
 			}
 
-			float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents);
+			float[] positions = ComputeWorldPositions(skeleton, pathAttachment, spacesCount, tangents);
 			float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation;
 			bool tip;
 			if (offsetRotation == 0) {
 				tip = data.rotateMode == RotateMode.Chain;
 			} else {
 				tip = false;
-				Bone p = slot.bone;
-				offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.DegRad : -MathUtils.DegRad;
+				BonePose bone = slot.bone.applied;
+				offsetRotation *= bone.a * bone.d - bone.b * bone.c > 0 ? MathUtils.DegRad : -MathUtils.DegRad;
 			}
-			for (int i = 0, p = 3; i < boneCount; i++, p += 3) {
-				Bone bone = bonesItems[i];
+			for (int i = 0, ip = 3, u = skeleton.update; i < boneCount; i++, ip += 3) {
+				BonePose bone = bonesItems[i];
 				bone.worldX += (boneX - bone.worldX) * mixX;
 				bone.worldY += (boneY - bone.worldY) * mixY;
-				float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY;
+				float x = positions[ip], y = positions[ip + 1], dx = x - boneX, dy = y - boneY;
 				if (scale) {
 					float length = lengths[i];
-					if (length >= PathConstraint.Epsilon) {
+					if (length >= Epsilon) {
 						float s = ((float)Math.Sqrt(dx * dx + dy * dy) / length - 1) * mixRotate + 1;
 						bone.a *= s;
 						bone.c *= s;
@@ -192,16 +167,16 @@ namespace Spine {
 				if (mixRotate > 0) {
 					float a = bone.a, b = bone.b, c = bone.c, d = bone.d, r, cos, sin;
 					if (tangents)
-						r = positions[p - 1];
-					else if (spaces[i + 1] < PathConstraint.Epsilon)
-						r = positions[p + 2];
+						r = positions[ip - 1];
+					else if (spaces[i + 1] < Epsilon)
+						r = positions[ip + 2];
 					else
 						r = MathUtils.Atan2(dy, dx);
 					r -= MathUtils.Atan2(c, a);
 					if (tip) {
 						cos = MathUtils.Cos(r);
 						sin = MathUtils.Sin(r);
-						float length = bone.data.length;
+						float length = bone.bone.data.length;
 						boneX += (length * (cos * a - sin * c) - dx) * mixRotate;
 						boneY += (length * (sin * a + cos * c) - dy) * mixRotate;
 					} else
@@ -218,13 +193,13 @@ namespace Spine {
 					bone.c = sin * a + cos * c;
 					bone.d = sin * b + cos * d;
 				}
-				bone.UpdateAppliedTransform();
+				bone.ModifyWorld(u);
 			}
 		}
 
-		float[] ComputeWorldPositions (PathAttachment path, int spacesCount, bool tangents) {
-			Slot target = this.slot;
-			float position = this.position;
+		float[] ComputeWorldPositions (Skeleton skeleton, PathAttachment path, int spacesCount, bool tangents) {
+			Slot slot = this.slot;
+			float position = applied.position;
 			float[] spaces = this.spaces.Items, output = this.positions.Resize(spacesCount * 3 + 2).Items, world;
 			bool closed = path.Closed;
 			int verticesLength = path.WorldVerticesLength, curveCount = verticesLength / 6, prevCurve = NONE;
@@ -262,14 +237,14 @@ namespace Spine {
 					} else if (p < 0) {
 						if (prevCurve != BEFORE) {
 							prevCurve = BEFORE;
-							path.ComputeWorldVertices(target, 2, 4, world, 0, 2);
+							path.ComputeWorldVertices(skeleton, slot, 2, 4, world, 0, 2);
 						}
 						AddBeforePosition(p, world, 0, output, o);
 						continue;
 					} else if (p > pathLength) {
 						if (prevCurve != AFTER) {
 							prevCurve = AFTER;
-							path.ComputeWorldVertices(target, verticesLength - 6, 4, world, 0, 2);
+							path.ComputeWorldVertices(skeleton, slot, verticesLength - 6, 4, world, 0, 2);
 						}
 						AddAfterPosition(p - pathLength, world, 0, output, o);
 						continue;
@@ -290,13 +265,13 @@ namespace Spine {
 					if (curve != prevCurve) {
 						prevCurve = curve;
 						if (closed && curve == curveCount) {
-							path.ComputeWorldVertices(target, verticesLength - 4, 4, world, 0, 2);
-							path.ComputeWorldVertices(target, 0, 4, world, 4, 2);
+							path.ComputeWorldVertices(skeleton, slot, verticesLength - 4, 4, world, 0, 2);
+							path.ComputeWorldVertices(skeleton, slot, 0, 4, world, 4, 2);
 						} else
-							path.ComputeWorldVertices(target, curve * 6 + 2, 8, world, 0, 2);
+							path.ComputeWorldVertices(skeleton, slot, curve * 6 + 2, 8, world, 0, 2);
 					}
 					AddCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], output, o,
-						tangents || (i > 0 && space < PathConstraint.Epsilon));
+						tangents || (i > 0 && space < Epsilon));
 				}
 				return output;
 			}
@@ -305,15 +280,15 @@ namespace Spine {
 			if (closed) {
 				verticesLength += 2;
 				world = this.world.Resize(verticesLength).Items;
-				path.ComputeWorldVertices(target, 2, verticesLength - 4, world, 0, 2);
-				path.ComputeWorldVertices(target, 0, 2, world, verticesLength - 4, 2);
+				path.ComputeWorldVertices(skeleton, slot, 2, verticesLength - 4, world, 0, 2);
+				path.ComputeWorldVertices(skeleton, slot, 0, 2, world, verticesLength - 4, 2);
 				world[verticesLength - 2] = world[0];
 				world[verticesLength - 1] = world[1];
 			} else {
 				curveCount--;
 				verticesLength -= 4;
 				world = this.world.Resize(verticesLength).Items;
-				path.ComputeWorldVertices(target, 2, verticesLength, world, 0, 2);
+				path.ComputeWorldVertices(skeleton, slot, 2, verticesLength, world, 0, 2);
 			}
 
 			// Curve lengths.
@@ -378,6 +353,7 @@ namespace Spine {
 					p %= pathLength;
 					if (p < 0) p += pathLength;
 					curve = 0;
+					segment = 0;
 				} else if (p < 0) {
 					AddBeforePosition(p, world, 0, output, o);
 					continue;
@@ -493,26 +469,53 @@ namespace Spine {
 			}
 		}
 
-		/// <summary>The position along the path.</summary>
-		public float Position { get { return position; } set { position = value; } }
-		/// <summary>The spacing between bones.</summary>
-		public float Spacing { get { return spacing; } set { spacing = value; } }
-		/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained rotations.</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>
-		public float MixX { get { return mixX; } set { mixX = value; } }
-		/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y.</summary>
-		public float MixY { get { return mixY; } set { mixY = value; } }
-		/// <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 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; } }
+		override public void Sort (Skeleton skeleton) {
+			int slotIndex = slot.Data.index;
+			Bone slotBone = slot.bone;
+			if (skeleton.skin != null) SortPathSlot(skeleton, skeleton.skin, slotIndex, slotBone);
+			if (skeleton.data.defaultSkin != null && skeleton.data.defaultSkin != skeleton.skin)
+				SortPathSlot(skeleton, skeleton.data.defaultSkin, slotIndex, slotBone);
+			SortPath(skeleton, slot.pose.attachment, slotBone);
+			BonePose[] bones = this.bones.Items;
+			int boneCount = this.bones.Count;
+			for (int i = 0; i < boneCount; i++) {
+				Bone bone = bones[i].bone;
+				skeleton.SortBone(bone);
+				skeleton.Constrained(bone);
+			}
+			skeleton.updateCache.Add(this);
+			for (int i = 0; i < boneCount; i++)
+				skeleton.SortReset(bones[i].bone.children);
+			for (int i = 0; i < boneCount; i++)
+				bones[i].bone.sorted = true;
+		}
 
-		override public string ToString () {
-			return data.name;
+		private void SortPathSlot (Skeleton skeleton, Skin skin, int slotIndex, Bone slotBone) {
+			foreach (Skin.SkinEntry entry in skin.Attachments)
+				if (entry.SlotIndex == slotIndex) SortPath(skeleton, entry.Attachment, slotBone);
+		}
+
+		private void SortPath (Skeleton skeleton, Attachment attachment, Bone slotBone) {
+			if (!(attachment is PathAttachment)) return;
+			int[] pathBones = ((PathAttachment)attachment).bones;
+			if (pathBones == null)
+				skeleton.SortBone(slotBone);
+			else {
+				Bone[] bones = skeleton.bones.Items;
+				for (int i = 0, n = pathBones.Length; i < n;) {
+					int nn = pathBones[i++];
+					nn += i;
+					while (i < nn)
+						skeleton.SortBone(bones[pathBones[i++]]);
+				}
+			}
 		}
+
+		override public bool IsSourceActive { get { return slot.bone.active; } }
+
+		/// <summary>The bones that will be modified by this path constraint.</summary>
+		public ExposedList<BonePose> Bones { get { return bones; } }
+		/// <summary>The slot whose path attachment will be used to constrained the bones.</summary>
+		public Slot Slot { get { return slot; } set { slot = value; } }
 	}
 }

+ 7 - 11
spine-csharp/src/PathConstraintData.cs

@@ -30,16 +30,20 @@
 using System;
 
 namespace Spine {
-	public class PathConstraintData : ConstraintData {
+	public class PathConstraintData : ConstraintData<PathConstraint, PathConstraintPose> {
 		internal ExposedList<BoneData> bones = new ExposedList<BoneData>();
 		internal SlotData slot;
 		internal PositionMode positionMode;
 		internal SpacingMode spacingMode;
 		internal RotateMode rotateMode;
 		internal float offsetRotation;
-		internal float position, spacing, mixRotate, mixX, mixY;
 
-		public PathConstraintData (string name) : base(name) {
+		public PathConstraintData (string name)
+			: base(name, new PathConstraintPose()) {
+		}
+
+		override public IConstraint Create (Skeleton skeleton) {
+			return new PathConstraint(this, skeleton);
 		}
 
 		public ExposedList<BoneData> Bones { get { return bones; } }
@@ -48,14 +52,6 @@ namespace Spine {
 		public SpacingMode SpacingMode { get { return spacingMode; } set { spacingMode = value; } }
 		public RotateMode RotateMode { get { return rotateMode; } set { rotateMode = value; } }
 		public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } }
-		public float Position { get { return position; } set { position = value; } }
-		public float Spacing { get { return spacing; } set { spacing = value; } }
-		/// <summary> A percentage (0-1) that controls the mix between the constrained and unconstrained rotation.</summary>
-		public float RotateMix { get { return mixRotate; } set { mixRotate = value; } }
-		/// <summary> A percentage (0-1) that controls the mix between the constrained and unconstrained translation X.</summary>
-		public float MixX { get { return mixX; } set { mixX = value; } }
-		/// <summary> A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y.</summary>
-		public float MixY { get { return mixY; } set { mixY = value; } }
 	}
 
 	public enum PositionMode {

+ 59 - 0
spine-csharp/src/PathConstraintPose.cs

@@ -0,0 +1,59 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated April 5, 2025. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2025, Esoteric Software LLC
+ *
+ * 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
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+
+	/// <summary>
+	/// Stores a pose for a path constraint.
+	/// </summary>
+	public class PathConstraintPose : IPose<PathConstraintPose> {
+		internal float position, spacing, mixRotate, mixX, mixY;
+
+		public void Set (PathConstraintPose pose) {
+			position = pose.position;
+			spacing = pose.spacing;
+			mixRotate = pose.mixRotate;
+			mixX = pose.mixX;
+			mixY = pose.mixY;
+		}
+
+		/// <summary>The position along the path.</summary>
+		public float Position { get { return position; } set { position = value; } }
+		/// <summary>The spacing between bones.</summary>
+		public float Spacing { get { return spacing; } set { spacing = value; } }
+		/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained rotations.</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>
+		public float MixX { get { return mixX; } set { mixX = value; } }
+		/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y.</summary>
+		public float MixY { get { return mixY; } set { mixY = value; } }
+	}
+}

+ 2 - 0
spine-csharp/src/PathConstraintPose.cs.meta

@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 40244ea3e8f247f4fac4363b219d8418

+ 46 - 0
spine-csharp/src/Physics.cs

@@ -0,0 +1,46 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated April 5, 2025. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2025, Esoteric Software LLC
+ *
+ * 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
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+namespace Spine {
+
+	/// <summary>Determines how physics and other non-deterministic updates are applied.</summary>
+	public enum Physics {
+		/// <summary>Physics are not updated or applied.</summary>
+		None,
+
+		/// <summary>Physics are reset to the current pose.</summary>
+		Reset,
+
+		/// <summary>Physics are updated and the pose from physics is applied.</summary>
+		Update,
+
+		/// <summary>Physics are not updated but the pose from physics is applied.</summary>
+		Pose
+	}
+}

+ 2 - 0
spine-csharp/src/Physics.cs.meta

@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 2dab58587078e30468faff31f1ad4fa9

+ 58 - 93
spine-csharp/src/PhysicsConstraint.cs

@@ -30,17 +30,14 @@
 using System;
 
 namespace Spine {
-	using Physics = Skeleton.Physics;
 
 	/// <summary>
 	/// Stores the current pose for a physics constraint. A physics constraint applies physics to bones.
 	/// <para>
 	/// See <a href="http://esotericsoftware.com/spine-physics-constraints">Physics constraints</a> in the Spine User Guide.</para>
 	/// </summary>
-	public class PhysicsConstraint : IUpdatable {
-		internal readonly PhysicsConstraintData data;
-		internal Bone bone;
-		internal float inertia, strength, damping, massInverse, wind, gravity, mix;
+	public class PhysicsConstraint : Constraint<PhysicsConstraint, PhysicsConstraintData, PhysicsConstraintPose> {
+		internal BonePose bone;
 
 		bool reset = true;
 		float ux, uy, cx, cy, tx, ty;
@@ -49,42 +46,22 @@ namespace Spine {
 		float rotateOffset, rotateLag, rotateVelocity;
 		float scaleOffset, scaleLag, scaleVelocity;
 
-		internal bool active;
-
-		readonly Skeleton skeleton;
 		float remaining, lastTime;
 
-		public PhysicsConstraint (PhysicsConstraintData data, Skeleton skeleton) {
-			if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
+		public PhysicsConstraint (PhysicsConstraintData data, Skeleton skeleton)
+			: base(data, new PhysicsConstraintPose(), new PhysicsConstraintPose()) {
 			if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
-			this.data = data;
-			this.skeleton = skeleton;
-
-			bone = skeleton.bones.Items[data.bone.index];
 
-			inertia = data.inertia;
-			strength = data.strength;
-			damping = data.damping;
-			massInverse = data.massInverse;
-			wind = data.wind;
-			gravity = data.gravity;
-			mix = data.mix;
+			bone = skeleton.bones.Items[data.bone.index].constrained;
 		}
 
-		/// <summary>Copy constructor.</summary>
-		public PhysicsConstraint (PhysicsConstraint constraint, Skeleton skeleton)
-			: this(constraint.data, skeleton) {
-
-			inertia = constraint.inertia;
-			strength = constraint.strength;
-			damping = constraint.damping;
-			massInverse = constraint.massInverse;
-			wind = constraint.wind;
-			gravity = constraint.gravity;
-			mix = constraint.mix;
+		override public IConstraint Copy (Skeleton skeleton) {
+			var copy = new PhysicsConstraint(data, skeleton);
+			copy.pose.Set(pose);
+			return copy;
 		}
 
-		public void Reset () {
+		public void Reset (Skeleton skeleton) {
 			remaining = 0;
 			lastTime = skeleton.time;
 			reset = true;
@@ -102,20 +79,9 @@ namespace Spine {
 			scaleVelocity = 0;
 		}
 
-		public void SetToSetupPose () {
-			PhysicsConstraintData data = this.data;
-			inertia = data.inertia;
-			strength = data.strength;
-			damping = data.damping;
-			massInverse = data.massInverse;
-			wind = data.wind;
-			gravity = data.gravity;
-			mix = data.mix;
-		}
-
 		/// <summary>
-		/// Translates the physics constraint so next <see cref="Update(Physics)"/> forces are applied as if the bone moved an additional
-		/// amount in world space.
+		/// Translates the physics constraint so next <see cref="Update(Skeleton, Physics)"/> forces are applied as if the bone moved an
+		/// additional amount in world space.
 		/// </summary>
 		public void Translate (float x, float y) {
 			ux -= x;
@@ -125,8 +91,8 @@ namespace Spine {
 		}
 
 		/// <summary>
-		/// Rotates the physics constraint so next <see cref="Update(Physics)"/> forces are applied as if the bone rotated around the
-		/// specified point in world space.
+		/// Rotates the physics constraint so next <see cref="Update(Skeleton, Physics)"/> forces are applied as if the bone rotated around
+		/// the specified point in world space.
 		/// </summary>
 		public void Rotate (float x, float y, float degrees) {
 			float r = degrees * MathUtils.DegRad, cos = (float)Math.Cos(r), sin = (float)Math.Sin(r);
@@ -135,23 +101,23 @@ namespace Spine {
 		}
 
 		/// <summary>Applies the constraint to the constrained bones.</summary>
-		public void Update (Physics physics) {
-			float mix = this.mix;
+		override public void Update (Skeleton skeleton, Physics physics) {
+			PhysicsConstraintPose p = applied;
+			float mix = p.mix;
 			if (mix == 0) return;
 
 			bool x = data.x > 0, y = data.y > 0, rotateOrShearX = data.rotate > 0 || data.shearX > 0, scaleX = data.scaleX > 0;
-			Bone bone = this.bone;
-			float l = bone.data.length, t = data.step, z = 0;
+			BonePose bone = this.bone;
+			float l = bone.bone.data.length, t = data.step, z = 0;
 
 			switch (physics) {
 			case Physics.None:
 				return;
 			case Physics.Reset:
-				Reset();
+				Reset(skeleton);
 				goto case Physics.Update; // Fall through.
 			case Physics.Update:
-				Skeleton skeleton = this.skeleton;
-				float delta = Math.Max(skeleton.time - lastTime, 0);
+				float delta = Math.Max(skeleton.time - lastTime, 0), aa = remaining;
 				remaining += delta;
 				lastTime = skeleton.time;
 
@@ -161,8 +127,8 @@ namespace Spine {
 					ux = bx;
 					uy = by;
 				} else {
-					float a = remaining, i = inertia, f = skeleton.data.referenceScale, d = -1, qx = data.limit * delta,
-						qy = qx * Math.Abs(skeleton.ScaleY);
+					float a = remaining, i = p.inertia, f = skeleton.data.referenceScale, d = -1, m = 0, e = 0, ax = 0, ay = 0,
+						qx = data.limit * delta, qy = qx * Math.Abs(skeleton.ScaleY);
 					qx *= Math.Abs(skeleton.ScaleX);
 
 					if (x || y) {
@@ -177,17 +143,21 @@ namespace Spine {
 							uy = by;
 						}
 						if (a >= t) {
-							d = (float)Math.Pow(damping, 60 * t);
-							float m = massInverse * t, e = strength, w = wind * f * skeleton.ScaleX,
-								g = gravity * f * skeleton.ScaleY, xs = xOffset, ys = yOffset;
+							float xs = xOffset, ys = yOffset;
+							d = (float)Math.Pow(p.damping, 60 * t);
+							m = t * p.massInverse;
+							e = p.strength;
+							float w = f * p.wind, g = f * p.gravity;
+							ax = (w * skeleton.windX + g * skeleton.gravityX) * skeleton.scaleX;
+							ay = (w * skeleton.windY + g * skeleton.gravityY) * skeleton.ScaleY;
 							do {
 								if (x) {
-									xVelocity += (w - xOffset * e) * m;
+									xVelocity += (ax - xOffset * e) * m;
 									xOffset += xVelocity * t;
 									xVelocity *= d;
 								}
 								if (y) {
-									yVelocity -= (g + yOffset * e) * m;
+									yVelocity -= (ay + yOffset * e) * m;
 									yOffset += yVelocity * t;
 									yVelocity *= d;
 								}
@@ -210,12 +180,12 @@ namespace Spine {
 							dy = qy;
 						else if (dy < -qy)
 							dy = -qy;
-						a = remaining;
 						if (rotateOrShearX) {
 							mr = (data.rotate + data.shearX) * mix;
-							float rz = rotateLag * Math.Max(0, 1 - a / t), r = (float)Math.Atan2(dy + ty, dx + tx) - ca - (rotateOffset - rz) * mr;
+							z = rotateLag * Math.Max(0, 1 - aa / t);
+							float r = (float)Math.Atan2(dy + ty, dx + tx) - ca - (rotateOffset - z) * mr;
 							rotateOffset += (r - (float)Math.Ceiling(r * MathUtils.InvPI2 - 0.5f) * MathUtils.PI2) * i;
-							r = (rotateOffset - rz) * mr + ca;
+							r = (rotateOffset - z) * mr + ca;
 							c = (float)Math.Cos(r);
 							s = (float)Math.Sin(r);
 							if (scaleX) {
@@ -225,23 +195,30 @@ namespace Spine {
 						} else {
 							c = (float)Math.Cos(ca);
 							s = (float)Math.Sin(ca);
-							float r = l * bone.WorldScaleX;
+							float r = l * bone.WorldScaleX - scaleLag * Math.Max(0, 1 - aa / t);
 							if (r > 0) scaleOffset += (dx * c + dy * s) * i / r;
 						}
 						a = remaining;
 						if (a >= t) {
-							if (d == -1) d = (float)Math.Pow(damping, 60 * t);
-							float m = massInverse * t, e = strength, w = wind, g = (Bone.yDown ? -gravity : gravity), h = l / f,
-								rs = rotateOffset, ss = scaleOffset;
+							if (d == -1) {
+								d = (float)Math.Pow(p.damping, 60 * t);
+								m = t * p.massInverse;
+								e = p.strength;
+								float w = f * p.wind, g = f * p.gravity;
+								ax = (w * skeleton.windX + g * skeleton.gravityX) * skeleton.scaleX;
+								ay = (w * skeleton.windY + g * skeleton.gravityY) * skeleton.ScaleY;
+							}
+							float rs = rotateOffset, ss = scaleOffset, h = l / f;
+							if (Spine.Bone.yDown) ay = -ay;
 							while (true) {
 								a -= t;
 								if (scaleX) {
-									scaleVelocity += (w * c - g * s - scaleOffset * e) * m;
+									scaleVelocity += (ax * c - ay * s - scaleOffset * e) * m;
 									scaleOffset += scaleVelocity * t;
 									scaleVelocity *= d;
 								}
 								if (rotateOrShearX) {
-									rotateVelocity -= ((w * s + g * c) * h + rotateOffset * e) * m;
+									rotateVelocity -= ((ax * s + ay * c) * h + rotateOffset * e) * m;
 									rotateOffset += rotateVelocity * t;
 									rotateVelocity *= d;
 									if (a < t) break;
@@ -307,32 +284,20 @@ namespace Spine {
 				tx = l * bone.a;
 				ty = l * bone.c;
 			}
-			bone.UpdateAppliedTransform();
+			bone.ModifyWorld(skeleton.update);
 		}
 
-		/// <summary>The bone constrained by this physics constraint.</summary>
-		public Bone Bone { get { return bone; } set { bone = value; } }
-		public float Inertia { get { return inertia; } set { inertia = value; } }
-		public float Strength { get { return strength; } set { strength = value; } }
-		public float Damping { get { return damping; } set { damping = value; } }
-		public float MassInverse { get { return massInverse; } set { massInverse = value; } }
-		public float Wind { get { return wind; } set { wind = value; } }
-		public float Gravity { get { return gravity; } set { gravity = value; } }
-		/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained poses.</summary>
-		public float Mix { get { return mix; } set { mix = value; } }
-		public bool Active { get { return active; } }
-
-
-		/// <summary>The physics constraint's setup pose data.</summary>
-		public PhysicsConstraintData getData () {
-			return data;
+		override public void Sort (Skeleton skeleton) {
+			Bone bone = this.bone.bone;
+			skeleton.SortBone(bone);
+			skeleton.updateCache.Add(this);
+			skeleton.SortReset(bone.children);
+			skeleton.Constrained(bone);
 		}
 
-		/// <summary>The physics constraint's setup pose data.</summary>
-		public PhysicsConstraintData Data { get { return data; } }
+		override public bool IsSourceActive { get { return bone.bone.active; } }
 
-		override public string ToString () {
-			return data.name;
-		}
+		/// <summary>The bone constrained by this physics constraint.</summary>
+		public BonePose Bone { get { return bone; } set { bone = value; } }
 	}
 }

+ 8 - 12
spine-csharp/src/PhysicsConstraintData.cs

@@ -33,13 +33,17 @@ namespace Spine {
 	/// <para>
 	/// See <a href="http://esotericsoftware.com/spine-physics-constraints">Physics constraints</a> in the Spine User Guide.</para>
 	/// </summary>
-	public class PhysicsConstraintData : ConstraintData {
+	public class PhysicsConstraintData : ConstraintData<PhysicsConstraint, PhysicsConstraintPose> {
 		internal BoneData bone;
-		internal float x, y, rotate, scaleX, shearX, limit;
-		internal float step, inertia, strength, damping, massInverse, wind, gravity, mix;
+		internal float x, y, rotate, scaleX, shearX, limit, step;
 		internal bool inertiaGlobal, strengthGlobal, dampingGlobal, massGlobal, windGlobal, gravityGlobal, mixGlobal;
 
-		public PhysicsConstraintData (string name) : base(name) {
+		public PhysicsConstraintData (string name)
+			: base(name, new PhysicsConstraintPose()) {
+		}
+
+		override public IConstraint Create (Skeleton skeleton) {
+			return new PhysicsConstraint(this, skeleton);
 		}
 
 		/// <summary>The bone constrained by this physics constraint.</summary>
@@ -52,14 +56,6 @@ namespace Spine {
 		public float ScaleX { get { return scaleX; } set { scaleX = value; } }
 		public float ShearX { get { return shearX; } set { shearX = value; } }
 		public float Limit { get { return limit; } set { limit = value; } }
-		public float Inertia { get { return inertia; } set { inertia = value; } }
-		public float Strength { get { return strength; } set { strength = value; } }
-		public float Damping { get { return damping; } set { damping = value; } }
-		public float MassInverse { get { return massInverse; } set { massInverse = value; } }
-		public float Wind { get { return wind; } set { wind = value; } }
-		public float Gravity { get { return gravity; } set { gravity = value; } }
-		/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained poses.</summary>
-		public float Mix { get { return mix; } set { mix = value; } }
 		public bool InertiaGlobal { get { return inertiaGlobal; } set { inertiaGlobal = value; } }
 		public bool StrengthGlobal { get { return strengthGlobal; } set { strengthGlobal = value; } }
 		public bool DampingGlobal { get { return dampingGlobal; } set { dampingGlobal = value; } }

+ 59 - 0
spine-csharp/src/PhysicsConstraintPose.cs

@@ -0,0 +1,59 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated April 5, 2025. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2025, Esoteric Software LLC
+ *
+ * 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
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+
+	/// <summary>
+	/// Stores a pose for a physics constraint.
+	/// </summary>
+	public class PhysicsConstraintPose : IPose<PhysicsConstraintPose> {
+		internal float inertia, strength, damping, massInverse, wind, gravity, mix;
+
+		public void Set (PhysicsConstraintPose pose) {
+			inertia = pose.inertia;
+			strength = pose.strength;
+			damping = pose.damping;
+			massInverse = pose.massInverse;
+			wind = pose.wind;
+			gravity = pose.gravity;
+			mix = pose.mix;
+		}
+
+		public float Inertia { get { return inertia; } set { inertia = value; } }
+		public float Strength { get { return strength; } set { strength = value; } }
+		public float Damping { get { return damping; } set { damping = value; } }
+		public float MassInverse { get { return massInverse; } set { massInverse = value; } }
+		public float Wind { get { return wind; } set { wind = value; } }
+		public float Gravity { get { return gravity; } set { gravity = value; } }
+		/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained poses.</summary>
+		public float Mix { get { return mix; } set { mix = value; } }
+	}
+}

+ 2 - 0
spine-csharp/src/PhysicsConstraintPose.cs.meta

@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 6974fd8e5e17807499e8fed214659cf9

+ 95 - 0
spine-csharp/src/Posed.cs

@@ -0,0 +1,95 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated April 5, 2025. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2025, Esoteric Software LLC
+ *
+ * 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
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+	
+	public interface IPosed {
+		// replaces "object.pose == object.applied" of reference implementation.
+		bool PoseEqualsApplied { get; }
+		// replaces "object.applied = object.pose" of reference implementation.
+		void UsePose ();
+		// replaces "object.applied = object.constrained" of reference implementation.
+		void UseConstrained ();
+		// replaces "object.applied.Set(object.pose)" of reference implementation.
+		void ResetConstrained ();
+		void SetupPose ();
+	}
+
+	public class Posed<D, P, A> : IPosed
+		where D : PosedData<P>
+		where P : IPose<P>
+		where A : P {
+
+		internal readonly D data;
+		internal readonly A pose;
+		internal readonly A constrained;
+		internal A applied;
+
+		public Posed (D data, A pose, A constrained) {
+			if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
+			this.data = data;
+			this.pose = pose;
+			this.constrained = constrained;
+			applied = pose;
+		}
+
+		public virtual void SetupPose () {
+			pose.Set(data.setup);
+		}
+
+		public bool PoseEqualsApplied {
+			get { return (object)pose == (object)applied; }
+		}
+
+		public void UsePose () {
+			applied = pose;
+		}
+
+		public void UseConstrained () {
+			applied = constrained;
+		}
+
+		public void ResetConstrained () {
+			applied.Set(pose);
+		}
+
+		/// <summary>The constraint's setup pose data.</summary>
+		public D Data { get { return data; } }
+
+		public P Pose { get { return pose; } }
+
+		public A AppliedPose { get { return applied; } }
+
+		override public string ToString () {
+			return data.name;
+		}
+	}
+}

+ 2 - 0
spine-csharp/src/Posed.cs.meta

@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 208c5c9065683a84e8166c4ce75c8951

+ 58 - 0
spine-csharp/src/PosedActive.cs

@@ -0,0 +1,58 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated April 5, 2025. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2025, Esoteric Software LLC
+ *
+ * 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
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+	public interface IPosedActive {
+		bool Active { get; set; }
+	}
+
+	public class PosedActive<D, P, A> : Posed<D, P, A>, IPosedActive
+		where D : PosedData<P>
+		where P : IPose<P>
+		where A : P {
+
+		internal bool active;
+
+		public PosedActive (D data, A pose, A constrained)
+			: base(data, pose, constrained) {
+			SetupPose();
+		}
+
+		/// <summary>Returns false when this constraint won't be updated by
+		/// <see cref="Skeleton.UpdateWorldTransform(Physics)"/> because a skin is required and the
+		/// <see cref="Skeleton.Skin">active skin</see> does not contain this item.</summary>
+		/// <seealso cref="Skin.Bones"/>
+		/// <seealso cref="Skin.Constraints"/>
+		/// <seealso cref="PosedData.SkinRequired"/>
+		/// <seealso cref="Skeleton.UpdateCache()"/>
+		public bool Active { get { return active; } set { active = value; } }
+	}
+}

+ 2 - 0
spine-csharp/src/PosedActive.cs.meta

@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: d3e0989017e310f44900496191b7d268

+ 74 - 0
spine-csharp/src/PosedData.cs

@@ -0,0 +1,74 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated April 5, 2025. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2025, Esoteric Software LLC
+ *
+ * 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
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+	public interface IPosedData {
+		public bool SkinRequired { get; }
+	}
+
+	/// <summary>
+	/// The base class for all constrained datas.
+	/// </summary>
+	public class PosedData<P> : IPosedData
+		where P : IPose<P> {
+
+		internal readonly string name;
+		internal readonly P setup;
+		internal bool skinRequired;
+
+		public PosedData (string name, P setup) {
+			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
+			this.name = name;
+			this.setup = setup;
+		}
+
+		///<summary>The constraint's name, which is unique across all constraints in the skeleton of the same type.</summary>
+		public string Name { get { return name; } }
+
+		public P GetSetupPose () {
+			return setup;
+		}
+
+		/// <summary>
+		/// When true, <see cref="Skeleton.UpdateWorldTransform(Physics)"/> only updates this constraint if the <see cref="Skeleton.Skin"/>
+		/// contains this constraint.
+		/// </summary>
+		/// <seealso cref="Skin.Constraints"/>
+		public bool SkinRequired {
+			get { return skinRequired; }
+			set { skinRequired = value; }
+		}
+
+		override public string ToString () {
+			return name;
+		}
+	}
+}

+ 2 - 0
spine-csharp/src/PosedData.cs.meta

@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 984c45a4e280725489d63d5bf5cf526e

+ 261 - 442
spine-csharp/src/Skeleton.cs

@@ -27,88 +27,36 @@
  * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
 
+#if UNITY_5_3_OR_NEWER
+#define IS_UNITY
+#endif
+
 using System;
 
 namespace Spine {
+#if IS_UNITY
+	using Color = UnityEngine.Color;
+#endif
 	public class Skeleton {
 		static private readonly int[] quadTriangles = { 0, 1, 2, 2, 3, 0 };
 		internal SkeletonData data;
 		internal ExposedList<Bone> bones;
 		internal ExposedList<Slot> slots;
 		internal ExposedList<Slot> drawOrder;
-		internal ExposedList<IkConstraint> ikConstraints;
-		internal ExposedList<TransformConstraint> transformConstraints;
-		internal ExposedList<PathConstraint> pathConstraints;
-		internal ExposedList<PhysicsConstraint> physicsConstraints;
-		internal ExposedList<IUpdatable> updateCache = new ExposedList<IUpdatable>();
+		internal ExposedList<IConstraint> constraints;
+		internal ExposedList<PhysicsConstraint> physics;
+		internal ExposedList<object> updateCache = new ExposedList<object>();
+		internal ExposedList<IPosed> resetCache = new ExposedList<IPosed>(16);
 		internal Skin skin;
-		internal float r = 1, g = 1, b = 1, a = 1;
-		internal float x, y, scaleX = 1, time;
+		// Color is a struct, set to protected to prevent
+		// Color color = slot.color; color.a = 0.5;
+		// modifying just a copy of the struct instead of the original
+		// object as in reference implementation.
+		protected Color color;
+		internal float x, y, scaleX = 1, time, windX = 1, windY = 0, gravityX = 0, gravityY = 1;
 		/// <summary>Private to enforce usage of ScaleY getter taking Bone.yDown into account.</summary>
 		private float scaleY = 1;
-
-		/// <summary>The skeleton's setup pose data.</summary>
-		public SkeletonData Data { get { return data; } }
-		/// <summary>The skeleton's bones, sorted parent first. The root bone is always the first bone.</summary>
-		public ExposedList<Bone> Bones { get { return bones; } }
-		/// <summary>The list of bones and constraints, sorted in the order they should be updated,
-		/// as computed by <see cref="UpdateCache()"/>.</summary>
-		public ExposedList<IUpdatable> UpdateCacheList { get { return updateCache; } }
-		/// <summary>The skeleton's slots.</summary>
-		public ExposedList<Slot> Slots { get { return slots; } }
-		/// <summary>The skeleton's slots in the order they should be drawn.
-		/// The returned array may be modified to change the draw order.</summary>
-		public ExposedList<Slot> DrawOrder { get { return drawOrder; } }
-		/// <summary>The skeleton's IK constraints.</summary>
-		public ExposedList<IkConstraint> IkConstraints { get { return ikConstraints; } }
-		/// <summary>The skeleton's path constraints.</summary>
-		public ExposedList<PathConstraint> PathConstraints { get { return pathConstraints; } }
-		/// <summary>The skeleton's physics constraints.</summary>
-		public ExposedList<PhysicsConstraint> PhysicsConstraints { get { return physicsConstraints; } }
-		/// <summary>The skeleton's transform constraints.</summary>
-		public ExposedList<TransformConstraint> TransformConstraints { get { return transformConstraints; } }
-
-		/// <summary>The skeleton's current skin. May be null. See <see cref="SetSkin(Spine.Skin)"/></summary>
-		public Skin Skin {
-			/// <summary>The skeleton's current skin. May be null.</summary>
-			get { return skin; }
-			/// <summary>Sets a skin, <see cref="SetSkin(Skin)"/>.</summary>
-			set { SetSkin(value); }
-		}
-		public float R { get { return r; } set { r = value; } }
-		public float G { get { return g; } set { g = value; } }
-		public float B { get { return b; } set { b = value; } }
-		public float A { get { return a; } set { a = value; } }
-		/// <summary><para>The skeleton X position, which is added to the root bone worldX position.</para>
-		/// <para>
-		/// Bones that do not inherit translation are still affected by this property.</para></summary>
-		public float X { get { return x; } set { x = value; } }
-		/// <summary><para>The skeleton Y position, which is added to the root bone worldY position.</para>
-		/// <para>
-		/// Bones that do not inherit translation are still affected by this property.</para></summary>
-		public float Y { get { return y; } set { y = value; } }
-		/// <summary><para> Scales the entire skeleton on the X axis.</para>
-		/// <para>
-		/// Bones that do not inherit scale are still affected by this property.</para></summary>
-		public float ScaleX { get { return scaleX; } set { scaleX = value; } }
-		/// <summary><para> Scales the entire skeleton on the Y axis.</para>
-		/// <para>
-		/// Bones that do not inherit scale are still affected by this property.</para></summary>
-		public float ScaleY { get { return scaleY * (Bone.yDown ? -1 : 1); } set { scaleY = value; } }
-
-		[Obsolete("Use ScaleX instead. FlipX is when ScaleX is negative.")]
-		public bool FlipX { get { return scaleX < 0; } set { scaleX = value ? -1f : 1f; } }
-
-		[Obsolete("Use ScaleY instead. FlipY is when ScaleY is negative.")]
-		public bool FlipY { get { return scaleY < 0; } set { scaleY = value ? -1f : 1f; } }
-		/// <summary>Returns the skeleton's time. This is used for time-based manipulations, such as <see cref="PhysicsConstraint"/>.</summary>
-		/// <seealso cref="Update(float)"/>
-		public float Time { get { return time; } set { time = value; } }
-
-		/// <summary>Returns the root bone, or null if the skeleton has no bones.</summary>
-		public Bone RootBone {
-			get { return bones.Count == 0 ? null : bones.Items[0]; }
-		}
+		internal int update;
 
 		public Skeleton (SkeletonData data) {
 			if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
@@ -119,10 +67,10 @@ namespace Spine {
 			foreach (BoneData boneData in data.bones) {
 				Bone bone;
 				if (boneData.parent == null) {
-					bone = new Bone(boneData, this, null);
+					bone = new Bone(boneData, null);
 				} else {
 					Bone parent = bonesItems[boneData.parent.index];
-					bone = new Bone(boneData, this, parent);
+					bone = new Bone(boneData, parent);
 					parent.children.Add(bone);
 				}
 				this.bones.Add(bone);
@@ -132,27 +80,23 @@ namespace Spine {
 			drawOrder = new ExposedList<Slot>(data.slots.Count);
 			foreach (SlotData slotData in data.slots) {
 				Bone bone = bonesItems[slotData.boneData.index];
-				Slot slot = new Slot(slotData, bone);
+				Slot slot = new Slot(slotData, this);
 				slots.Add(slot);
 				drawOrder.Add(slot);
 			}
 
-			ikConstraints = new ExposedList<IkConstraint>(data.ikConstraints.Count);
-			foreach (IkConstraintData ikConstraintData in data.ikConstraints)
-				ikConstraints.Add(new IkConstraint(ikConstraintData, this));
-
-			transformConstraints = new ExposedList<TransformConstraint>(data.transformConstraints.Count);
-			foreach (TransformConstraintData transformConstraintData in data.transformConstraints)
-				transformConstraints.Add(new TransformConstraint(transformConstraintData, this));
-
-			pathConstraints = new ExposedList<PathConstraint>(data.pathConstraints.Count);
-			foreach (PathConstraintData pathConstraintData in data.pathConstraints)
-				pathConstraints.Add(new PathConstraint(pathConstraintData, this));
-
-			physicsConstraints = new ExposedList<PhysicsConstraint>(data.physicsConstraints.Count);
-			foreach (PhysicsConstraintData physicsConstraintData in data.physicsConstraints)
-				physicsConstraints.Add(new PhysicsConstraint(physicsConstraintData, this));
+			physics = new ExposedList<PhysicsConstraint>(8);
+			constraints = new ExposedList<IConstraint>(data.constraints.Count);
+			foreach (IConstraintData constraintData in data.constraints) {
+				IConstraint constraint = constraintData.Create(this);
+				PhysicsConstraint physicsConstraint = constraint as PhysicsConstraint;
+				if (physicsConstraint != null) physics.Add(physicsConstraint);
+				constraints.Add(constraint);
+			}
+			physics.TrimExcess();
 
+			color = new Color(1, 1, 1, 1);
+			
 			UpdateCache();
 		}
 
@@ -165,10 +109,10 @@ namespace Spine {
 			foreach (Bone bone in skeleton.bones) {
 				Bone newBone;
 				if (bone.parent == null)
-					newBone = new Bone(bone, this, null);
+					newBone = new Bone(bone, null);
 				else {
 					Bone parent = bones.Items[bone.parent.data.index];
-					newBone = new Bone(bone, this, parent);
+					newBone = new Bone(bone, parent);
 					parent.children.Add(newBone);
 				}
 				bones.Add(newBone);
@@ -176,37 +120,25 @@ namespace Spine {
 
 			slots = new ExposedList<Slot>(skeleton.slots.Count);
 			Bone[] bonesItems = bones.Items;
-			foreach (Slot slot in skeleton.slots) {
-				Bone bone = bonesItems[slot.bone.data.index];
-				slots.Add(new Slot(slot, bone));
-			}
+			foreach (Slot slot in skeleton.slots)
+				slots.Add(new Slot(slot, bonesItems[slot.bone.data.index], this));
 
 			drawOrder = new ExposedList<Slot>(slots.Count);
 			Slot[] slotsItems = slots.Items;
 			foreach (Slot slot in skeleton.drawOrder)
 				drawOrder.Add(slotsItems[slot.data.index]);
 
-			ikConstraints = new ExposedList<IkConstraint>(skeleton.ikConstraints.Count);
-			foreach (IkConstraint ikConstraint in skeleton.ikConstraints)
-				ikConstraints.Add(new IkConstraint(ikConstraint, skeleton));
-
-			transformConstraints = new ExposedList<TransformConstraint>(skeleton.transformConstraints.Count);
-			foreach (TransformConstraint transformConstraint in skeleton.transformConstraints)
-				transformConstraints.Add(new TransformConstraint(transformConstraint, skeleton));
-
-			pathConstraints = new ExposedList<PathConstraint>(skeleton.pathConstraints.Count);
-			foreach (PathConstraint pathConstraint in skeleton.pathConstraints)
-				pathConstraints.Add(new PathConstraint(pathConstraint, skeleton));
-
-			physicsConstraints = new ExposedList<PhysicsConstraint>(skeleton.physicsConstraints.Count);
-			foreach (PhysicsConstraint physicsConstraint in skeleton.physicsConstraints)
-				physicsConstraints.Add(new PhysicsConstraint(physicsConstraint, skeleton));
+			physics = new ExposedList<PhysicsConstraint>(skeleton.physics.Count);
+			constraints = new ExposedList<IConstraint>(skeleton.constraints.Count);
+			foreach (IConstraint other in skeleton.constraints) {
+				IConstraint constraint = other.Copy(this);
+				PhysicsConstraint physicsConstraint = constraint as PhysicsConstraint;
+				if (physicsConstraint != null) physics.Add(physicsConstraint);
+				constraints.Add(constraint);
+			}
 
 			skin = skeleton.skin;
-			r = skeleton.r;
-			g = skeleton.g;
-			b = skeleton.b;
-			a = skeleton.a;
+			color = skeleton.color;
 			x = skeleton.x;
 			y = skeleton.y;
 			scaleX = skeleton.scaleX;
@@ -219,8 +151,13 @@ namespace Spine {
 		/// <summary>Caches information about bones and constraints. Must be called if the <see cref="Skin"/> is modified or if bones, constraints, or
 		/// constraints, or weighted path attachments are added or removed.</summary>
 		public void UpdateCache () {
-			ExposedList<IUpdatable> updateCache = this.updateCache;
 			updateCache.Clear();
+			resetCache.Clear();
+
+			Slot[] slots = this.slots.Items;
+			for (int i = 0, n = this.slots.Count; i < n; i++) {
+				slots[i].UsePose();
+			}
 
 			int boneCount = this.bones.Count;
 			Bone[] bones = this.bones.Items;
@@ -228,6 +165,7 @@ namespace Spine {
 				Bone bone = bones[i];
 				bone.sorted = bone.data.skinRequired;
 				bone.active = !bone.sorted;
+				bone.UsePose();
 			}
 			if (skin != null) {
 				BoneData[] skinBones = skin.bones.Items;
@@ -240,180 +178,55 @@ namespace Spine {
 					} while (bone != null);
 				}
 			}
+			IConstraint[] constraints = this.constraints.Items;
 
-			int ikCount = this.ikConstraints.Count, transformCount = this.transformConstraints.Count, pathCount = this.pathConstraints.Count,
-				physicsCount = this.physicsConstraints.Count;
-			IkConstraint[] ikConstraints = this.ikConstraints.Items;
-			TransformConstraint[] transformConstraints = this.transformConstraints.Items;
-			PathConstraint[] pathConstraints = this.pathConstraints.Items;
-			PhysicsConstraint[] physicsConstraints = this.physicsConstraints.Items;
-			int constraintCount = ikCount + transformCount + pathCount + physicsCount;
-			for (int i = 0; i < constraintCount; i++) {
-				for (int ii = 0; ii < ikCount; ii++) {
-					IkConstraint constraint = ikConstraints[ii];
-					if (constraint.data.order == i) {
-						SortIkConstraint(constraint);
-						goto continue_outer;
-					}
-				}
-				for (int ii = 0; ii < transformCount; ii++) {
-					TransformConstraint constraint = transformConstraints[ii];
-					if (constraint.data.order == i) {
-						SortTransformConstraint(constraint);
-						goto continue_outer;
-					}
+			{ // scope added to prevent compile error of n already being declared in enclosing scope
+				int n = this.constraints.Count;
+				for (int i = 0; i < n; i++) {
+					constraints[i].UsePose();
 				}
-				for (int ii = 0; ii < pathCount; ii++) {
-					PathConstraint constraint = pathConstraints[ii];
-					if (constraint.data.order == i) {
-						SortPathConstraint(constraint);
-						goto continue_outer;
-					}
-				}
-				for (int ii = 0; ii < physicsCount; ii++) {
-					PhysicsConstraint constraint = physicsConstraints[ii];
-					if (constraint.data.order == i) {
-						SortPhysicsConstraint(constraint);
-						goto continue_outer;
-					}
+				for (int i = 0; i < n; i++) {
+					IConstraint constraint = constraints[i];
+					constraint.Active = constraint.IsSourceActive
+						&& (!constraint.IData.SkinRequired || (skin != null && skin.constraints.Contains(constraint.IData)));
+					if (constraint.Active) constraint.Sort(this);
 				}
-				continue_outer: { }
-			}
-
-			for (int i = 0; i < boneCount; i++)
-				SortBone(bones[i]);
-		}
-
-		private void SortIkConstraint (IkConstraint constraint) {
-			constraint.active = constraint.target.active
-				&& (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data)));
-			if (!constraint.active) return;
-
-			SortBone(constraint.target);
-
-			ExposedList<Bone> constrained = constraint.bones;
-			Bone parent = constrained.Items[0];
-			SortBone(parent);
-
-			if (constrained.Count == 1) {
-				updateCache.Add(constraint);
-				SortReset(parent.children);
-			} else {
-				Bone child = constrained.Items[constrained.Count - 1];
-				SortBone(child);
-
-				updateCache.Add(constraint);
-
-				SortReset(parent.children);
-				child.sorted = true;
-			}
-		}
-
-		private void SortTransformConstraint (TransformConstraint constraint) {
-			constraint.active = constraint.source.active
-				&& (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data)));
-			if (!constraint.active) return;
 
-			SortBone(constraint.source);
-
-			Bone[] constrained = constraint.bones.Items;
-			int boneCount = constraint.bones.Count;
-			if (constraint.data.localSource) {
-				for (int i = 0; i < boneCount; i++) {
-					Bone child = constrained[i];
-					SortBone(child.parent);
-					SortBone(child);
-				}
-			} else {
 				for (int i = 0; i < boneCount; i++)
-					SortBone(constrained[i]);
-			}
-
-			updateCache.Add(constraint);
-
-			for (int i = 0; i < boneCount; i++)
-				SortReset(constrained[i].children);
-			for (int i = 0; i < boneCount; i++)
-				constrained[i].sorted = true;
-		}
-
-		private void SortPathConstraint (PathConstraint constraint) {
-			constraint.active = constraint.slot.bone.active
-				&& (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data)));
-			if (!constraint.active) return;
-
-			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);
-
-			SortPathConstraintAttachment(slot.attachment, slotBone);
-
-			Bone[] constrained = constraint.bones.Items;
-			int boneCount = constraint.bones.Count;
-			for (int i = 0; i < boneCount; i++)
-				SortBone(constrained[i]);
-
-			updateCache.Add(constraint);
-
-			for (int i = 0; i < boneCount; i++)
-				SortReset(constrained[i].children);
-			for (int i = 0; i < boneCount; i++)
-				constrained[i].sorted = true;
-		}
-
-		private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) {
-			foreach (Skin.SkinEntry entry in skin.Attachments)
-				if (entry.SlotIndex == slotIndex) SortPathConstraintAttachment(entry.Attachment, slotBone);
-		}
+					SortBone(bones[i]);
 
-		private void SortPathConstraintAttachment (Attachment attachment, Bone slotBone) {
-			if (!(attachment is PathAttachment)) return;
-			int[] pathBones = ((PathAttachment)attachment).bones;
-			if (pathBones == null)
-				SortBone(slotBone);
-			else {
-				Bone[] bones = this.bones.Items;
-				for (int i = 0, n = pathBones.Length; i < n;) {
-					int nn = pathBones[i++];
-					nn += i;
-					while (i < nn)
-						SortBone(bones[pathBones[i++]]);
+				object[] updateCache = this.updateCache.Items;
+				n = this.updateCache.Count;
+				for (int i = 0; i < n; i++) {
+					Bone bone = updateCache[i] as Bone;
+					if (bone != null) updateCache[i] = bone.applied;
 				}
 			}
 		}
 
-		private void SortPhysicsConstraint (PhysicsConstraint constraint) {
-			Bone bone = constraint.bone;
-			constraint.active = bone.active
-				&& (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data)));
-			if (!constraint.active) return;
-
-			SortBone(bone);
-
-			updateCache.Add(constraint);
-
-			SortReset(bone.children);
-			bone.sorted = true;
+		internal void Constrained (IPosed obj) {
+			if (obj.PoseEqualsApplied) { // if (obj.pose == obj.applied) {
+				obj.UseConstrained();
+				resetCache.Add(obj);
+			}
 		}
 
-		private void SortBone (Bone bone) {
-			if (bone.sorted) return;
+		internal void SortBone (Bone bone) {
+			if (bone.sorted || !bone.active) return;
 			Bone parent = bone.parent;
 			if (parent != null) SortBone(parent);
 			bone.sorted = true;
 			updateCache.Add(bone);
 		}
 
-		private static void SortReset (ExposedList<Bone> bones) {
-			Bone[] bonesItems = bones.Items;
+		internal void SortReset (ExposedList<Bone> bones) {
+			Bone[] items = bones.Items;
 			for (int i = 0, n = bones.Count; i < n; i++) {
-				Bone bone = bonesItems[i];
-				if (!bone.active) continue;
-				if (bone.sorted) SortReset(bone.children);
-				bone.sorted = false;
+				Bone bone = items[i];
+				if (bone.active) {
+					if (bone.sorted) SortReset(bone.children);
+					bone.sorted = false;
+				}
 			}
 		}
 
@@ -424,113 +237,55 @@ namespace Spine {
 		/// Runtimes Guide.</para>
 		/// </summary>
 		public void UpdateWorldTransform (Physics physics) {
-			Bone[] bones = this.bones.Items;
-			for (int i = 0, n = this.bones.Count; i < n; i++) {
-				Bone bone = bones[i];
-				bone.ax = bone.x;
-				bone.ay = bone.y;
-				bone.arotation = bone.rotation;
-				bone.ascaleX = bone.scaleX;
-				bone.ascaleY = bone.scaleY;
-				bone.ashearX = bone.shearX;
-				bone.ashearY = bone.shearY;
-			}
+			update++;
 
-			IUpdatable[] updateCache = this.updateCache.Items;
-			for (int i = 0, n = this.updateCache.Count; i < n; i++)
-				updateCache[i].Update(physics);
-		}
-
-		/// <summary>
-		/// Temporarily sets the root bone as a child of the specified bone, then updates the world transform for each bone and applies
-		/// all constraints.
-		/// </summary>
-		public void UpdateWorldTransform (Physics physics, Bone parent) {
-			if (parent == null) throw new ArgumentNullException("parent", "parent cannot be null.");
-
-			// Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection.
-			Bone rootBone = this.RootBone;
-			float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
-			rootBone.worldX = pa * x + pb * y + parent.worldX;
-			rootBone.worldY = pc * x + pd * y + parent.worldY;
-
-			float rx = (rootBone.rotation + rootBone.shearX) * MathUtils.DegRad;
-			float ry = (rootBone.rotation + 90 + rootBone.shearY) * MathUtils.DegRad;
-			float la = (float)Math.Cos(rx) * rootBone.scaleX;
-			float lb = (float)Math.Cos(ry) * rootBone.scaleY;
-			float lc = (float)Math.Sin(rx) * rootBone.scaleX;
-			float ld = (float)Math.Sin(ry) * rootBone.scaleY;
-			rootBone.a = (pa * la + pb * lc) * scaleX;
-			rootBone.b = (pa * lb + pb * ld) * scaleX;
-			rootBone.c = (pc * la + pd * lc) * scaleY;
-			rootBone.d = (pc * lb + pd * ld) * scaleY;
-
-			// Update everything except root bone.
-			IUpdatable[] updateCache = this.updateCache.Items;
-			for (int i = 0, n = this.updateCache.Count; i < n; i++) {
-				IUpdatable updatable = updateCache[i];
-				if (updatable != rootBone) updatable.Update(physics);
+			IPosed[] resetCache = this.resetCache.Items;
+			for (int i = 0, n = this.resetCache.Count; i < n; i++) {
+				resetCache[i].ResetConstrained();
 			}
-		}
-
-		/// <summary>
-		/// Calls <see cref="PhysicsConstraint.Translate(float, float)"/> for each physics constraint.
-		/// </summary>
-		public void PhysicsTranslate (float x, float y) {
-			PhysicsConstraint[] physicsConstraints = this.physicsConstraints.Items;
-			for (int i = 0, n = this.physicsConstraints.Count; i < n; i++)
-				physicsConstraints[i].Translate(x, y);
-		}
 
-		/// <summary>
-		/// Calls <see cref="PhysicsConstraint.Rotate(float, float, float)"/> for each physics constraint.
-		/// </summary>
-		public void PhysicsRotate (float x, float y, float degrees) {
-			PhysicsConstraint[] physicsConstraints = this.physicsConstraints.Items;
-			for (int i = 0, n = this.physicsConstraints.Count; i < n; i++)
-				physicsConstraints[i].Rotate(x, y, degrees);
-		}
-
-		/// <summary>Increments the skeleton's <see cref="time"/>.</summary>
-		public void Update (float delta) {
-			time += delta;
+			object[] updateCache = this.updateCache.Items;
+			for (int i = 0, n = this.updateCache.Count; i < n; i++)
+				((IUpdate)updateCache[i]).Update(this, physics);
 		}
 
-		/// <summary>Sets the bones, constraints, and slots to their setup pose values.</summary>
-		public void SetToSetupPose () {
-			SetBonesToSetupPose();
-			SetSlotsToSetupPose();
+		/// <summary>Sets the bones, constraints, slots, and draw order to their setup pose values.</summary>
+		public void SetupPose () {
+			SetupPoseBones();
+			SetupPoseSlots();
 		}
 
 		/// <summary>Sets the bones and constraints to their setup pose values.</summary>
-		public void SetBonesToSetupPose () {
+		public void SetupPoseBones () {
 			Bone[] bones = this.bones.Items;
 			for (int i = 0, n = this.bones.Count; i < n; i++)
-				bones[i].SetToSetupPose();
-
-			IkConstraint[] ikConstraints = this.ikConstraints.Items;
-			for (int i = 0, n = this.ikConstraints.Count; i < n; i++)
-				ikConstraints[i].SetToSetupPose();
-
-			TransformConstraint[] transformConstraints = this.transformConstraints.Items;
-			for (int i = 0, n = this.transformConstraints.Count; i < n; i++)
-				transformConstraints[i].SetToSetupPose();
+				bones[i].SetupPose();
 
-			PathConstraint[] pathConstraints = this.pathConstraints.Items;
-			for (int i = 0, n = this.pathConstraints.Count; i < n; i++)
-				pathConstraints[i].SetToSetupPose();
-
-			PhysicsConstraint[] physicsConstraints = this.physicsConstraints.Items;
-			for (int i = 0, n = this.physicsConstraints.Count; i < n; i++)
-				physicsConstraints[i].SetToSetupPose();
+			IConstraint[] constraints = this.constraints.Items;
+			for (int i = 0, n = this.constraints.Count; i < n; i++)
+				constraints[i].SetupPose();
 		}
 
-		public void SetSlotsToSetupPose () {
+		/// <summary>Sets the slots and draw order to their setup pose values.</summary>
+		public void SetupPoseSlots () {
 			Slot[] slots = this.slots.Items;
 			int n = this.slots.Count;
 			Array.Copy(slots, 0, drawOrder.Items, 0, n);
 			for (int i = 0; i < n; i++)
-				slots[i].SetToSetupPose();
+				slots[i].SetupPose();
+		}
+
+		/// <summary>The skeleton's setup pose data.</summary>
+		public SkeletonData Data { get { return data; } }
+		/// <summary>The skeleton's bones, sorted parent first. The root bone is always the first bone.</summary>
+		public ExposedList<Bone> Bones { get { return bones; } }
+		/// <summary>
+		/// The list of bones and constraints, sorted in the order they should be updated, as computed by <see cref="UpdateCache()"/>.
+		/// </summary>
+		public ExposedList<object> UpdateCacheList { get { return updateCache; } }
+		/// <summary>Returns the root bone, or null if the skeleton has no bones.</summary>
+		public Bone RootBone {
+			get { return bones.Count == 0 ? null : bones.Items[0]; }
 		}
 
 		/// <summary>Finds a bone by comparing each bone's name. It is more efficient to cache the results of this method than to call it
@@ -546,6 +301,9 @@ namespace Spine {
 			return null;
 		}
 
+		/// <summary>The skeleton's slots.</summary>
+		public ExposedList<Slot> Slots { get { return slots; } }
+
 		/// <summary>Finds a slot by comparing each slot's name. It is more efficient to cache the results of this method than to call it
 		/// repeatedly.</summary>
 		/// <returns>May be null.</returns>
@@ -559,6 +317,25 @@ namespace Spine {
 			return null;
 		}
 
+		/// <summary>
+		/// The skeleton's slots in the order they should be drawn. The returned array may be modified to change the draw order.
+		/// </summary>
+		public ExposedList<Slot> DrawOrder {
+			get { return drawOrder; }
+			set {
+				if (value == null) throw new ArgumentNullException("drawOrder ", "drawOrder cannot be null.");
+				this.drawOrder = value;
+			}
+		}
+
+		/// <summary>The skeleton's current skin. May be null. See <see cref="SetSkin(Spine.Skin)"/></summary>
+		public Skin Skin {
+			/// <summary>The skeleton's current skin. May be null.</summary>
+			get { return skin; }
+			/// <summary>Sets a skin, <see cref="SetSkin(Skin)"/>.</summary>
+			set { SetSkin(value); }
+		}
+
 		/// <summary>Sets a skin by name (see <see cref="SetSkin(Spine.Skin)"/>).</summary>
 		public void SetSkin (string skinName) {
 			Skin foundSkin = data.FindSkin(skinName);
@@ -570,13 +347,12 @@ namespace Spine {
 		/// <para>Sets the skin used to look up attachments before looking in the <see cref="SkeletonData.DefaultSkin"/>. If the
 		/// skin is changed, <see cref="UpdateCache()"/> is called.
 		/// </para>
-		/// <para>Attachments from the new skin are attached if the corresponding attachment from the old skin was attached.
-		/// If there was no old skin, each slot's setup mode attachment is attached from the new skin.
+		/// <para>Attachments from the new skin are attached if the corresponding attachment from the old skin was attached. If there was no
+		/// old skin, each slot's setup mode attachment is attached from the new skin.
 		/// </para>
 		/// <para>After changing the skin, the visible attachments can be reset to those attached in the setup pose by calling
-		/// <see cref="Skeleton.SetSlotsToSetupPose()"/>.
-		/// Also, often <see cref="AnimationState.Apply(Skeleton)"/> is called before the next time the
-		/// skeleton is rendered to allow any attachment keys in the current animation(s) to hide or show attachments from the new skin.</para>
+		/// <see cref="Skeleton.SetupPoseSlots()"/>. Also, often <see cref="AnimationState.Apply(Skeleton)"/> is called before the next time the skeleton is
+		/// rendered to allow any attachment keys in the current animation(s) to hide or show attachments from the new skin.</para>
 		/// </summary>
 		/// <param name="newSkin">May be null.</param>
 		public void SetSkin (Skin newSkin) {
@@ -591,7 +367,7 @@ namespace Spine {
 						string name = slot.data.attachmentName;
 						if (name != null) {
 							Attachment attachment = newSkin.GetAttachment(i, name);
-							if (attachment != null) slot.Attachment = attachment;
+							if (attachment != null) slot.pose.Attachment = attachment;
 						}
 					}
 				}
@@ -600,13 +376,20 @@ namespace Spine {
 			UpdateCache();
 		}
 
-		/// <summary>Finds an attachment by looking in the <see cref="Skeleton.Skin"/> and <see cref="SkeletonData.DefaultSkin"/> using the slot name and attachment name.</summary>
+		/// <summary>Finds an attachment by looking in the <see cref="Skeleton.Skin"/> and <see cref="SkeletonData.DefaultSkin"/> using the slot name and attachment
+		/// name.</summary>
 		/// <returns>May be null.</returns>
+		/// <seealso cref="GetAttachment(int, string)"/>
 		public Attachment GetAttachment (string slotName, string attachmentName) {
-			return GetAttachment(data.FindSlot(slotName).index, attachmentName);
+			SlotData slot = data.FindSlot(slotName);
+			if (slot == null) throw new ArgumentException("Slot not found: " + slotName, "slotName");
+			return GetAttachment(slot.index, attachmentName);
 		}
 
-		/// <summary>Finds an attachment by looking in the skin and skeletonData.defaultSkin using the slot index and attachment name.First the skin is checked and if the attachment was not found, the default skin is checked.</summary>
+		/// <summary>Finds an attachment by looking in the skin and skeletonData.defaultSkin using the slot index and
+		/// attachment name. First the skin is checked and if the attachment was not found, the default skin is checked.</summary>
+		/// <para>
+		/// See <a href="http://esotericsoftware.com/spine-runtime-skins">Runtime skins</a> in the Spine Runtimes Guide.</para>
 		/// <returns>May be null.</returns>
 		public Attachment GetAttachment (int slotIndex, string attachmentName) {
 			if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null.");
@@ -614,77 +397,40 @@ namespace Spine {
 				Attachment attachment = skin.GetAttachment(slotIndex, attachmentName);
 				if (attachment != null) return attachment;
 			}
-			return data.defaultSkin != null ? data.defaultSkin.GetAttachment(slotIndex, attachmentName) : null;
+			if (data.defaultSkin != null) return data.defaultSkin.GetAttachment(slotIndex, attachmentName);
+			return null;
 		}
 
-		/// <summary>A convenience method to set an attachment by finding the slot with FindSlot, finding the attachment with GetAttachment, then setting the slot's slot.Attachment.</summary>
+		/// <summary>A convenience method to set an attachment by finding the slot with <see cref="FindSlot(string)"/>, finding the attachment with
+		/// <see cref="GetAttachment(int, string)"/>, then setting the slot's <see cref="SlotPose.Attachment"/>.</summary>
 		/// <param name="attachmentName">May be null to clear the slot's attachment.</param>
 		public void SetAttachment (string slotName, string attachmentName) {
 			if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
-			Slot[] slots = this.slots.Items;
-			for (int i = 0, n = this.slots.Count; i < n; i++) {
-				Slot slot = slots[i];
-				if (slot.data.name == slotName) {
-					Attachment attachment = null;
-					if (attachmentName != null) {
-						attachment = GetAttachment(i, attachmentName);
-						if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName);
-					}
-					slot.Attachment = attachment;
-					return;
-				}
-			}
-			throw new Exception("Slot not found: " + slotName);
-		}
 
-		/// <summary>Finds an IK constraint by comparing each IK constraint's name. It is more efficient to cache the results of this method
-		/// than to call it repeatedly.</summary>
-		/// <returns>May be null.</returns>
-		public IkConstraint FindIkConstraint (string constraintName) {
-			if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
-			IkConstraint[] ikConstraints = this.ikConstraints.Items;
-			for (int i = 0, n = this.ikConstraints.Count; i < n; i++) {
-				IkConstraint ikConstraint = ikConstraints[i];
-				if (ikConstraint.data.name == constraintName) return ikConstraint;
+			Slot slot = FindSlot(slotName);
+			if (slot == null) throw new ArgumentException("Slot not found: " + slotName, "slotName");
+			Attachment attachment = null;
+			if (attachmentName != null) {
+				attachment = GetAttachment(slot.data.index, attachmentName);
+				if (attachment == null)
+					throw new ArgumentException("Attachment not found: " + attachmentName + ", for slot: " + slotName, "attachmentName");
 			}
-			return null;
+			slot.pose.Attachment = attachment;
 		}
 
-		/// <summary>Finds a transform constraint by comparing each transform constraint's name. It is more efficient to cache the results of
-		/// this method than to call it repeatedly.</summary>
-		/// <returns>May be null.</returns>
-		public TransformConstraint FindTransformConstraint (string constraintName) {
-			if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
-			TransformConstraint[] transformConstraints = this.transformConstraints.Items;
-			for (int i = 0, n = this.transformConstraints.Count; i < n; i++) {
-				TransformConstraint transformConstraint = transformConstraints[i];
-				if (transformConstraint.data.Name == constraintName) return transformConstraint;
-			}
-			return null;
-		}
+		/// <summary>The skeleton's constraints.</summary>
+		public ExposedList<IConstraint> Constraints { get { return constraints; } }
+		/// <summary>The skeleton's physics constraints.</summary>
+		public ExposedList<PhysicsConstraint> PhysicsConstraints { get { return physics; } }
 
-		/// <summary>Finds a path constraint by comparing each path constraint's name. It is more efficient to cache the results of this method
-		/// than to call it repeatedly.</summary>
 		/// <returns>May be null.</returns>
-		public PathConstraint FindPathConstraint (string constraintName) {
+		public T FindConstraint<T> (string constraintName) where T : class, IConstraint {
 			if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
-			PathConstraint[] pathConstraints = this.pathConstraints.Items;
-			for (int i = 0, n = this.pathConstraints.Count; i < n; i++) {
-				PathConstraint constraint = pathConstraints[i];
-				if (constraint.data.Name.Equals(constraintName)) return constraint;
-			}
-			return null;
-		}
 
-		/// <summary>Finds a physics constraint by comparing each physics constraint's name. It is more efficient to cache the results of this
-		/// method than to call it repeatedly.</summary>
-		/// <returns>May be null.</returns>
-		public PhysicsConstraint FindPhysicsConstraint (String constraintName) {
-			if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
-			PhysicsConstraint[] physicsConstraints = this.physicsConstraints.Items;
-			for (int i = 0, n = this.physicsConstraints.Count; i < n; i++) {
-				PhysicsConstraint constraint = physicsConstraints[i];
-				if (constraint.data.name.Equals(constraintName)) return constraint;
+			IConstraint[] constraints = this.constraints.Items;
+			for (int i = 0, n = this.constraints.Count; i < n; i++) {
+				IConstraint constraint = constraints[i];
+				if (constraint is T && constraint.IData.Name == constraintName) return (T)constraint;
 			}
 			return null;
 		}
@@ -708,13 +454,13 @@ namespace Spine {
 				int verticesLength = 0;
 				float[] vertices = null;
 				int[] triangles = null;
-				Attachment attachment = slot.attachment;
+				Attachment attachment = slot.pose.attachment;
 				RegionAttachment region = attachment as RegionAttachment;
 				if (region != null) {
 					verticesLength = 8;
 					vertices = temp;
 					if (vertices.Length < 8) vertices = temp = new float[8];
-					region.ComputeWorldVertices(slot, temp, 0, 2);
+					region.ComputeWorldVertices(slot, vertices, 0, 2);
 					triangles = quadTriangles;
 				} else {
 					MeshAttachment mesh = attachment as MeshAttachment;
@@ -722,12 +468,13 @@ namespace Spine {
 						verticesLength = mesh.WorldVerticesLength;
 						vertices = temp;
 						if (vertices.Length < verticesLength) vertices = temp = new float[verticesLength];
-						mesh.ComputeWorldVertices(slot, 0, verticesLength, temp, 0, 2);
+						mesh.ComputeWorldVertices(this, slot, 0, verticesLength, temp, 0, 2);
 						triangles = mesh.Triangles;
 					} else if (clipper != null) {
 						ClippingAttachment clip = attachment as ClippingAttachment;
 						if (clip != null) {
-							clipper.ClipStart(slot, clip);
+							clipper.ClipEnd(slot);
+							clipper.ClipStart(this, slot, clip);
 							continue;
 						}
 					}
@@ -757,23 +504,95 @@ namespace Spine {
 			vertexBuffer = temp;
 		}
 
-		override public string ToString () {
-			return data.name;
+		/// <returns>A copy of the color to tint all the skeleton's attachments.</returns>
+		public Color GetColor () {
+			return color;
+		}
+
+		/// <summary>Sets the color to tint all the skeleton's attachments.</summary>
+		public void SetColor (Color color) {
+			this.color = color;
+		}
+
+		/// <summary>
+		/// A convenience method for setting the skeleton color. The color can also be set by
+		/// <see cref="SetColor(Color)"/>
+		/// </summary>
+		public void SetColor (float r, float g, float b, float a) {
+			color = new Color(r, g, b, a);
+		}
+
+		/// <summary><para> Scales the entire skeleton on the X axis.</para>
+		/// <para>
+		/// Bones that do not inherit scale are still affected by this property.</para></summary>
+		public float ScaleX { get { return scaleX; } set { scaleX = value; } }
+		/// <summary><para> Scales the entire skeleton on the Y axis.</para>
+		/// <para>
+		/// Bones that do not inherit scale are still affected by this property.</para></summary>
+		public float ScaleY { get { return scaleY * (Bone.yDown ? -1 : 1); } set { scaleY = value; } }
+
+		/// <summary>
+		/// Scales the entire skeleton on the X and Y axes.
+		/// <para>
+		/// Bones that do not inherit scale are still affected by this property.
+		/// </para></summary>
+		public void SetScale (float scaleX, float scaleY) {
+			this.scaleX = scaleX;
+			this.scaleY = scaleY;
+		}
+
+		/// <summary><para>The skeleton X position, which is added to the root bone worldX position.</para>
+		/// <para>
+		/// Bones that do not inherit translation are still affected by this property.</para></summary>
+		public float X { get { return x; } set { x = value; } }
+		/// <summary><para>The skeleton Y position, which is added to the root bone worldY position.</para>
+		/// <para>
+		/// Bones that do not inherit translation are still affected by this property.</para></summary>
+		public float Y { get { return y; } set { y = value; } }
+
+		/// <summary>
+		/// Sets the skeleton X and Y position, which is added to the root bone worldX and worldY position.
+		/// <para>
+		/// Bones that do not inherit translation are still affected by this property.</para></summary>
+		public void SetPosition (float x, float y) {
+			this.x = x;
+			this.y = y;
 		}
 
-		/// <summary>Determines how physics and other non-deterministic updates are applied.</summary>
-		public enum Physics {
-			/// <summary>Physics are not updated or applied.</summary>
-			None,
+		public float WindX { get { return windX; } set { windX = value; } }
+		public float WindY { get { return windY; } set { windY = value; } }
+		public float GravityX { get { return gravityX; } set { gravityX = value; } }
+		public float GravityY { get { return gravityY; } set { gravityY = value; } }
 
-			/// <summary>Physics are reset to the current pose.</summary>
-			Reset,
+		/// <summary>
+		/// Calls <see cref="PhysicsConstraint.Translate(float, float)"/> for each physics constraint.
+		/// </summary>
+		public void PhysicsTranslate (float x, float y) {
+			PhysicsConstraint[] physicsConstraints = this.physics.Items;
+			for (int i = 0, n = this.physics.Count; i < n; i++)
+				physicsConstraints[i].Translate(x, y);
+		}
 
-			/// <summary>Physics are updated and the pose from physics is applied.</summary>
-			Update,
+		/// <summary>
+		/// Calls <see cref="PhysicsConstraint.Rotate(float, float, float)"/> for each physics constraint.
+		/// </summary>
+		public void PhysicsRotate (float x, float y, float degrees) {
+			PhysicsConstraint[] physicsConstraints = this.physics.Items;
+			for (int i = 0, n = this.physics.Count; i < n; i++)
+				physicsConstraints[i].Rotate(x, y, degrees);
+		}
+
+		/// <summary>Returns the skeleton's time. This is used for time-based manipulations, such as <see cref="PhysicsConstraint"/>.</summary>
+		/// <seealso cref="Update(float)"/>
+		public float Time { get { return time; } set { time = value; } }
 
-			/// <summary>Physics are not updated but the pose from physics is applied.</summary>
-			Pose
+		/// <summary>Increments the skeleton's <see cref="time"/>.</summary>
+		public void Update (float delta) {
+			time += delta;
+		}
+
+		override public string ToString () {
+			return data.name;
 		}
 	}
 }

+ 367 - 319
spine-csharp/src/SkeletonBinary.cs

@@ -27,10 +27,6 @@
  * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
 
-#if (UNITY_5 || UNITY_5_3_OR_NEWER || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1)
-#define IS_UNITY
-#endif
-
 using System;
 using System.Collections.Generic;
 using System.IO;
@@ -42,7 +38,7 @@ using Windows.Storage;
 #endif
 
 namespace Spine {
-
+	using static Spine.TransformConstraintData;
 	using FromProperty = TransformConstraintData.FromProperty;
 	using FromRotate = TransformConstraintData.FromRotate;
 	using FromScaleX = TransformConstraintData.FromScaleX;
@@ -78,6 +74,12 @@ namespace Spine {
 		public const int SLOT_RGB2 = 4;
 		public const int SLOT_ALPHA = 5;
 
+		public const int CONSTRAINT_IK = 0;
+		public const int CONSTRAINT_PATH = 1;
+		public const int CONSTRAINT_TRANSFORM = 2;
+		public const int CONSTRAINT_PHYSICS = 3;
+		public const int CONSTRAINT_SLIDER = 4;
+
 		public const int ATTACHMENT_DEFORM = 0;
 		public const int ATTACHMENT_SEQUENCE = 1;
 
@@ -94,6 +96,9 @@ namespace Spine {
 		public const int PHYSICS_MIX = 7;
 		public const int PHYSICS_RESET = 8;
 
+		public const int SLIDER_TIME = 0;
+		public const int SLIDER_MIX = 1;
+
 		public const int CURVE_LINEAR = 0;
 		public const int CURVE_STEPPED = 1;
 		public const int CURVE_BEZIER = 2;
@@ -128,9 +133,13 @@ namespace Spine {
 #else
 			using (FileStream input = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) {
 #endif
-				SkeletonData skeletonData = ReadSkeletonData(input);
-				skeletonData.name = Path.GetFileNameWithoutExtension(path);
-				return skeletonData;
+				try {
+					SkeletonData skeletonData = ReadSkeletonData(input);
+					skeletonData.name = Path.GetFileNameWithoutExtension(path);
+					return skeletonData;
+				} catch (Exception ex) {
+					throw new SerializationException("Error reading binary skeleton file: " + path, ex);
+				}
 			}
 		}
 #endif // WINDOWS_STOREAPP
@@ -146,302 +155,344 @@ namespace Spine {
 			if (file == null) throw new ArgumentNullException("file");
 			float scale = this.scale;
 
-			var skeletonData = new SkeletonData();
 			var input = new SkeletonInput(file);
+			var skeletonData = new SkeletonData();
+			string version = null;
+			try {
+				long hash = input.ReadLong();
+				skeletonData.hash = hash == 0 ? null : hash.ToString();
+				skeletonData.version = input.ReadString();
+				version = skeletonData.version;
+				if (string.IsNullOrEmpty(skeletonData.version)) skeletonData.version = null;
+				// early return for old 3.8 format instead of reading past the end
+				if (skeletonData.version.Length > 13) return null;
+				skeletonData.x = input.ReadFloat();
+				skeletonData.y = input.ReadFloat();
+				skeletonData.width = input.ReadFloat();
+				skeletonData.height = input.ReadFloat();
+				skeletonData.referenceScale = input.ReadFloat() * scale;
+
+				bool nonessential = input.ReadBoolean();
 
-			long hash = input.ReadLong();
-			skeletonData.hash = hash == 0 ? null : hash.ToString();
-			skeletonData.version = input.ReadString();
-			if (skeletonData.version.Length == 0) skeletonData.version = null;
-			// early return for old 3.8 format instead of reading past the end
-			if (skeletonData.version.Length > 13) return null;
-			skeletonData.x = input.ReadFloat();
-			skeletonData.y = input.ReadFloat();
-			skeletonData.width = input.ReadFloat();
-			skeletonData.height = input.ReadFloat();
-			skeletonData.referenceScale = input.ReadFloat() * scale;
-
-			bool nonessential = input.ReadBoolean();
-
-			if (nonessential) {
-				skeletonData.fps = input.ReadFloat();
+				if (nonessential) {
+					skeletonData.fps = input.ReadFloat();
 
-				skeletonData.imagesPath = input.ReadString();
-				if (string.IsNullOrEmpty(skeletonData.imagesPath)) skeletonData.imagesPath = null;
+					skeletonData.imagesPath = input.ReadString();
+					if (string.IsNullOrEmpty(skeletonData.imagesPath)) skeletonData.imagesPath = null;
 
-				skeletonData.audioPath = input.ReadString();
-				if (string.IsNullOrEmpty(skeletonData.audioPath)) skeletonData.audioPath = null;
-			}
+					skeletonData.audioPath = input.ReadString();
+					if (string.IsNullOrEmpty(skeletonData.audioPath)) skeletonData.audioPath = null;
+				}
 
-			int n;
-			Object[] o;
+				int n;
+				object[] o;
 
-			// Strings.
-			o = input.strings = new String[n = input.ReadInt(true)];
-			for (int i = 0; i < n; i++)
-				o[i] = input.ReadString();
-
-			// Bones.
-			BoneData[] bones = skeletonData.bones.Resize(n = input.ReadInt(true)).Items;
-			for (int i = 0; i < n; i++) {
-				String name = input.ReadString();
-				BoneData parent = i == 0 ? null : bones[input.ReadInt(true)];
-				var data = new BoneData(i, name, parent);
-				data.rotation = input.ReadFloat();
-				data.x = input.ReadFloat() * scale;
-				data.y = input.ReadFloat() * scale;
-				data.scaleX = input.ReadFloat();
-				data.scaleY = input.ReadFloat();
-				data.shearX = input.ReadFloat();
-				data.shearY = input.ReadFloat();
-				data.Length = input.ReadFloat() * scale;
-				data.inherit = InheritEnum.Values[input.ReadInt(true)];
-				data.skinRequired = input.ReadBoolean();
-				if (nonessential) { // discard non-essential data
-					input.ReadInt(); // Color.rgba8888ToColor(data.color, input.readInt());
-					input.ReadString(); // data.icon = input.readString();
-					input.ReadBoolean(); // data.visible = input.readBoolean();
+				// Strings.
+				o = input.strings = new String[n = input.ReadInt(true)];
+				for (int i = 0; i < n; i++)
+					o[i] = input.ReadString();
+
+				// Bones.
+				BoneData[] bones = skeletonData.bones.Resize(n = input.ReadInt(true)).Items;
+				for (int i = 0; i < n; i++) {
+					string name = input.ReadString();
+					BoneData parent = i == 0 ? null : bones[input.ReadInt(true)];
+					var data = new BoneData(i, name, parent);
+					BoneLocal setup = data.setup;
+					setup.rotation = input.ReadFloat();
+					setup.x = input.ReadFloat() * scale;
+					setup.y = input.ReadFloat() * scale;
+					setup.scaleX = input.ReadFloat();
+					setup.scaleY = input.ReadFloat();
+					setup.shearX = input.ReadFloat();
+					setup.shearY = input.ReadFloat();
+					setup.inherit = InheritEnum.Values[input.ReadSByte()];
+					data.Length = input.ReadFloat() * scale;
+					data.skinRequired = input.ReadBoolean();
+					if (nonessential) { // discard non-essential data
+						input.ReadInt(); // Color.rgba8888ToColor(data.color, input.readInt());
+						input.ReadString(); // data.icon = input.readString();
+						input.ReadBoolean(); // data.visible = input.readBoolean();
+					}
+					bones[i] = data;
 				}
-				bones[i] = data;
-			}
 
-			// Slots.
-			SlotData[] slots = skeletonData.slots.Resize(n = input.ReadInt(true)).Items;
-			for (int i = 0; i < n; i++) {
-				String slotName = input.ReadString();
-
-				BoneData boneData = bones[input.ReadInt(true)];
-				var slotData = new SlotData(i, slotName, boneData);
-				int color = input.ReadInt();
-				slotData.r = ((color & 0xff000000) >> 24) / 255f;
-				slotData.g = ((color & 0x00ff0000) >> 16) / 255f;
-				slotData.b = ((color & 0x0000ff00) >> 8) / 255f;
-				slotData.a = ((color & 0x000000ff)) / 255f;
-
-				int darkColor = input.ReadInt(); // 0x00rrggbb
-				if (darkColor != -1) {
-					slotData.hasSecondColor = true;
-					slotData.r2 = ((darkColor & 0x00ff0000) >> 16) / 255f;
-					slotData.g2 = ((darkColor & 0x0000ff00) >> 8) / 255f;
-					slotData.b2 = ((darkColor & 0x000000ff)) / 255f;
-				}
+				// Slots.
+				SlotData[] slots = skeletonData.slots.Resize(n = input.ReadInt(true)).Items;
+				for (int i = 0; i < n; i++) {
+					string slotName = input.ReadString();
+
+					BoneData boneData = bones[input.ReadInt(true)];
+					var slotData = new SlotData(i, slotName, boneData);
+					slotData.setup.SetColor(((uint)input.ReadInt()).RGBA8888ToColor());
+					int darkColor = input.ReadInt(); // 0x00rrggbb
+					if (darkColor != -1) {
+						slotData.setup.SetDarkColor(((uint)darkColor).XRGB888ToColor());
+					}
 
-				slotData.attachmentName = input.ReadStringRef();
-				slotData.blendMode = (BlendMode)input.ReadInt(true);
-				if (nonessential) {
-					input.ReadBoolean(); // data.visible = input.readBoolean(); data.path = path;
+					slotData.attachmentName = input.ReadStringRef();
+					slotData.blendMode = (BlendMode)input.ReadInt(true);
+					if (nonessential) {
+						input.ReadBoolean(); // data.visible = input.readBoolean(); data.path = path;
+					}
+					slots[i] = slotData;
 				}
-				slots[i] = slotData;
-			}
 
-			// IK constraints.
-			o = skeletonData.ikConstraints.Resize(n = input.ReadInt(true)).Items;
-			for (int i = 0, nn; i < n; i++) {
-				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++)
-					constraintBones[ii] = bones[input.ReadInt(true)];
-				data.target = bones[input.ReadInt(true)];
-				int flags = input.Read();
-				data.skinRequired = (flags & 1) != 0;
-				data.bendDirection = (flags & 2) != 0 ? 1 : -1;
-				data.compress = (flags & 4) != 0;
-				data.stretch = (flags & 8) != 0;
-				data.uniform = (flags & 16) != 0;
-				if ((flags & 32) != 0) data.mix = (flags & 64) != 0 ? input.ReadFloat() : 1;
-				if ((flags & 128) != 0) data.softness = input.ReadFloat() * scale;
-				o[i] = data;
-			}
+				// Constraints.
+				int constraintCount = input.ReadInt(true);
+				IConstraintData[] constraints = skeletonData.constraints.Resize(constraintCount).Items;
+				for (int i = 0; i < constraintCount; i++) {
+					string name = input.ReadString();
+					int nn;
 
-			// Transform constraints.
-			o = skeletonData.transformConstraints.Resize(n = input.ReadInt(true)).Items;
-			for (int i = 0, nn; i < n; i++) {
-				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.source = bones[input.ReadInt(true)];
-				int flags = input.Read();
-				data.skinRequired = (flags & 1) != 0;
-				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++) {
-					float fromScale = 1;
-					FromProperty from;
-					switch (input.ReadSByte()) {
-					case 0: from = new FromRotate(); break;
-					case 1: {
-						from = new FromX();
-						fromScale = scale;
+					switch (input.ReadUByte()) {
+					case CONSTRAINT_IK: {
+						var data = new IkConstraintData(name);
+						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)];
+						int flags = input.Read();
+						data.skinRequired = (flags & 1) != 0;
+						data.uniform = (flags & 2) != 0;
+						IkConstraintPose setup = data.setup;
+						setup.bendDirection = (flags & 4) != 0 ? 1 : -1;
+						setup.compress = (flags & 8) != 0;
+						setup.stretch = (flags & 16) != 0;
+						if ((flags & 32) != 0) setup.mix = (flags & 64) != 0 ? input.ReadFloat() : 1;
+						if ((flags & 128) != 0) setup.softness = input.ReadFloat() * scale;
+						constraints[i] = data;
 						break;
 					}
-					case 2: {
-						from = new FromY();
-						fromScale = scale;
+					case CONSTRAINT_TRANSFORM: {
+						var data = new TransformConstraintData(name);
+						BoneData[] constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items;
+						for (int ii = 0; ii < nn; ii++)
+							constraintBones[ii] = bones[input.ReadInt(true)];
+						data.source = bones[input.ReadInt(true)];
+						int flags = input.Read();
+						data.skinRequired = (flags & 1) != 0;
+						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++) {
+							float fromScale = 1;
+							FromProperty from;
+							switch (input.ReadUByte()) {
+							case 0: from = new FromRotate(); break;
+							case 1: {
+								from = new FromX();
+								fromScale = scale;
+								break;
+							}
+							case 2: {
+								from = new FromY();
+								fromScale = scale;
+								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() * fromScale;
+							ToProperty[] tos = from.to.Resize(tn = input.ReadSByte()).Items;
+							for (int t = 0; t < tn; t++) {
+								float toScale = 1;
+								ToProperty to;
+								switch (input.ReadUByte()) {
+								case 0: to = new ToRotate(); break;
+								case 1: {
+									to = new ToX();
+									toScale = scale;
+									break;
+								}
+								case 2: {
+									to = new ToY();
+									toScale = scale;
+									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() * toScale;
+								to.max = input.ReadFloat() * toScale;
+								to.scale = input.ReadFloat() * (toScale / fromScale);
+								tos[t] = to;
+							}
+							froms[ii] = from;
+						}
+						flags = input.Read();
+						if ((flags & 1) != 0) data.offsets[TransformConstraintData.ROTATION] = input.ReadFloat();
+						if ((flags & 2) != 0) data.offsets[TransformConstraintData.X] = input.ReadFloat() * scale;
+						if ((flags & 4) != 0) data.offsets[TransformConstraintData.Y] = input.ReadFloat() * scale;
+						if ((flags & 8) != 0) data.offsets[TransformConstraintData.SCALEX] = input.ReadFloat();
+						if ((flags & 16) != 0) data.offsets[TransformConstraintData.SCALEY] = input.ReadFloat();
+						if ((flags & 32) != 0) data.offsets[TransformConstraintData.SHEARY] = input.ReadFloat();
+						flags = input.Read();
+						TransformConstraintPose setup = data.setup;
+						if ((flags & 1) != 0) setup.mixRotate = input.ReadFloat();
+						if ((flags & 2) != 0) setup.mixX = input.ReadFloat();
+						if ((flags & 4) != 0) setup.mixY = input.ReadFloat();
+						if ((flags & 8) != 0) setup.mixScaleX = input.ReadFloat();
+						if ((flags & 16) != 0) setup.mixScaleY = input.ReadFloat();
+						if ((flags & 32) != 0) setup.mixShearY = input.ReadFloat();
+						constraints[i] = data;
 						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() * fromScale;
-					ToProperty[] tos = from.to.Resize(tn = input.ReadSByte()).Items;
-					for (int t = 0; t < tn; t++) {
-						float toScale = 1;
-						ToProperty to;
-						switch (input.ReadSByte()) {
-						case 0: to = new ToRotate(); break;
-						case 1: {
-							to = new ToX();
-							toScale = scale;
-							break;
-						}
-						case 2: {
-							to = new ToY();
-							toScale = scale;
-							break;
+					case CONSTRAINT_PATH: {
+						var data = new PathConstraintData(name);
+						BoneData[] constraintBones = data.bones.Resize(nn = input.ReadInt(true)).Items;
+						for (int ii = 0; ii < nn; ii++)
+							constraintBones[ii] = bones[input.ReadInt(true)];
+						data.slot = slots[input.ReadInt(true)];
+						int flags = input.Read();
+						data.skinRequired = (flags & 1) != 0;
+						data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue((flags >> 1) & 2);
+						data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue((flags >> 2) & 3);
+						data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue((flags >> 4) & 3);
+						if ((flags & 128) != 0) data.offsetRotation = input.ReadFloat();
+						PathConstraintPose setup = data.setup;
+						setup.position = input.ReadFloat();
+						if (data.positionMode == PositionMode.Fixed) setup.position *= scale;
+						setup.spacing = input.ReadFloat();
+						if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) setup.spacing *= scale;
+						setup.mixRotate = input.ReadFloat();
+						setup.mixX = input.ReadFloat();
+						setup.mixY = input.ReadFloat();
+						constraints[i] = data;
+						break;
+					}
+					case CONSTRAINT_PHYSICS: {
+						var data = new PhysicsConstraintData(name);
+						data.bone = bones[input.ReadInt(true)];
+						int flags = input.Read();
+						data.skinRequired = (flags & 1) != 0;
+						if ((flags & 2) != 0) data.x = input.ReadFloat();
+						if ((flags & 4) != 0) data.y = input.ReadFloat();
+						if ((flags & 8) != 0) data.rotate = input.ReadFloat();
+						if ((flags & 16) != 0) data.scaleX = input.ReadFloat();
+						if ((flags & 32) != 0) data.shearX = input.ReadFloat();
+						data.limit = ((flags & 64) != 0 ? input.ReadFloat() : 5000) * scale;
+						data.step = 1f / input.ReadUByte();
+						PhysicsConstraintPose setup = data.setup;
+						setup.inertia = input.ReadFloat();
+						setup.strength = input.ReadFloat();
+						setup.damping = input.ReadFloat();
+						setup.massInverse = (flags & 128) != 0 ? input.ReadFloat() : 1;
+						setup.wind = input.ReadFloat();
+						setup.gravity = input.ReadFloat();
+						flags = input.Read();
+						if ((flags & 1) != 0) data.inertiaGlobal = true;
+						if ((flags & 2) != 0) data.strengthGlobal = true;
+						if ((flags & 4) != 0) data.dampingGlobal = true;
+						if ((flags & 8) != 0) data.massGlobal = true;
+						if ((flags & 16) != 0) data.windGlobal = true;
+						if ((flags & 32) != 0) data.gravityGlobal = true;
+						if ((flags & 64) != 0) data.mixGlobal = true;
+						setup.mix = (flags & 128) != 0 ? input.ReadFloat() : 1;
+						constraints[i] = data;
+						break;
+					}
+					case CONSTRAINT_SLIDER: {
+						var data = new SliderData(name);
+						int flags = input.Read();
+						data.skinRequired = (flags & 1) != 0;
+						data.loop = (flags & 2) != 0;
+						data.additive = (flags & 4) != 0;
+						if ((flags & 8) != 0) data.setup.time = input.ReadFloat();
+						if ((flags & 16) != 0) data.setup.mix = (flags & 32) != 0 ? input.ReadFloat() : 1;
+						if ((flags & 64) != 0) {
+							data.local = (flags & 128) != 0;
+							data.bone = bones[input.ReadInt(true)];
+							float offset = input.ReadFloat();
+							switch (input.ReadUByte()) {
+							case 0: data.property = new FromRotate(); break;
+							case 1: {
+								offset *= scale;
+								data.property = new FromX();
+								break;
+							}
+							case 2: {
+								offset *= scale;
+								data.property = new FromY();
+								break;
+							}
+							case 3: data.property = new FromScaleX(); break;
+							case 4: data.property = new FromScaleY(); break;
+							case 5: data.property = new FromShearY(); break;
+							default: data.property = null; break;
+							};
+							data.property.offset = offset;
+							data.offset = input.ReadFloat();
+							data.scale = input.ReadFloat();
 						}
-						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() * toScale;
-						to.max = input.ReadFloat() * toScale;
-						to.scale = input.ReadFloat() * (toScale / fromScale);
-						tos[t] = to;
+						constraints[i] = data;
+						break;
+					}
 					}
-					froms[ii] = from;
 				}
-				flags = input.Read();
-				if ((flags & 1) != 0) data.offsetRotation = input.ReadFloat();
-				if ((flags & 2) != 0) data.offsetX = input.ReadFloat() * scale;
-				if ((flags & 4) != 0) data.offsetY = input.ReadFloat() * scale;
-				if ((flags & 8) != 0) data.offsetScaleX = input.ReadFloat();
-				if ((flags & 16) != 0) data.offsetScaleY = input.ReadFloat();
-				if ((flags & 32) != 0) data.offsetShearY = input.ReadFloat();
-				flags = input.Read();
-				if ((flags & 1) != 0) data.mixRotate = input.ReadFloat();
-				if ((flags & 2) != 0) data.mixX = input.ReadFloat();
-				if ((flags & 4) != 0) data.mixY = input.ReadFloat();
-				if ((flags & 8) != 0) data.mixScaleX = input.ReadFloat();
-				if ((flags & 16) != 0) data.mixScaleY = input.ReadFloat();
-				if ((flags & 32) != 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++) {
-				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.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);
-				data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue((flags >> 3) & 3);
-				if ((flags & 128) != 0) data.offsetRotation = input.ReadFloat();
-
-				data.position = input.ReadFloat();
-				if (data.positionMode == PositionMode.Fixed) data.position *= scale;
-				data.spacing = input.ReadFloat();
-				if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale;
-				data.mixRotate = input.ReadFloat();
-				data.mixX = input.ReadFloat();
-				data.mixY = input.ReadFloat();
-				o[i] = data;
-			}
+				// Default skin.
+				Skin defaultSkin = ReadSkin(input, skeletonData, true, nonessential);
+				if (defaultSkin != null) {
+					skeletonData.defaultSkin = defaultSkin;
+					skeletonData.skins.Add(defaultSkin);
+				}
 
-			// Physics constraints.
-			o = skeletonData.physicsConstraints.Resize(n = input.ReadInt(true)).Items;
-			for (int i = 0; i < n; i++) {
-				var data = new PhysicsConstraintData(input.ReadString());
-				data.order = input.ReadInt(true);
-				data.bone = bones[input.ReadInt(true)];
-				int flags = input.Read();
-				data.skinRequired = (flags & 1) != 0;
-				if ((flags & 2) != 0) data.x = input.ReadFloat();
-				if ((flags & 4) != 0) data.y = input.ReadFloat();
-				if ((flags & 8) != 0) data.rotate = input.ReadFloat();
-				if ((flags & 16) != 0) data.scaleX = input.ReadFloat();
-				if ((flags & 32) != 0) data.shearX = input.ReadFloat();
-				data.limit = ((flags & 64) != 0 ? input.ReadFloat() : 5000) * scale;
-				data.step = 1f / input.ReadUByte();
-				data.inertia = input.ReadFloat();
-				data.strength = input.ReadFloat();
-				data.damping = input.ReadFloat();
-				data.massInverse = (flags & 128) != 0 ? input.ReadFloat() : 1;
-				data.wind = input.ReadFloat();
-				data.gravity = input.ReadFloat();
-				flags = input.Read();
-				if ((flags & 1) != 0) data.inertiaGlobal = true;
-				if ((flags & 2) != 0) data.strengthGlobal = true;
-				if ((flags & 4) != 0) data.dampingGlobal = true;
-				if ((flags & 8) != 0) data.massGlobal = true;
-				if ((flags & 16) != 0) data.windGlobal = true;
-				if ((flags & 32) != 0) data.gravityGlobal = true;
-				if ((flags & 64) != 0) data.mixGlobal = true;
-				data.mix = (flags & 128) != 0 ? input.ReadFloat() : 1;
-				o[i] = data;
-			}
+				// Skins.
+				{
+					int i = skeletonData.skins.Count;
+					o = skeletonData.skins.Resize(n = i + input.ReadInt(true)).Items;
+					for (; i < n; i++)
+						o[i] = ReadSkin(input, skeletonData, false, nonessential);
+				}
 
-			// Default skin.
-			Skin defaultSkin = ReadSkin(input, skeletonData, true, nonessential);
-			if (defaultSkin != null) {
-				skeletonData.defaultSkin = defaultSkin;
-				skeletonData.skins.Add(defaultSkin);
-			}
+				// Linked meshes.
+				n = linkedMeshes.Count;
+				for (int i = 0; i < n; i++) {
+					LinkedMesh linkedMesh = linkedMeshes[i];
+					Skin skin = skeletonData.skins.Items[linkedMesh.skinIndex];
+					Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent);
+					if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent);
+					linkedMesh.mesh.TimelineAttachment = linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh;
+					linkedMesh.mesh.ParentMesh = (MeshAttachment)parent;
+					if (linkedMesh.mesh.Sequence == null) linkedMesh.mesh.UpdateRegion();
+				}
+				linkedMeshes.Clear();
+
+				// Events.
+				o = skeletonData.events.Resize(n = input.ReadInt(true)).Items;
+				for (int i = 0; i < n; i++) {
+					var data = new EventData(input.ReadString());
+					data.Int = input.ReadInt(false);
+					data.Float = input.ReadFloat();
+					data.String = input.ReadString();
+					data.AudioPath = input.ReadString();
+					if (data.AudioPath != null) {
+						data.Volume = input.ReadFloat();
+						data.Balance = input.ReadFloat();
+					}
+					o[i] = data;
+				}
 
-			// Skins.
-			{
-				int i = skeletonData.skins.Count;
-				o = skeletonData.skins.Resize(n = i + input.ReadInt(true)).Items;
-				for (; i < n; i++)
-					o[i] = ReadSkin(input, skeletonData, false, nonessential);
-			}
+				// Animations.
+				Animation[] animations = skeletonData.animations.Resize(n = input.ReadInt(true)).Items;
+				for (int i = 0; i < n; i++)
+					animations[i] = ReadAnimation(input, input.ReadString(), skeletonData);
 
-			// Linked meshes.
-			n = linkedMeshes.Count;
-			for (int i = 0; i < n; i++) {
-				LinkedMesh linkedMesh = linkedMeshes[i];
-				Skin skin = skeletonData.skins.Items[linkedMesh.skinIndex];
-				Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent);
-				if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent);
-				linkedMesh.mesh.TimelineAttachment = linkedMesh.inheritTimelines ? (VertexAttachment)parent : linkedMesh.mesh;
-				linkedMesh.mesh.ParentMesh = (MeshAttachment)parent;
-				if (linkedMesh.mesh.Sequence == null) linkedMesh.mesh.UpdateRegion();
-			}
-			linkedMeshes.Clear();
-
-			// Events.
-			o = skeletonData.events.Resize(n = input.ReadInt(true)).Items;
-			for (int i = 0; i < n; i++) {
-				var data = new EventData(input.ReadString());
-				data.Int = input.ReadInt(false);
-				data.Float = input.ReadFloat();
-				data.String = input.ReadString();
-				data.AudioPath = input.ReadString();
-				if (data.AudioPath != null) {
-					data.Volume = input.ReadFloat();
-					data.Balance = input.ReadFloat();
+				for (int i = 0; i < constraintCount; i++) {
+					SliderData data = constraints[i] as SliderData;
+					if (data != null) data.animation = animations[input.ReadInt(true)];
 				}
-				o[i] = data;
-			}
-
-			// Animations.
-			o = skeletonData.animations.Resize(n = input.ReadInt(true)).Items;
-			for (int i = 0; i < n; i++)
-				o[i] = ReadAnimation(input.ReadString(), input, skeletonData);
+			} catch (IOException ex) {
+				if (version != null) throw new SerializationException("Error reading binary skeleton data, version: " + version, ex);
+				throw new SerializationException("Error binary skeleton data.", ex);
+			} // note: no need to close Input as in reference implementation.
 
 			return skeletonData;
 		}
@@ -461,24 +512,15 @@ namespace Spine {
 
 				if (nonessential) input.ReadInt(); // discard, Color.rgba8888ToColor(skin.color, input.readInt());
 
-				Object[] bones = skin.bones.Resize(input.ReadInt(true)).Items;
-				BoneData[] bonesItems = skeletonData.bones.Items;
-				for (int i = 0, n = skin.bones.Count; i < n; i++)
-					bones[i] = bonesItems[input.ReadInt(true)];
-
-				IkConstraintData[] ikConstraintsItems = skeletonData.ikConstraints.Items;
-				for (int i = 0, n = input.ReadInt(true); i < n; i++)
-					skin.constraints.Add(ikConstraintsItems[input.ReadInt(true)]);
-				TransformConstraintData[] transformConstraintsItems = skeletonData.transformConstraints.Items;
-				for (int i = 0, n = input.ReadInt(true); i < n; i++)
-					skin.constraints.Add(transformConstraintsItems[input.ReadInt(true)]);
-				PathConstraintData[] pathConstraintsItems = skeletonData.pathConstraints.Items;
-				for (int i = 0, n = input.ReadInt(true); i < n; i++)
-					skin.constraints.Add(pathConstraintsItems[input.ReadInt(true)]);
-				PhysicsConstraintData[] physicsConstraintsItems = skeletonData.physicsConstraints.Items;
-				for (int i = 0, n = input.ReadInt(true); i < n; i++)
-					skin.constraints.Add(physicsConstraintsItems[input.ReadInt(true)]);
-				skin.constraints.TrimExcess();
+				int n;
+				object[] from = skeletonData.bones.Items, to = skin.bones.Resize(n = input.ReadInt(true)).Items;
+				for (int i = 0; i < n; i++)
+					to[i] = from[input.ReadInt(true)];
+
+				from = skeletonData.constraints.Items;
+				to = skin.constraints.Resize(n = input.ReadInt(true)).Items;
+				for (int i = 0; i < n; i++)
+					to[i] = from[input.ReadInt(true)];
 
 				slotCount = input.ReadInt(true);
 			}
@@ -524,10 +566,7 @@ namespace Spine {
 				region.rotation = rotation;
 				region.width = width * scale;
 				region.height = height * scale;
-				region.r = ((color & 0xff000000) >> 24) / 255f;
-				region.g = ((color & 0x00ff0000) >> 16) / 255f;
-				region.b = ((color & 0x0000ff00) >> 8) / 255f;
-				region.a = ((color & 0x000000ff)) / 255f;
+				region.SetColor(color.RGBA8888ToColor());
 				region.sequence = sequence;
 				if (sequence == null) region.UpdateRegion();
 				return region;
@@ -564,10 +603,7 @@ namespace Spine {
 				MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path, sequence);
 				if (mesh == null) return null;
 				mesh.Path = path;
-				mesh.r = ((color & 0xff000000) >> 24) / 255f;
-				mesh.g = ((color & 0x00ff0000) >> 16) / 255f;
-				mesh.b = ((color & 0x0000ff00) >> 8) / 255f;
-				mesh.a = ((color & 0x000000ff)) / 255f;
+				mesh.SetColor(color.RGBA8888ToColor());
 				mesh.bones = vertices.bones;
 				mesh.vertices = vertices.vertices;
 				mesh.WorldVerticesLength = vertices.length;
@@ -584,7 +620,7 @@ namespace Spine {
 				return mesh;
 			}
 			case AttachmentType.Linkedmesh: {
-				String path = (flags & 16) != 0 ? input.ReadStringRef() : name;
+				string path = (flags & 16) != 0 ? input.ReadStringRef() : name;
 				uint color = (flags & 32) != 0 ? (uint)input.ReadInt() : 0xffffffff;
 				Sequence sequence = (flags & 64) != 0 ? ReadSequence(input) : null;
 				bool inheritTimelines = (flags & 128) != 0;
@@ -599,10 +635,7 @@ namespace Spine {
 				MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path, sequence);
 				if (mesh == null) return null;
 				mesh.Path = path;
-				mesh.r = ((color & 0xff000000) >> 24) / 255f;
-				mesh.g = ((color & 0x00ff0000) >> 16) / 255f;
-				mesh.b = ((color & 0x0000ff00) >> 8) / 255f;
-				mesh.a = ((color & 0x000000ff)) / 255f;
+				mesh.SetColor(color.RGBA8888ToColor());
 				mesh.Sequence = sequence;
 				if (nonessential) {
 					mesh.Width = width * scale;
@@ -618,7 +651,7 @@ namespace Spine {
 				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;
+				if (nonessential) input.ReadInt(); // discard, int color = nonessential ? input.ReadInt() : 0;
 
 				PathAttachment path = attachmentLoader.NewPathAttachment(skin, name);
 				if (path == null) return null;
@@ -635,7 +668,7 @@ namespace Spine {
 				float rotation = input.ReadFloat();
 				float x = input.ReadFloat();
 				float y = input.ReadFloat();
-				if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0;
+				if (nonessential) input.ReadInt(); // discard, int color = nonessential ? input.ReadInt() : 0;
 
 				PointAttachment point = attachmentLoader.NewPointAttachment(skin, name);
 				if (point == null) return null;
@@ -648,7 +681,7 @@ namespace Spine {
 			case AttachmentType.Clipping: {
 				int endSlotIndex = input.ReadInt(true);
 				Vertices vertices = ReadVertices(input, (flags & 16) != 0);
-				if (nonessential) input.ReadInt();
+				if (nonessential) input.ReadInt(); // discard, int color = nonessential ? input.readInt() : 0;
 
 				ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name);
 				if (clip == null) return null;
@@ -719,7 +752,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) {
+		private Animation ReadAnimation (SkeletonInput input, string name, SkeletonData skeletonData) {
 			var timelines = new ExposedList<Timeline>(input.ReadInt(true));
 			float scale = this.scale;
 
@@ -1007,7 +1040,7 @@ namespace Spine {
 			// Path constraint timelines.
 			for (int i = 0, n = input.ReadInt(true); i < n; i++) {
 				int index = input.ReadInt(true);
-				PathConstraintData data = skeletonData.pathConstraints.Items[index];
+				var data = (PathConstraintData)skeletonData.constraints.Items[index];
 				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) {
@@ -1094,20 +1127,35 @@ namespace Spine {
 				}
 			}
 
+			// Slider timelines.
+			for (int i = 0, n = input.ReadInt(true); i < n; i++) {
+				int index = input.ReadInt(true);
+				for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) {
+					int type = input.ReadSByte(), frameCount = input.ReadInt(true), bezierCount = input.ReadInt(true);
+					ConstraintTimeline1 newTimeline;
+					switch (type) {
+					case SLIDER_TIME: newTimeline = new SliderTimeline(frameCount, bezierCount, index); break;
+					case SLIDER_MIX: newTimeline = new SliderMixTimeline(frameCount, bezierCount, index); break;
+					default: throw new SerializationException();
+					}
+					ReadTimeline(input, timelines, newTimeline, 1);
+				}
+			}
+
 			// Attachment timelines.
 			for (int i = 0, n = input.ReadInt(true); i < n; i++) {
 				Skin skin = skeletonData.skins.Items[input.ReadInt(true)];
 				for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) {
 					int slotIndex = input.ReadInt(true);
 					for (int iii = 0, nnn = input.ReadInt(true); iii < nnn; iii++) {
-						String attachmentName = input.ReadStringRef();
+						string attachmentName = input.ReadStringRef();
 						Attachment attachment = skin.GetAttachment(slotIndex, attachmentName);
 						if (attachment == null) throw new SerializationException("Timeline attachment not found: " + attachmentName);
 
 						int timelineType = input.ReadUByte(), frameCount = input.ReadInt(true), frameLast = frameCount - 1;
 						switch (timelineType) {
 						case ATTACHMENT_DEFORM: {
-							VertexAttachment vertexAttachment = (VertexAttachment)attachment;
+							var vertexAttachment = (VertexAttachment)attachment;
 							bool weighted = vertexAttachment.Bones != null;
 							float[] vertices = vertexAttachment.Vertices;
 							int deformLength = weighted ? (vertices.Length / 3) << 1 : vertices.Length;
@@ -1212,7 +1260,7 @@ namespace Spine {
 					e.floatValue = input.ReadFloat();
 					e.stringValue = input.ReadString();
 					if (e.stringValue == null) e.stringValue = eventData.String;
-					if (e.Data.AudioPath != null) {
+					if (e.data.AudioPath != null) {
 						e.volume = input.ReadFloat();
 						e.balance = input.ReadFloat();
 					}
@@ -1250,7 +1298,7 @@ namespace Spine {
 		}
 
 		/// <exception cref="IOException">Throws IOException when a read operation fails.</exception>
-		private void ReadTimeline (SkeletonInput input, ExposedList<Timeline> timelines, CurveTimeline2 timeline, float scale) {
+		private void ReadTimeline (SkeletonInput input, ExposedList<Timeline> timelines, BoneTimeline2 timeline, float scale) {
 			float time = input.ReadFloat(), value1 = input.ReadFloat() * scale, value2 = input.ReadFloat() * scale;
 			for (int frame = 0, bezier = 0, frameLast = timeline.FrameCount - 1; ; frame++) {
 				timeline.SetFrame(frame, time, value1, value2);

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

@@ -75,7 +75,7 @@ namespace Spine {
 			for (int i = 0; i < slotCount; i++) {
 				Slot slot = slots[i];
 				if (!slot.bone.active) continue;
-				BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment;
+				BoundingBoxAttachment boundingBox = slot.applied.attachment as BoundingBoxAttachment;
 				if (boundingBox == null) continue;
 				boundingBoxes.Add(boundingBox);
 
@@ -91,7 +91,7 @@ namespace Spine {
 				int count = boundingBox.worldVerticesLength;
 				polygon.Count = count;
 				if (polygon.Vertices.Length < count) polygon.Vertices = new float[count];
-				boundingBox.ComputeWorldVertices(slot, polygon.Vertices);
+				boundingBox.ComputeWorldVertices(skeleton, slot, polygon.Vertices);
 			}
 
 			if (updateAabb) {

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

@@ -48,13 +48,13 @@ namespace Spine {
 
 		public bool IsClipping { get { return clipAttachment != null; } }
 
-		public int ClipStart (Slot slot, ClippingAttachment clip) {
+		public int ClipStart (Skeleton skeleton, Slot slot, ClippingAttachment clip) {
 			if (clipAttachment != null) return 0;
 			clipAttachment = clip;
 
 			int n = clip.worldVerticesLength;
 			float[] vertices = clippingPolygon.Resize(n).Items;
-			clip.ComputeWorldVertices(slot, 0, n, vertices, 0, 2);
+			clip.ComputeWorldVertices(skeleton, slot, 0, n, vertices, 0, 2);
 			MakeClockwise(clippingPolygon);
 			clippingPolygons = triangulator.Decompose(clippingPolygon, triangulator.Triangulate(clippingPolygon));
 			foreach (ExposedList<float> polygon in clippingPolygons) {

+ 9 - 73
spine-csharp/src/SkeletonData.cs

@@ -40,10 +40,7 @@ namespace Spine {
 		internal Skin defaultSkin;
 		internal ExposedList<EventData> events = new ExposedList<EventData>();
 		internal ExposedList<Animation> animations = new ExposedList<Animation>();
-		internal ExposedList<IkConstraintData> ikConstraints = new ExposedList<IkConstraintData>();
-		internal ExposedList<TransformConstraintData> transformConstraints = new ExposedList<TransformConstraintData>();
-		internal ExposedList<PathConstraintData> pathConstraints = new ExposedList<PathConstraintData>();
-		internal ExposedList<PhysicsConstraintData> physicsConstraints = new ExposedList<PhysicsConstraintData>();
+		internal ExposedList<IConstraintData> constraints = new ExposedList<IConstraintData>();
 		internal float x, y, width, height, referenceScale = 100;
 		internal string version, hash;
 
@@ -75,14 +72,9 @@ namespace Spine {
 		public ExposedList<EventData> Events { get { return events; } set { events = value; } }
 		/// <summary>The skeleton's animations.</summary>
 		public ExposedList<Animation> Animations { get { return animations; } set { animations = value; } }
-		/// <summary>The skeleton's IK constraints.</summary>
-		public ExposedList<IkConstraintData> IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } }
+		/// <summary>The skeleton's constraints.</summary>
+		public ExposedList<IConstraintData> Constraints { get { return constraints; } }
 		/// <summary>The skeleton's transform constraints.</summary>
-		public ExposedList<TransformConstraintData> TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } }
-		/// <summary>The skeleton's path constraints.</summary>
-		public ExposedList<PathConstraintData> PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } }
-		/// <summary>The skeleton's physics constraints.</summary>
-		public ExposedList<PhysicsConstraintData> PhysicsConstraints { get { return physicsConstraints; } set { physicsConstraints = value; } }
 
 		public float X { get { return x; } set { x = value; } }
 		public float Y { get { return y; } set { y = value; } }
@@ -110,7 +102,6 @@ namespace Spine {
 		public float Fps { get { return fps; } set { fps = value; } }
 
 		// --- Bones
-
 		/// <summary>
 		/// Finds a bone by comparing each bone's name.
 		/// It is more efficient to cache the results of this method than to call it multiple times.</summary>
@@ -126,7 +117,6 @@ namespace Spine {
 		}
 
 		// --- Slots
-
 		/// <returns>May be null.</returns>
 		public SlotData FindSlot (string slotName) {
 			if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
@@ -139,7 +129,6 @@ namespace Spine {
 		}
 
 		// --- Skins
-
 		/// <returns>May be null.</returns>
 		public Skin FindSkin (string skinName) {
 			if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null.");
@@ -149,7 +138,6 @@ namespace Spine {
 		}
 
 		// --- Events
-
 		/// <returns>May be null.</returns>
 		public EventData FindEvent (string eventDataName) {
 			if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null.");
@@ -159,7 +147,6 @@ namespace Spine {
 		}
 
 		// --- Animations
-
 		/// <returns>May be null.</returns>
 		public Animation FindAnimation (string animationName) {
 			if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null.");
@@ -171,68 +158,17 @@ namespace Spine {
 			return null;
 		}
 
-		// --- IK constraints
-
-		/// <returns>May be null.</returns>
-		public IkConstraintData FindIkConstraint (string constraintName) {
+		// --- Constraints
+		public T FindConstraint<T> (String constraintName) where T : class, IConstraintData {
 			if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
-			IkConstraintData[] ikConstraints = this.ikConstraints.Items;
-			for (int i = 0, n = this.ikConstraints.Count; i < n; i++) {
-				IkConstraintData ikConstraint = ikConstraints[i];
-				if (ikConstraint.name == constraintName) return ikConstraint;
+			IConstraintData[] constraints = this.constraints.Items;
+			for (int i = 0, n = this.constraints.Count; i < n; i++) {
+				T constraint = constraints[i] as T;
+				if (constraint != null && constraint.Name.Equals(constraintName)) return constraint;
 			}
 			return null;
 		}
 
-		// --- Transform constraints
-
-		/// <returns>May be null.</returns>
-		public TransformConstraintData FindTransformConstraint (string constraintName) {
-			if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
-			TransformConstraintData[] transformConstraints = this.transformConstraints.Items;
-			for (int i = 0, n = this.transformConstraints.Count; i < n; i++) {
-				TransformConstraintData transformConstraint = transformConstraints[i];
-				if (transformConstraint.name == constraintName) return transformConstraint;
-			}
-			return null;
-		}
-
-		// --- Path constraints
-
-		/// <summary>
-		/// Finds a path constraint by comparing each path constraint's name. It is more efficient to cache the results of this method
-		/// than to call it multiple times.
-		/// </summary>
-		/// <returns>May be null.</returns>
-		public PathConstraintData FindPathConstraint (string constraintName) {
-			if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
-			PathConstraintData[] pathConstraints = this.pathConstraints.Items;
-			for (int i = 0, n = this.pathConstraints.Count; i < n; i++) {
-				PathConstraintData constraint = pathConstraints[i];
-				if (constraint.name.Equals(constraintName)) return constraint;
-			}
-			return null;
-		}
-
-		// --- Physics constraints
-
-		/// <summary>
-		/// Finds a physics constraint by comparing each physics constraint's name. It is more efficient to cache the results of this
-		/// method than to call it multiple times.
-		/// </summary>
-		/// <returns>May be null.</returns>
-		public PhysicsConstraintData FindPhysicsConstraint (String constraintName) {
-			if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
-			PhysicsConstraintData[] physicsConstraints = this.physicsConstraints.Items;
-			for (int i = 0, n = this.physicsConstraints.Count; i < n; i++) {
-				PhysicsConstraintData constraint = (PhysicsConstraintData)physicsConstraints[i];
-				if (constraint.name.Equals(constraintName)) return constraint;
-			}
-			return null;
-		}
-
-		// ---
-
 		override public string ToString () {
 			return name ?? base.ToString();
 		}

Datei-Diff unterdrückt, da er zu groß ist
+ 443 - 411
spine-csharp/src/SkeletonJson.cs


+ 6 - 7
spine-csharp/src/Skin.cs

@@ -42,13 +42,13 @@ namespace Spine {
 		// Reason is that there is no efficient way to replace or access an already added element, losing any benefits.
 		private Dictionary<SkinKey, SkinEntry> attachments = new Dictionary<SkinKey, SkinEntry>(SkinKeyComparer.Instance);
 		internal readonly ExposedList<BoneData> bones = new ExposedList<BoneData>();
-		internal readonly ExposedList<ConstraintData> constraints = new ExposedList<ConstraintData>();
+		internal readonly ExposedList<IConstraintData> constraints = new ExposedList<IConstraintData>();
 
 		public string Name { get { return name; } }
 		/// <summary>Returns all attachments contained in this skin.</summary>
 		public ICollection<SkinEntry> Attachments { get { return attachments.Values; } }
 		public ExposedList<BoneData> Bones { get { return bones; } }
-		public ExposedList<ConstraintData> Constraints { get { return constraints; } }
+		public ExposedList<IConstraintData> Constraints { get { return constraints; } }
 
 		public Skin (string name) {
 			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
@@ -67,7 +67,7 @@ namespace Spine {
 			foreach (BoneData data in skin.bones)
 				if (!bones.Contains(data)) bones.Add(data);
 
-			foreach (ConstraintData data in skin.constraints)
+			foreach (IConstraintData data in skin.constraints)
 				if (!constraints.Contains(data)) constraints.Add(data);
 
 			foreach (KeyValuePair<SkinKey, SkinEntry> item in skin.attachments) {
@@ -81,7 +81,7 @@ namespace Spine {
 			foreach (BoneData data in skin.bones)
 				if (!bones.Contains(data)) bones.Add(data);
 
-			foreach (ConstraintData data in skin.constraints)
+			foreach (IConstraintData data in skin.constraints)
 				if (!constraints.Contains(data)) constraints.Add(data);
 
 			foreach (KeyValuePair<SkinKey, SkinEntry> item in skin.attachments) {
@@ -134,10 +134,9 @@ namespace Spine {
 			Slot[] slots = skeleton.slots.Items;
 			foreach (KeyValuePair<SkinKey, SkinEntry> item in oldSkin.attachments) {
 				SkinEntry entry = item.Value;
-				int slotIndex = entry.slotIndex;
-				Slot slot = slots[slotIndex];
+				SlotPose slot = slots[entry.slotIndex].pose;
 				if (slot.Attachment == entry.attachment) {
-					Attachment attachment = GetAttachment(slotIndex, entry.name);
+					Attachment attachment = GetAttachment(entry.slotIndex, entry.name);
 					if (attachment != null) slot.Attachment = attachment;
 				}
 			}

+ 115 - 0
spine-csharp/src/Slider.cs

@@ -0,0 +1,115 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated April 5, 2025. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2025, Esoteric Software LLC
+ *
+ * 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
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+	/// <summary>
+	/// Stores the current pose for a slider. 
+	/// </summary>
+	public class Slider : Constraint<Slider, SliderData, SliderPose> {
+		static private readonly float[] offsets = new float[6];
+		internal Bone bone;
+
+		public Slider (SliderData data, Skeleton skeleton)
+			: base(data, new SliderPose(), new SliderPose()) {
+			if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
+
+			if (data.bone != null) bone = skeleton.bones.Items[data.bone.index];
+		}
+
+		override public IConstraint Copy (Skeleton skeleton) {
+			var copy = new Slider(data, skeleton);
+			copy.pose.Set(pose);
+			return copy;
+		}
+
+		override public void Update (Skeleton skeleton, Physics physics) {
+			SliderPose p = applied;
+			if (p.mix == 0) return;
+
+			Animation animation = data.animation;
+			if (bone != null) {
+				if (!bone.active) return;
+				if (data.local) bone.applied.ValidateLocalTransform(skeleton);
+				p.time = data.offset
+					+ (data.property.Value(skeleton, bone.applied, data.local, offsets) - data.property.offset) * data.scale;
+				if (data.loop)
+					p.time = animation.duration + (p.time % animation.duration);
+				else
+					p.time = Math.Max(0, p.time);
+			}
+
+			Bone[] bones = skeleton.bones.Items;
+			int[] indices = animation.bones.Items;
+			for (int i = 0, n = animation.bones.Count; i < n; i++)
+				bones[indices[i]].applied.ModifyLocal(skeleton);
+
+			animation.Apply(skeleton, p.time, p.time, data.loop, null, p.mix, data.additive ? MixBlend.Add : MixBlend.Replace,
+				MixDirection.In, true);
+		}
+
+		override public void Sort (Skeleton skeleton) {
+			if (bone != null && !data.local) skeleton.SortBone(bone);
+			skeleton.updateCache.Add(this);
+
+			Timeline[] timelines = data.animation.timelines.Items;
+			Bone[] bones = skeleton.bones.Items;
+			Slot[] slots = skeleton.slots.Items;
+			IConstraint[] constraints = skeleton.constraints.Items;
+			PhysicsConstraint[] physics = skeleton.physics.Items;
+			int physicsCount = skeleton.physics.Count;
+			for (int i = 0, n = data.animation.timelines.Count; i < n; i++) {
+				Timeline t = timelines[i];
+				IBoneTimeline boneTimeline = t as IBoneTimeline;
+				if (boneTimeline != null) {
+					Bone bone = bones[boneTimeline.BoneIndex];
+					bone.sorted = false;
+					skeleton.SortReset(bone.children);
+					skeleton.Constrained(bone);
+				} else if (t as ISlotTimeline != null) {
+					ISlotTimeline timeline = (ISlotTimeline)t;
+					skeleton.Constrained(slots[timeline.SlotIndex]);
+				} else if (t as PhysicsConstraintTimeline != null) {
+					PhysicsConstraintTimeline timeline = (PhysicsConstraintTimeline)t;
+					if (timeline.constraintIndex == -1) {
+						for (int ii = 0; ii < physicsCount; ii++)
+							skeleton.Constrained(physics[ii]);
+					} else
+						skeleton.Constrained(constraints[timeline.constraintIndex]);
+				} else if (t as IConstraintTimeline != null) {
+					IConstraintTimeline timeline = (IConstraintTimeline)t;
+					skeleton.Constrained(constraints[timeline.ConstraintIndex]);
+				}
+			}
+		}
+
+		public Bone Bone { get { return bone; } set { bone = value; } }
+	}
+}

+ 2 - 0
spine-csharp/src/Slider.cs.meta

@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 5bdcb9aaed0e86b459d0d79ca58603da

+ 65 - 0
spine-csharp/src/SliderData.cs

@@ -0,0 +1,65 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated April 5, 2025. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2025, Esoteric Software LLC
+ *
+ * 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
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+	using FromProperty = TransformConstraintData.FromProperty;
+
+	/// <summary>
+	/// Stores the setup pose for a <see cref="Slider"/>.
+	/// </summary>
+	public class SliderData : ConstraintData<Slider, SliderPose> {
+		internal Animation animation;
+		internal bool additive, loop;
+		internal BoneData bone;
+		internal FromProperty property;
+		internal float offset, scale;
+		internal bool local;
+
+		public SliderData (string name)
+			: base(name, new SliderPose()) {
+		}
+
+		override public IConstraint Create (Skeleton skeleton) {
+			return new Slider(this, skeleton);
+		}
+
+		public Animation Animation { get { return animation; } set { animation = value; } }
+		public bool Additive { get { return additive; } set { additive = value; } }
+		public bool Loop { get { return loop; } set { loop = value; } }
+		/// <summary>May be null.</summary>
+		public BoneData Bone { get { return bone; } set { bone = value; } }
+		/// <summary>May be null.</summary>
+		public FromProperty Property { get { return property; } set { property = value; } }
+		public float Offset { get { return offset; } set { offset = value; } }
+		public float Scale { get { return scale; } set { scale = value; } }
+		public bool Local { get { return local; } set { local = value; } }
+	}
+}

+ 2 - 0
spine-csharp/src/SliderData.cs.meta

@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: cb99a53a43158cd42b8c85a74aa29333

+ 45 - 0
spine-csharp/src/SliderPose.cs

@@ -0,0 +1,45 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated April 5, 2025. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2025, Esoteric Software LLC
+ *
+ * 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
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+namespace Spine {
+	/// <summary>
+	/// Stores a pose for a slider.
+	/// </summary>
+	public class SliderPose : IPose<SliderPose> {
+		internal float time, mix;
+
+		public void Set (SliderPose pose) {
+			time = pose.time;
+			mix = pose.mix;
+		}
+
+		public float Time { get { return time; } set { time = value; } }
+		public float Mix { get { return mix; } set { mix = value; } }
+	}
+}

+ 2 - 0
spine-csharp/src/SliderPose.cs.meta

@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 404ec38f814cc31479b3957079ac6233

+ 34 - 145
spine-csharp/src/Slot.cs

@@ -27,178 +27,67 @@
  * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
 
+#if UNITY_5_3_OR_NEWER
+#define IS_UNITY
+#endif
+
 using System;
 
 namespace Spine {
+#if IS_UNITY
+	using Color = UnityEngine.Color;
+#endif
 
 	/// <summary>
 	/// Stores a slot's current pose. Slots organize attachments for <see cref="Skeleton.DrawOrder"/> purposes and provide a place to store
 	/// state for an attachment.State cannot be stored in an attachment itself because attachments are stateless and may be shared
 	/// across multiple skeletons.
 	/// </summary>
-	public class Slot {
-		internal SlotData data;
-		internal Bone bone;
-		internal float r, g, b, a;
-		internal float r2, g2, b2;
-		internal bool hasSecondColor;
-		internal Attachment attachment;
-		internal int sequenceIndex;
-		internal ExposedList<float> deform = new ExposedList<float>();
+	public class Slot : Posed<SlotData, SlotPose, SlotPose> {
+		internal readonly Skeleton skeleton;
+		internal readonly Bone bone;
 		internal int attachmentState;
 
-		public Slot (SlotData data, Bone bone) {
-			if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
-			if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null.");
-			this.data = data;
-			this.bone = bone;
-
-			// darkColor = data.darkColor == null ? null : new Color();
-			if (data.hasSecondColor) {
-				r2 = g2 = b2 = 0;
+		public Slot (SlotData data, Skeleton skeleton)
+			: base(data, new SlotPose(), new SlotPose()) {
+			if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
+			this.skeleton = skeleton;
+			bone = skeleton.bones.Items[data.boneData.index];
+			if (data.setup.GetDarkColor().HasValue) {
+				pose.SetDarkColor(new Color());
+				constrained.SetDarkColor(new Color());
 			}
-
-			SetToSetupPose();
+			SetupPose();
 		}
 
 		/// <summary>Copy constructor.</summary>
-		public Slot (Slot slot, Bone bone) {
-			if (slot == null) throw new ArgumentNullException("slot", "slot cannot be null.");
+		public Slot (Slot slot, Bone bone, Skeleton skeleton)
+			: base(slot.data, new SlotPose(), new SlotPose()) {
 			if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null.");
-			data = slot.data;
+			if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
 			this.bone = bone;
-			r = slot.r;
-			g = slot.g;
-			b = slot.b;
-			a = slot.a;
-
-			// darkColor = slot.darkColor == null ? null : new Color(slot.darkColor);
-			if (slot.hasSecondColor) {
-				r2 = slot.r2;
-				g2 = slot.g2;
-				b2 = slot.b2;
-			} else {
-				r2 = g2 = b2 = 0;
+			this.skeleton = skeleton;
+			if (data.setup.GetDarkColor().HasValue) {
+				pose.SetDarkColor(new Color());
+				constrained.SetDarkColor(new Color());
 			}
-			hasSecondColor = slot.hasSecondColor;
-
-			attachment = slot.attachment;
-			sequenceIndex = slot.sequenceIndex;
-			deform.AddRange(slot.deform);
+			pose.Set(slot.pose);
 		}
 
-		/// <summary>The slot's setup pose data.</summary>
-		public SlotData Data { get { return data; } }
 		/// <summary>The bone this slot belongs to.</summary>
 		public Bone Bone { get { return bone; } }
-		/// <summary>The skeleton this slot belongs to.</summary>
-		public Skeleton Skeleton { get { return bone.skeleton; } }
-		/// <summary>The color used to tint the slot's attachment. If <see cref="HasSecondColor"/> is set, this is used as the light color for two
-		/// color tinting.</summary>
-		public float R { get { return r; } set { r = value; } }
-		/// <summary>The color used to tint the slot's attachment. If <see cref="HasSecondColor"/> is set, this is used as the light color for two
-		/// color tinting.</summary>
-		public float G { get { return g; } set { g = value; } }
-		/// <summary>The color used to tint the slot's attachment. If <see cref="HasSecondColor"/> is set, this is used as the light color for two
-		/// color tinting.</summary>
-		public float B { get { return b; } set { b = value; } }
-		/// <summary>The color used to tint the slot's attachment. If <see cref="HasSecondColor"/> is set, this is used as the light color for two
-		/// color tinting.</summary>
-		public float A { get { return a; } set { a = value; } }
-
-		public void ClampColor () {
-			r = MathUtils.Clamp(r, 0, 1);
-			g = MathUtils.Clamp(g, 0, 1);
-			b = MathUtils.Clamp(b, 0, 1);
-			a = MathUtils.Clamp(a, 0, 1);
-		}
-
-		/// <summary>The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used.</summary>
-		/// <seealso cref="HasSecondColor"/>
-		public float R2 { get { return r2; } set { r2 = value; } }
-		/// <summary>The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used.</summary>
-		/// <seealso cref="HasSecondColor"/>
-		public float G2 { get { return g2; } set { g2 = value; } }
-		/// <summary>The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used.</summary>
-		/// <seealso cref="HasSecondColor"/>
-		public float B2 { get { return b2; } set { b2 = value; } }
-		/// <summary>Whether R2 G2 B2 are used to tint the slot's attachment for two color tinting. False if two color tinting is not used.</summary>
-		public bool HasSecondColor { get { return data.hasSecondColor; } set { data.hasSecondColor = value; } }
-
-		public void ClampSecondColor () {
-			r2 = MathUtils.Clamp(r2, 0, 1);
-			g2 = MathUtils.Clamp(g2, 0, 1);
-			b2 = MathUtils.Clamp(b2, 0, 1);
-		}
-
-		/// <summary>
-		/// The current attachment for the slot, or null if the slot has no attachment.
-		/// If the attachment is changed, resets <see cref="SequenceIndex"/> and clears the <see cref="Deform"/>.
-		/// The deform is not cleared if the old attachment has the same <see cref="VertexAttachment.TimelineAttachment"/> as the
-		/// specified attachment.</summary>
-		public Attachment Attachment {
-			/// <summary>The current attachment for the slot, or null if the slot has no attachment.</summary>
-			get { return attachment; }
-			/// <summary>
-			/// Sets the slot's attachment and, if the attachment changed, resets <see cref="SequenceIndex"/> and clears the <see cref="Deform"/>.
-			/// The deform is not cleared if the old attachment has the same <see cref="VertexAttachment.TimelineAttachment"/> as the
-			/// specified attachment.</summary>
-			/// <param name="value">May be null.</param>
-			set {
-				if (attachment == value) return;
-				if (!(value is VertexAttachment) || !(this.attachment is VertexAttachment)
-					|| ((VertexAttachment)value).TimelineAttachment != ((VertexAttachment)this.attachment).TimelineAttachment) {
-					deform.Clear();
-				}
-				this.attachment = value;
-				sequenceIndex = -1;
-			}
-		}
-
-		/// <summary>
-		/// The index of the texture region to display when the slot's attachment has a <see cref="Sequence"/>. -1 represents the
-		/// <see cref="Sequence.SetupIndex"/>.
-		/// </summary>
-		public int SequenceIndex { get { return sequenceIndex; } set { sequenceIndex = value; } }
-
-		/// <summary> Vertices to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a
-		/// weighted mesh, the entries are an offset for each vertex which will be added to the mesh's local vertex positions.
-		/// <para />
-		/// See <see cref="VertexAttachment.ComputeWorldVertices(Slot, int, int, float[], int, int)"/> and <see cref="DeformTimeline"/>.</summary>
-		public ExposedList<float> Deform {
-			get {
-				return deform;
-			}
-			set {
-				if (deform == null) throw new ArgumentNullException("deform", "deform cannot be null.");
-				deform = value;
-			}
-		}
 
 		/// <summary>Sets this slot to the setup pose.</summary>
-		public void SetToSetupPose () {
-			r = data.r;
-			g = data.g;
-			b = data.b;
-			a = data.a;
-
-			// if (darkColor != null) darkColor.set(data.darkColor);
-			if (HasSecondColor) {
-				r2 = data.r2;
-				g2 = data.g2;
-				b2 = data.b2;
-			}
-
+		override public void SetupPose () {
+			pose.SetColor(data.setup.GetColor());
+			if (pose.GetDarkColor().HasValue) pose.SetDarkColor(data.setup.GetDarkColor());
+			pose.sequenceIndex = data.setup.sequenceIndex;
 			if (data.attachmentName == null)
-				Attachment = null;
+				pose.Attachment = null;
 			else {
-				attachment = null;
-				Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName);
+				pose.attachment = null;
+				pose.Attachment = skeleton.GetAttachment(data.index, data.attachmentName);
 			}
 		}
-
-		override public string ToString () {
-			return data.name;
-		}
 	}
 }

+ 19 - 30
spine-csharp/src/SlotData.cs

@@ -27,54 +27,43 @@
  * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
 
+#if UNITY_5_3_OR_NEWER
+#define IS_UNITY
+#endif
+
 using System;
 
 namespace Spine {
-	public class SlotData {
+#if IS_UNITY
+	using Color = UnityEngine.Color;
+#endif
+
+	public class SlotData : PosedData<SlotPose> {
 		internal int index;
-		internal string name;
 		internal BoneData boneData;
-		internal float r = 1, g = 1, b = 1, a = 1;
-		internal float r2 = 0, g2 = 0, b2 = 0;
-		internal bool hasSecondColor = false;
 		internal string attachmentName;
 		internal BlendMode blendMode;
-
+		
 		// Nonessential.
 		// bool visible = true;
 
+		public SlotData (int index, String name, BoneData boneData)
+			: base(name, new SlotPose()) {
+			if (index < 0) throw new ArgumentException("index must be >= 0.", "index");
+			if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null.");
+			this.index = index;
+			this.boneData = boneData;
+		}
+
 		/// <summary>The index of the slot in <see cref="Skeleton.Slots"/>.</summary>
 		public int Index { get { return index; } }
-		/// <summary>The name of the slot, which is unique across all slots in the skeleton.</summary>
-		public string Name { get { return name; } }
+		
 		/// <summary>The bone this slot belongs to.</summary>
 		public BoneData BoneData { get { return boneData; } }
-		public float R { get { return r; } set { r = value; } }
-		public float G { get { return g; } set { g = value; } }
-		public float B { get { return b; } set { b = value; } }
-		public float A { get { return a; } set { a = value; } }
-
-		public float R2 { get { return r2; } set { r2 = value; } }
-		public float G2 { get { return g2; } set { g2 = value; } }
-		public float B2 { get { return b2; } set { b2 = value; } }
-		public bool HasSecondColor { get { return hasSecondColor; } set { hasSecondColor = value; } }
 
 		/// <summary>The name of the attachment that is visible for this slot in the setup pose, or null if no attachment is visible.</summary>
 		public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } }
 		/// <summary>The blend mode for drawing the slot's attachment.</summary>
 		public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } }
-
-		public SlotData (int index, String name, BoneData boneData) {
-			if (index < 0) throw new ArgumentException("index must be >= 0.", "index");
-			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
-			if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null.");
-			this.index = index;
-			this.name = name;
-			this.boneData = boneData;
-		}
-
-		override public string ToString () {
-			return name;
-		}
 	}
 }

+ 143 - 0
spine-csharp/src/SlotPose.cs

@@ -0,0 +1,143 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated April 5, 2025. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2025, Esoteric Software LLC
+ *
+ * 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
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+#if UNITY_5_3_OR_NEWER
+#define IS_UNITY
+#endif
+
+using System;
+
+namespace Spine {
+#if IS_UNITY
+	using Color = UnityEngine.Color;
+#endif
+
+	/// <summary>
+	/// Stores a slot's pose. Slots organize attachments for <see cref="Skeleton.DrawOrder"/> purposes and provide a place to store state
+	/// for an attachment. State cannot be stored in an attachment itself because attachments are stateless and may be shared across
+	/// multiple skeletons.
+	/// </summary>
+	public class SlotPose : IPose<SlotPose> {
+		// Color is a struct, thus set to protected to prevent
+		// Color color = slot.color; color.a = 0.5; modifying just a copy of the struct instead of the original
+		// object as in reference implementation.
+		protected Color color = new Color(1, 1, 1, 1);
+		protected Color? darkColor = null;
+		internal Attachment attachment; // Not used in setup pose.
+		internal int sequenceIndex;
+		internal readonly ExposedList<float> deform = new ExposedList<float>();
+
+		internal SlotPose () {
+		}
+
+		public void Set (SlotPose pose) {
+			if (pose == null) throw new ArgumentNullException("pose", "pose cannot be null.");
+			color = pose.color;
+			if (darkColor.HasValue) darkColor = pose.darkColor;
+			attachment = pose.attachment;
+			sequenceIndex = pose.sequenceIndex;
+			deform.Clear(false);
+			deform.AddRange(pose.deform);
+		}
+
+		/// <returns>A copy of the color used to tint the slot's attachment. If <see cref="DarkColor"/> is set, this is used as the light color for two
+		/// color tinting.</returns>
+		public Color GetColor () {
+			return color;
+		}
+
+		/// <summary>Sets the color used to tint the slot's attachment. If <see cref="DarkColor"/> is set, this is used as the light color for two
+		/// color tinting.</summary>
+		public void SetColor (Color color) {
+			this.color = color;
+		}
+
+		/// <summary>Clamps the <see cref="GetColor()">color</see> used to tint the slot's attachment to the 0-1 range.</summary>
+		public void ClampColor () {
+			color.Clamp();
+		}
+
+		/// <returns>A copy of the dark color used to tint the slot's attachment for two color tinting, or null if two color tinting is not used. The dark
+		/// color's alpha is not used.</returns>
+		public Color? GetDarkColor () {
+			return darkColor;
+		}
+
+		/// <summary>Sets the dark color used to tint the slot's attachment for two color tinting, or null if two color tinting is not used. The dark
+		/// color's alpha is not used.</summary>
+		public void SetDarkColor (Color? darkColor) {
+			this.darkColor = darkColor;
+		}
+
+		/// <summary>Clamps the <see cref="GetDarkColor()">dark color</see> used to tint the slot's attachment to the 0-1 range.</summary>
+		public void ClampDarkColor () {
+			if (darkColor.HasValue) darkColor = darkColor.Value.Clamp();
+		}
+
+		/// <summary>
+		/// The current attachment for the slot, or null if the slot has no attachment.
+		/// If the attachment is changed, resets <see cref="SequenceIndex"/> and clears the <see cref="Deform"/>.
+		/// The deform is not cleared if the old attachment has the same <see cref="VertexAttachment.TimelineAttachment"/> as the
+		/// specified attachment.</summary>
+		public Attachment Attachment {
+			/// <summary>The current attachment for the slot, or null if the slot has no attachment.</summary>
+			get { return attachment; }
+			/// <summary>
+			/// Sets the slot's attachment and, if the attachment changed, resets <see cref="SequenceIndex"/> and clears the <see cref="Deform"/>.
+			/// The deform is not cleared if the old attachment has the same <see cref="VertexAttachment.TimelineAttachment"/> as the
+			/// specified attachment.</summary>
+			/// <param name="value">May be null.</param>
+			set {
+				if (attachment == value) return;
+				if (!(value is VertexAttachment) || !(this.attachment is VertexAttachment)
+					|| ((VertexAttachment)value).TimelineAttachment != ((VertexAttachment)this.attachment).TimelineAttachment) {
+					deform.Clear();
+				}
+				this.attachment = value;
+				sequenceIndex = -1;
+			}
+		}
+
+		/// <summary>
+		/// The index of the texture region to display when the slot's attachment has a <see cref="Sequence"/>. -1 represents the
+		/// <see cref="Sequence.SetupIndex"/>.
+		/// </summary>
+		public int SequenceIndex { get { return sequenceIndex; } set { sequenceIndex = value; } }
+
+		/// <summary> Vertices to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a
+		/// weighted mesh, the entries are an offset for each vertex which will be added to the mesh's local vertex positions.
+		/// <para />
+		/// See <see cref="VertexAttachment.ComputeWorldVertices(Slot, int, int, float[], int, int)"/> and <see cref="DeformTimeline"/>.</summary>
+		public ExposedList<float> Deform {
+			get {
+				return deform;
+			}
+		}
+	}
+}

+ 2 - 0
spine-csharp/src/SlotPose.cs.meta

@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: e46a84746dc30a649a4488cec8aac930

+ 51 - 74
spine-csharp/src/TransformConstraint.cs

@@ -31,7 +31,6 @@ using System;
 
 namespace Spine {
 	using FromProperty = TransformConstraintData.FromProperty;
-	using Physics = Skeleton.Physics;
 	using ToProperty = TransformConstraintData.ToProperty;
 
 	/// <summary>
@@ -41,73 +40,53 @@ namespace Spine {
 	/// <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;
+	public class TransformConstraint : Constraint<TransformConstraint, TransformConstraintData, TransformConstraintPose> {
+		internal readonly ExposedList<BonePose> bones;
 		internal Bone source;
-		internal float mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY;
-
-		internal bool active;
-
-		public TransformConstraint (TransformConstraintData data, Skeleton skeleton) {
-			if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
+		
+		public TransformConstraint (TransformConstraintData data, Skeleton skeleton)
+			: base(data, new TransformConstraintPose(), new TransformConstraintPose()) {
 			if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
-			this.data = data;
 
-			bones = new ExposedList<Bone>();
+			bones = new ExposedList<BonePose>(data.bones.Count);
 			foreach (BoneData boneData in data.bones)
-				bones.Add(skeleton.bones.Items[boneData.index]);
+				bones.Add(skeleton.bones.Items[boneData.index].constrained);
 
 			source = skeleton.bones.Items[data.source.index];
-
-			mixRotate = data.mixRotate;
-			mixX = data.mixX;
-			mixY = data.mixY;
-			mixScaleX = data.mixScaleX;
-			mixScaleY = data.mixScaleY;
-			mixShearY = data.mixShearY;
 		}
 
-		/// <summary>Copy constructor.</summary>
-		public TransformConstraint (TransformConstraint constraint, Skeleton skeleton)
-			: this(constraint.data, skeleton) {
-
-			mixRotate = constraint.mixRotate;
-			mixX = constraint.mixX;
-			mixY = constraint.mixY;
-			mixScaleX = constraint.mixScaleX;
-			mixScaleY = constraint.mixScaleY;
-			mixShearY = constraint.mixShearY;
+		override public IConstraint Copy (Skeleton skeleton) {
+			var copy = new TransformConstraint(data, skeleton);
+			copy.pose.Set(pose);
+			return copy;
 		}
 
-		public void SetToSetupPose () {
-			TransformConstraintData data = this.data;
-			mixRotate = data.mixRotate;
-			mixX = data.mixX;
-			mixY = data.mixY;
-			mixScaleX = data.mixScaleX;
-			mixScaleY = data.mixScaleY;
-			mixShearY = data.mixShearY;
-		}
-
-		public void Update (Physics physics) {
-			if (mixRotate == 0 && mixX == 0 && mixY == 0 && mixScaleX == 0 && mixScaleY == 0 && mixShearY == 0) return;
+		/// <summary>Applies the constraint to the constrained bones.</summary>
+		override public void Update (Skeleton skeleton, Physics physics) {
+			TransformConstraintPose p = applied;
+			if (p.mixRotate == 0 && p.mixX == 0 && p.mixY == 0 && p.mixScaleX == 0 && p.mixScaleY == 0 && p.mixShearY == 0) return;
 
 			TransformConstraintData data = this.data;
-			bool localFrom = data.localSource, localTarget = data.localTarget, additive = data.additive, clamp = data.clamp;
-			Bone source = this.source;
+			bool localSource = data.localSource, localTarget = data.localTarget, additive = data.additive, clamp = data.clamp;
+			float[] offsets = data.offsets;
+			BonePose source = this.source.applied;
+			if (localSource) source.ValidateLocalTransform(skeleton);
 			FromProperty[] fromItems = data.properties.Items;
-			int fn = data.properties.Count;
-			Bone[] bones = this.bones.Items;
+			int fn = data.properties.Count, update = skeleton.update;
+			BonePose[] bones = this.bones.Items;
 			for (int i = 0, n = this.bones.Count; i < n; i++) {
-				var bone = bones[i];
+				BonePose bone = bones[i];
+				if (localTarget)
+					bone.ModifyLocal(skeleton);
+				else
+					bone.ModifyWorld(update);
 				for (int f = 0; f < fn; f++) {
 					FromProperty from = fromItems[f];
-					float value = from.Value(data, source, localFrom) - from.offset;
+					float value = from.Value(skeleton, source, localSource, offsets) - 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) {
+						if (to.Mix(p) != 0) {
 							float clamped = to.offset + value * to.scale;
 							if (clamp) {
 								if (to.offset < to.max)
@@ -115,39 +94,37 @@ namespace Spine {
 								else
 									clamped = MathUtils.Clamp(clamped, to.max, to.offset);
 							}
-							to.Apply(this, bone, clamped, localTarget, additive);
+							to.Apply(p, bone, clamped, localTarget, additive);
 						}
 					}
 				}
-				if (localTarget)
-					bone.Update(Skeleton.Physics.None); // note: reference implementation passes null, ignored parameter
-				else
-					bone.UpdateAppliedTransform();
 			}
 		}
 
+		override public void Sort (Skeleton skeleton) {
+			if (!data.localSource) skeleton.SortBone(source);
+			BonePose[] bones = this.bones.Items;
+			int boneCount = this.bones.Count;
+			bool worldTarget = !data.localTarget;
+			if (worldTarget) {
+				for (int i = 0; i < boneCount; i++)
+					skeleton.SortBone(bones[i].bone);
+			}
+			skeleton.updateCache.Add(this);
+			for (int i = 0; i < boneCount; i++) {
+				Bone bone = bones[i].bone;
+				skeleton.SortReset(bone.children);
+				skeleton.Constrained(bone);
+			}
+			for (int i = 0; i < boneCount; i++)
+				bones[i].bone.sorted = worldTarget;
+		}
+
+		override public bool IsSourceActive { get { return source.active; } }
+
 		/// <summary>The bones that will be modified by this transform constraint.</summary>
-		public ExposedList<Bone> Bones { get { return bones; } }
+		public ExposedList<BonePose> Bones { get { return bones; } }
 		/// <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>
-		public float MixX { get { return mixX; } set { mixX = value; } }
-		/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y.</summary>
-		public float MixY { get { return mixY; } set { mixY = value; } }
-		/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained scale X.</summary>
-		public float MixScaleX { get { return mixScaleX; } set { mixScaleX = value; } }
-		/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained scale Y.</summary>
-		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 bool Active { get { return active; } }
-		/// <summary>The transform constraint's setup pose data.</summary>
-		public TransformConstraintData Data { get { return data; } }
-
-		override public string ToString () {
-			return data.name;
-		}
 	}
 }

+ 87 - 87
spine-csharp/src/TransformConstraintData.cs

@@ -30,17 +30,24 @@
 using System;
 
 namespace Spine {
-	public class TransformConstraintData : ConstraintData {
+	public class TransformConstraintData : ConstraintData<TransformConstraint, TransformConstraintPose> {
+		public const int ROTATION = 0, X = 1, Y = 2, SCALEX = 3, SCALEY = 4, SHEARY = 5;
+
 		internal readonly ExposedList<BoneData> bones = new ExposedList<BoneData>();
 		internal BoneData source;
-		internal float mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY;
-		internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY;
+		internal float[] offsets = new float[6];
 		internal bool localSource, localTarget, additive, clamp;
 		internal readonly ExposedList<FromProperty> properties = new ExposedList<FromProperty>();
 
-		public TransformConstraintData (string name) : base(name) {
+		public TransformConstraintData (string name)
+			: base(name, new TransformConstraintPose()) {
+		}
+
+		override public IConstraint Create (Skeleton skeleton) {
+			return new TransformConstraint(this, skeleton);
 		}
 
+		/// <summary>The bones that will be modified by this transform constraint.</summary>
 		public ExposedList<BoneData> Bones { get { return bones; } }
 
 		/// <summary>The bone whose world transform will be copied to the constrained bones.</summary>
@@ -52,35 +59,18 @@ namespace Spine {
 			}
 		}
 
-		/// <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>
-		public float MixX { get { return mixX; } set { mixX = value; } }
-		/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y.</summary>
-		public float MixY { get { return mixY; } set { mixY = value; } }
-		/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained scale X.</summary>
-		public float MixScaleX { get { return mixScaleX; } set { mixScaleX = value; } }
-		/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained scale Y.</summary>
-		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; } }
 		/// <summary>An offset added to the constrained bone rotation.</summary>
-		public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } }
+		public float OffsetRotation { get { return offsets[ROTATION]; } set { offsets[ROTATION] = value; } }
 		/// <summary>An offset added to the constrained bone X translation.</summary>
-		public float OffsetX { get { return offsetX; } set { offsetX = value; } }
+		public float OffsetX { get { return offsets[X]; } set { offsets[X] = value; } }
 		/// <summary>An offset added to the constrained bone Y translation.</summary>
-		public float OffsetY { get { return offsetY; } set { offsetY = value; } }
+		public float OffsetY { get { return offsets[Y]; } set { offsets[Y] = value; } }
 		/// <summary>An offset added to the constrained bone scaleX.</summary>
-		public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } }
+		public float OffsetScaleX { get { return offsets[SCALEX]; } set { offsets[SCALEX] = value; } }
 		/// <summary>An offset added to the constrained bone scaleY.</summary>
-		public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } }
+		public float OffsetScaleY { get { return offsets[SCALEY]; } set { offsets[SCALEY] = value; } }
 		/// <summary>An offset added to the constrained bone shearY.</summary>
-		public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } }
+		public float OffsetShearY { get { return offsets[SHEARY]; } set { offsets[SHEARY] = value; } }
 
 		/// <summary>Reads the source bone's local transform instead of its world transform.</summary>
 		public bool LocalSource { get { return localSource; } set { localSource = value; } }
@@ -92,16 +82,21 @@ namespace Spine {
 		/// <see cref="ToProperty.max"/>.</summary>
 		public bool Clamp { get { return clamp; } set { clamp = value; } }
 
+		/// <summary>The mapping of transform properties to other transform properties.</summary>
+		public ExposedList<FromProperty> Properties {
+			get { return properties; }
+		}
+
 		/// <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;
 
 			/// <summary>Constrained properties.</summary>
-			public readonly ExposedList<ToProperty> to = new ExposedList<ToProperty>();
+			public readonly ExposedList<ToProperty> to = new ExposedList<ToProperty>(1);
 
 			/// <summary>Reads this property from the specified bone.</summary>
-			abstract public float Value (TransformConstraintData data, Bone source, bool local);
+			abstract public float Value (Skeleton skeleton, BonePose source, bool local, float[] offsets);
 		}
 
 		///<summary>Constrained property for a <see cref="TransformConstraint"/>.</summary>
@@ -115,32 +110,32 @@ namespace Spine {
 			/// <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>Reads the mix for this property from the specified pose.</summary>
+			public abstract float Mix (TransformConstraintPose pose);
 
 			/// <summary>Applies the value to this property.</summary>
-			public abstract void Apply (TransformConstraint constraint, Bone bone, float value, bool local, bool additive);
+			public abstract void Apply (TransformConstraintPose pose, BonePose bone, float value, bool local, bool additive);
 		}
 
 		public class FromRotate : FromProperty {
-			public override float Value (TransformConstraintData data, Bone source, bool local) {
-				if (local) return source.arotation + data.offsetRotation;
-				float value = MathUtils.Atan2(source.c, source.a) * MathUtils.RadDeg
-					+ (source.a * source.d - source.b * source.c > 0 ? data.offsetRotation : -data.offsetRotation);
+			public override float Value (Skeleton skeleton, BonePose source, bool local, float[] offsets) {
+				if (local) return source.rotation + offsets[ROTATION];
+				float value = MathUtils.Atan2(source.c / skeleton.ScaleY, source.a / skeleton.scaleX) * MathUtils.RadDeg
+					+ (source.a * source.d - source.b * source.c > 0 ? offsets[ROTATION] : -offsets[ROTATION]);
 				if (value < 0) value += 360;
 				return value;
 			}
 		}
 
 		public class ToRotate : ToProperty {
-			public override float Mix (TransformConstraint constraint) {
-				return constraint.mixRotate;
+			public override float Mix (TransformConstraintPose pose) {
+				return pose.mixRotate;
 			}
 
-			public override void Apply (TransformConstraint constraint, Bone bone, float value, bool local, bool additive) {
+			public override void Apply (TransformConstraintPose pose, BonePose bone, float value, bool local, bool additive) {
 				if (local) {
-					if (!additive) value -= bone.arotation;
-					bone.arotation += value * constraint.mixRotate;
+					if (!additive) value -= bone.rotation;
+					bone.rotation += value * pose.mixRotate;
 				} else {
 					float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
 					value *= MathUtils.DegRad;
@@ -149,7 +144,7 @@ namespace Spine {
 						value -= MathUtils.PI2;
 					else if (value < -MathUtils.PI) //
 						value += MathUtils.PI2;
-					value *= constraint.mixRotate;
+					value *= pose.mixRotate;
 					float cos = MathUtils.Cos(value), sin = MathUtils.Sin(value);
 					bone.a = cos * a - sin * c;
 					bone.b = cos * b - sin * d;
@@ -160,73 +155,75 @@ namespace Spine {
 		}
 
 		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 override float Value (Skeleton skeleton, BonePose source, bool local, float[] offsets) {
+				return local ? source.x + offsets[X] : (offsets[X] * source.a + offsets[Y] * source.b + source.worldX) / skeleton.scaleX;
 			}
 		}
 
 		public class ToX : ToProperty {
-			public override float Mix (TransformConstraint constraint) {
-				return constraint.mixX;
+			public override float Mix (TransformConstraintPose pose) {
+				return pose.mixX;
 			}
 
-			public override void Apply (TransformConstraint constraint, Bone bone, float value, bool local, bool additive) {
+			public override void Apply (TransformConstraintPose pose, BonePose bone, float value, bool local, bool additive) {
 				if (local) {
-					if (!additive) value -= bone.ax;
-					bone.ax += value * constraint.mixX;
+					if (!additive) value -= bone.x;
+					bone.x += value * pose.mixX;
 				} else {
 					if (!additive) value -= bone.worldX;
-					bone.worldX += value * constraint.mixX;
+					bone.worldX += value * pose.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 override float Value (Skeleton skeleton, BonePose source, bool local, float[] offsets) {
+				return local ? source.y + offsets[Y] : (offsets[X] * source.c + offsets[Y] * source.d + source.worldY) / skeleton.ScaleY;
 			}
 		}
 
 		public class ToY : ToProperty {
-			public override float Mix (TransformConstraint constraint) {
-				return constraint.mixY;
+			public override float Mix (TransformConstraintPose pose) {
+				return pose.mixY;
 			}
 
-			public override void Apply (TransformConstraint constraint, Bone bone, float value, bool local, bool additive) {
+			public override void Apply (TransformConstraintPose pose, BonePose bone, float value, bool local, bool additive) {
 				if (local) {
-					if (!additive) value -= bone.ay;
-					bone.ay += value * constraint.mixY;
+					if (!additive) value -= bone.y;
+					bone.y += value * pose.mixY;
 				} else {
 					if (!additive) value -= bone.worldY;
-					bone.worldY += value * constraint.mixY;
+					bone.worldY += value * pose.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)) + data.offsetScaleX;
+			public override float Value (Skeleton skeleton, BonePose source, bool local, float[] offsets) {
+				if (local) return source.scaleX + offsets[SCALEX];
+				float a = source.a / skeleton.scaleX, c = source.c / skeleton.ScaleY;
+				return (float)Math.Sqrt(a * a + c * c) + offsets[SCALEX];
 			}
 		}
 
 		public class ToScaleX : ToProperty {
-			public override float Mix (TransformConstraint constraint) {
-				return constraint.mixScaleX;
+			public override float Mix (TransformConstraintPose pose) {
+				return pose.mixScaleX;
 			}
 
-			public override void Apply (TransformConstraint constraint, Bone bone, float value, bool local, bool additive) {
+			public override void Apply (TransformConstraintPose pose, BonePose 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;
+						bone.scaleX *= 1 + ((value - 1) * pose.mixScaleX);
+					else if (bone.scaleX != 0) //
+						bone.scaleX = 1 + (value / bone.scaleX - 1) * pose.mixScaleX;
 				} else {
 					float s;
 					if (additive)
-						s = 1 + (value - 1) * constraint.mixScaleX;
+						s = 1 + (value - 1) * pose.mixScaleX;
 					else {
 						s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c);
-						if (s != 0) s = 1 + (value / s - 1) * constraint.mixScaleX;
+						if (s != 0) s = 1 + (value / s - 1) * pose.mixScaleX;
 					}
 					bone.a *= s;
 					bone.c *= s;
@@ -235,29 +232,31 @@ namespace Spine {
 		}
 
 		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)) + data.offsetScaleY;
+			public override float Value (Skeleton skeleton, BonePose source, bool local, float[] offsets) {
+				if (local) return source.scaleY + offsets[SCALEY];
+				float b = source.b / skeleton.scaleX, d = source.d / skeleton.ScaleY;
+				return (float)Math.Sqrt(b * b + d * d) + offsets[SCALEY];
 			}
 		}
 
 		public class ToScaleY : ToProperty {
-			public override float Mix (TransformConstraint constraint) {
-				return constraint.mixScaleY;
+			public override float Mix (TransformConstraintPose pose) {
+				return pose.mixScaleY;
 			}
 
-			public override void Apply (TransformConstraint constraint, Bone bone, float value, bool local, bool additive) {
+			public override void Apply (TransformConstraintPose pose, BonePose 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;
+						bone.scaleY *= 1 + ((value - 1) * pose.mixScaleY);
+					else if (bone.scaleY != 0) //
+						bone.scaleY = 1 + (value / bone.scaleY - 1) * pose.mixScaleY;
 				} else {
 					float s;
 					if (additive)
-						s = 1 + (value - 1) * constraint.mixScaleY;
+						s = 1 + (value - 1) * pose.mixScaleY;
 					else {
 						s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d);
-						if (s != 0) s = 1 + (value / s - 1) * constraint.mixScaleY;
+						if (s != 0) s = 1 + (value / s - 1) * pose.mixScaleY;
 					}
 					bone.b *= s;
 					bone.d *= s;
@@ -266,21 +265,22 @@ namespace Spine {
 		}
 
 		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)
-					+ data.offsetShearY;
+			public override float Value (Skeleton skeleton, BonePose source, bool local, float[] offsets) {
+				if (local) return source.shearY + offsets[SHEARY];
+				float sx = 1 / skeleton.scaleX, sy = 1 / skeleton.ScaleY;
+				return (MathUtils.Atan2(source.d * sy, source.b * sx) - MathUtils.Atan2(source.c * sy, source.a * sx)) * MathUtils.RadDeg - 90 + offsets[SHEARY];
 			}
 		}
 
 		public class ToShearY : ToProperty {
-			public override float Mix (TransformConstraint constraint) {
-				return constraint.mixShearY;
+			public override float Mix (TransformConstraintPose pose) {
+				return pose.mixShearY;
 			}
 
-			public override void Apply (TransformConstraint constraint, Bone bone, float value, bool local, bool additive) {
+			public override void Apply (TransformConstraintPose pose, BonePose bone, float value, bool local, bool additive) {
 				if (local) {
-					if (!additive) value -= bone.ashearY;
-					bone.ashearY += value * constraint.mixShearY;
+					if (!additive) value -= bone.shearY;
+					bone.shearY += value * pose.mixShearY;
 				} else {
 					float b = bone.b, d = bone.d, by = MathUtils.Atan2(d, b);
 					value = (value + 90) * MathUtils.DegRad;
@@ -293,7 +293,7 @@ namespace Spine {
 						else if (value < -MathUtils.PI) //
 							value += MathUtils.PI2;
 					}
-					value = by + value * constraint.mixShearY;
+					value = by + value * pose.mixShearY;
 					float s = (float)Math.Sqrt(b * b + d * d);
 					bone.b = MathUtils.Cos(value) * s;
 					bone.d = MathUtils.Sin(value) * s;

+ 64 - 0
spine-csharp/src/TransformConstraintPose.cs

@@ -0,0 +1,64 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated April 5, 2025. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2025, Esoteric Software LLC
+ *
+ * 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
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+	using FromProperty = TransformConstraintData.FromProperty;
+	using ToProperty = TransformConstraintData.ToProperty;
+
+	/// <summary>
+	/// Stores a pose for a transform constraint.
+	/// </summary>
+	public class TransformConstraintPose : IPose<TransformConstraintPose> {
+		internal float mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY;
+
+		public void Set (TransformConstraintPose pose) {
+			mixRotate = pose.mixRotate;
+			mixX = pose.mixX;
+			mixY = pose.mixY;
+			mixScaleX = pose.mixScaleX;
+			mixScaleY = pose.mixScaleY;
+			mixShearY = pose.mixShearY;
+		}
+
+		/// <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>
+		public float MixX { get { return mixX; } set { mixX = value; } }
+		/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained translation Y.</summary>
+		public float MixY { get { return mixY; } set { mixY = value; } }
+		/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained scale X.</summary>
+		public float MixScaleX { get { return mixScaleX; } set { mixScaleX = value; } }
+		/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained scale Y.</summary>
+		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; } }
+	}
+}

+ 2 - 0
spine-csharp/src/TransformConstraintPose.cs.meta

@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 8f731f92402be95469edd617a53bd865

+ 1 - 1
spine-unity/Assets/Spine Examples/Scripts/Getting Started Scripts/BasicPlatformerController.cs

@@ -115,7 +115,7 @@ namespace Spine.Unity.Examples {
 			}
 
 			// Dummy physics and controller using UnityEngine.CharacterController.
-			Vector3 gravityDeltaVelocity = Physics.gravity * gravityScale * dt;
+			Vector3 gravityDeltaVelocity = UnityEngine.Physics.gravity * gravityScale * dt;
 
 			if (doJump) {
 				velocity.y = jumpSpeed;

+ 2 - 2
spine-unity/Assets/Spine Examples/Scripts/Goblins.cs

@@ -48,12 +48,12 @@ namespace Spine.Unity.Examples {
 
 		// This is called after the animation is applied to the skeleton and can be used to adjust the bones dynamically.
 		public void UpdateLocal (ISkeletonAnimation skeletonRenderer) {
-			headBone.Rotation += extraRotation;
+			headBone.Pose.Rotation += extraRotation;
 		}
 
 		public void OnMouseDown () {
 			skeletonAnimation.Skeleton.SetSkin(girlSkin ? "goblin" : "goblingirl");
-			skeletonAnimation.Skeleton.SetSlotsToSetupPose();
+			skeletonAnimation.Skeleton.SetupPoseSlots();
 
 			girlSkin = !girlSkin;
 

+ 27 - 17
spine-unity/Assets/Spine Examples/Scripts/MecanimAnimationMatchModifier/AnimationMatchModifierAsset.cs

@@ -167,14 +167,19 @@ namespace Spine.Unity.Examples {
 			static RGBATimeline GetFillerTimeline (RGBATimeline timeline, SkeletonData skeletonData) {
 				RGBATimeline t = new RGBATimeline(1, 0, timeline.SlotIndex);
 				SlotData slotData = skeletonData.Slots.Items[t.SlotIndex];
-				t.SetFrame(0, 0, slotData.R, slotData.G, slotData.B, slotData.A);
+				Color color = slotData.GetSetupPose().GetColor();
+				t.SetFrame(0, 0, color.r, color.g, color.b, color.a);
 				return t;
 			}
 
 			static RGBA2Timeline GetFillerTimeline (RGBA2Timeline timeline, SkeletonData skeletonData) {
 				RGBA2Timeline t = new RGBA2Timeline(1, 0, timeline.SlotIndex);
 				SlotData slotData = skeletonData.Slots.Items[t.SlotIndex];
-				t.SetFrame(0, 0, slotData.R, slotData.G, slotData.B, slotData.A, slotData.R2, slotData.G2, slotData.B2);
+				var setup = slotData.GetSetupPose();
+				Color color = setup.GetColor();
+				Color? darkColorOptional = setup.GetDarkColor();
+				Color darkColor = darkColorOptional.HasValue ? darkColorOptional.Value : Color.black;
+				t.SetFrame(0, 0, color.r, color.g, color.b, color.a, darkColor.r, darkColor.g, darkColor.b);
 				return t;
 			}
 
@@ -196,37 +201,42 @@ namespace Spine.Unity.Examples {
 			}
 
 			static IkConstraintTimeline GetFillerTimeline (IkConstraintTimeline timeline, SkeletonData skeletonData) {
-				IkConstraintTimeline t = new IkConstraintTimeline(1, 0, timeline.IkConstraintIndex);
-				IkConstraintData ikConstraintData = skeletonData.IkConstraints.Items[timeline.IkConstraintIndex];
-				t.SetFrame(0, 0, ikConstraintData.Mix, ikConstraintData.Softness, ikConstraintData.BendDirection, ikConstraintData.Compress, ikConstraintData.Stretch);
+				IkConstraintTimeline t = new IkConstraintTimeline(1, 0, timeline.ConstraintIndex);
+				var ikConstraintData = (IkConstraintData)skeletonData.Constraints.Items[timeline.ConstraintIndex];
+				var setup = ikConstraintData.GetSetupPose();
+				t.SetFrame(0, 0, setup.Mix, setup.Softness, setup.BendDirection, setup.Compress, setup.Stretch);
 				return t;
 			}
 
 			static TransformConstraintTimeline GetFillerTimeline (TransformConstraintTimeline timeline, SkeletonData skeletonData) {
-				TransformConstraintTimeline t = new TransformConstraintTimeline(1, 0, timeline.TransformConstraintIndex);
-				TransformConstraintData data = skeletonData.TransformConstraints.Items[timeline.TransformConstraintIndex];
-				t.SetFrame(0, 0, data.MixRotate, data.MixX, data.MixY, data.MixScaleX, data.MixScaleY, data.MixShearY);
+				TransformConstraintTimeline t = new TransformConstraintTimeline(1, 0, timeline.ConstraintIndex);
+				var data = (TransformConstraintData)skeletonData.Constraints.Items[timeline.ConstraintIndex];
+				var setup = data.GetSetupPose();
+				t.SetFrame(0, 0, setup.MixRotate, setup.MixX, setup.MixY, setup.MixScaleX, setup.MixScaleY, setup.MixShearY);
 				return t;
 			}
 
 			static PathConstraintPositionTimeline GetFillerTimeline (PathConstraintPositionTimeline timeline, SkeletonData skeletonData) {
-				PathConstraintPositionTimeline t = new PathConstraintPositionTimeline(1, 0, timeline.PathConstraintIndex);
-				PathConstraintData data = skeletonData.PathConstraints.Items[timeline.PathConstraintIndex];
-				t.SetFrame(0, 0, data.Position);
+				PathConstraintPositionTimeline t = new PathConstraintPositionTimeline(1, 0, timeline.ConstraintIndex);
+				var data = (PathConstraintData)skeletonData.Constraints.Items[timeline.ConstraintIndex];
+				var setup = data.GetSetupPose();
+				t.SetFrame(0, 0, setup.Position);
 				return t;
 			}
 
 			static PathConstraintSpacingTimeline GetFillerTimeline (PathConstraintSpacingTimeline timeline, SkeletonData skeletonData) {
-				PathConstraintSpacingTimeline t = new PathConstraintSpacingTimeline(1, 0, timeline.PathConstraintIndex);
-				PathConstraintData data = skeletonData.PathConstraints.Items[timeline.PathConstraintIndex];
-				t.SetFrame(0, 0, data.Spacing);
+				PathConstraintSpacingTimeline t = new PathConstraintSpacingTimeline(1, 0, timeline.ConstraintIndex);
+				var data = (PathConstraintData)skeletonData.Constraints.Items[timeline.ConstraintIndex];
+				var setup = data.GetSetupPose();
+				t.SetFrame(0, 0, setup.Spacing);
 				return t;
 			}
 
 			static PathConstraintMixTimeline GetFillerTimeline (PathConstraintMixTimeline timeline, SkeletonData skeletonData) {
-				PathConstraintMixTimeline t = new PathConstraintMixTimeline(1, 0, timeline.PathConstraintIndex);
-				PathConstraintData data = skeletonData.PathConstraints.Items[timeline.PathConstraintIndex];
-				t.SetFrame(0, 0, data.RotateMix, data.MixX, data.MixY);
+				PathConstraintMixTimeline t = new PathConstraintMixTimeline(1, 0, timeline.ConstraintIndex);
+				PathConstraintData data = (PathConstraintData)skeletonData.Constraints.Items[timeline.ConstraintIndex];
+				var setup = data.GetSetupPose();
+				t.SetFrame(0, 0, setup.MixRotate, setup.MixX, setup.MixY);
 				return t;
 			}
 			#endregion

+ 1 - 1
spine-unity/Assets/Spine Examples/Scripts/Mix and Match Character Customize/EquipsVisualsComponentExample.cs

@@ -91,7 +91,7 @@ namespace Spine.Unity.Examples {
 		}
 
 		void RefreshSkeletonAttachments () {
-			skeletonAnimation.Skeleton.SetSlotsToSetupPose();
+			skeletonAnimation.Skeleton.SetupPoseSlots();
 			skeletonAnimation.AnimationState.Apply(skeletonAnimation.Skeleton); //skeletonAnimation.Update(0);
 		}
 

+ 2 - 2
spine-unity/Assets/Spine Examples/Scripts/Mix and Match Character Customize/MixAndMatchSkinsExample.cs

@@ -146,7 +146,7 @@ namespace Spine.Unity.Examples {
 
 			// Use the repacked skin.
 			skeletonAnimation.Skeleton.Skin = repackedSkin;
-			skeletonAnimation.Skeleton.SetSlotsToSetupPose();
+			skeletonAnimation.Skeleton.SetupPoseSlots();
 			skeletonAnimation.AnimationState.Apply(skeletonAnimation.Skeleton);
 
 			// `GetRepackedSkin()` and each call to `GetRemappedClone()` with parameter `premultiplyAlpha` set to `true`
@@ -189,7 +189,7 @@ namespace Spine.Unity.Examples {
 			AddEquipmentSkinsTo(resultCombinedSkin);
 
 			skeleton.SetSkin(resultCombinedSkin);
-			skeleton.SetSlotsToSetupPose();
+			skeleton.SetupPoseSlots();
 		}
 	}
 }

+ 1 - 1
spine-unity/Assets/Spine Examples/Scripts/MixAndMatch.cs

@@ -136,7 +136,7 @@ namespace Spine.Unity.Examples {
 				skeleton.SetSkin(customSkin); // Just use the custom skin directly.
 			}
 
-			skeleton.SetSlotsToSetupPose(); // Use the pose from setup pose.
+			skeleton.SetupPoseSlots(); // Use the pose from setup pose.
 			skeletonAnimation.Update(0); // Use the pose in the currently active animation.
 
 			// `GetRepackedSkin()` and each call to `GetRemappedClone()` with parameter `premultiplyAlpha` set to `true`

+ 2 - 2
spine-unity/Assets/Spine Examples/Scripts/MixAndMatchGraphic.cs

@@ -133,8 +133,8 @@ namespace Spine.Unity.Examples {
 				skeleton.SetSkin(customSkin);
 			}
 
-			//skeleton.SetSlotsToSetupPose();
-			skeleton.SetToSetupPose();
+			//skeleton.SetupPoseSlots();
+			skeleton.SetupPose();
 			skeletonGraphic.Update(0);
 			skeletonGraphic.OverrideTexture = runtimeAtlas;
 

+ 2 - 2
spine-unity/Assets/Spine Examples/Scripts/RuntimeLoadFromExportsExample.cs

@@ -89,7 +89,7 @@ namespace Spine.Unity.Examples {
 			runtimeSkeletonAnimation.Initialize(false);
 			if (skinName != "")
 				runtimeSkeletonAnimation.Skeleton.SetSkin(skinName);
-			runtimeSkeletonAnimation.Skeleton.SetSlotsToSetupPose();
+			runtimeSkeletonAnimation.Skeleton.SetupPoseSlots();
 			if (animationName != "")
 				runtimeSkeletonAnimation.AnimationState.SetAnimation(0, animationName, true);
 		}
@@ -112,7 +112,7 @@ namespace Spine.Unity.Examples {
 			runtimeSkeletonGraphic.Initialize(false);
 			if (skinName != "")
 				runtimeSkeletonGraphic.Skeleton.SetSkin(skinName);
-			runtimeSkeletonGraphic.Skeleton.SetSlotsToSetupPose();
+			runtimeSkeletonGraphic.Skeleton.SetupPoseSlots();
 			if (animationName != "")
 				runtimeSkeletonGraphic.AnimationState.SetAnimation(0, animationName, true);
 		}

+ 5 - 4
spine-unity/Assets/Spine Examples/Scripts/Sample Components/BoneLocalOverride.cs

@@ -55,7 +55,7 @@ namespace Spine.Unity.Examples {
 			if (Application.isPlaying) return;
 			if (spineComponent == null) spineComponent = GetComponent<ISkeletonAnimation>();
 			if (spineComponent.IsNullOrDestroyed()) return;
-			if (bone != null) bone.SetToSetupPose();
+			if (bone != null) bone.SetupPose();
 			OverrideLocal(spineComponent);
 		}
 #endif
@@ -78,13 +78,14 @@ namespace Spine.Unity.Examples {
 				}
 			}
 
+			var bonePose = bone.Pose;
 			if (overridePosition) {
-				bone.X = Mathf.Lerp(bone.X, localPosition.x, alpha);
-				bone.Y = Mathf.Lerp(bone.Y, localPosition.y, alpha);
+				bonePose.X = Mathf.Lerp(bonePose.X, localPosition.x, alpha);
+				bonePose.Y = Mathf.Lerp(bonePose.Y, localPosition.y, alpha);
 			}
 
 			if (overrideRotation)
-				bone.Rotation = Mathf.Lerp(bone.Rotation, rotation, alpha);
+				bonePose.Rotation = Mathf.Lerp(bonePose.Rotation, rotation, alpha);
 		}
 
 	}

+ 1 - 1
spine-unity/Assets/Spine Examples/Scripts/Sample Components/CombinedSkin.cs

@@ -53,7 +53,7 @@ namespace Spine.Unity.Examples {
 			}
 
 			skeleton.SetSkin(combinedSkin);
-			skeleton.SetToSetupPose();
+			skeleton.SetupPose();
 			IAnimationStateComponent animationStateComponent = skeletonComponent as IAnimationStateComponent;
 			if (animationStateComponent != null) animationStateComponent.AnimationState.Apply(skeleton);
 		}

+ 5 - 4
spine-unity/Assets/Spine Examples/Scripts/Sample Components/Legacy/AtlasRegionAttacher.cs

@@ -64,16 +64,17 @@ namespace Spine.Unity.Examples {
 
 			foreach (SlotRegionPair entry in attachments) {
 				Slot slot = skeletonRenderer.Skeleton.FindSlot(entry.slot);
-				Attachment originalAttachment = slot.Attachment;
+				var slotPose = slot.AppliedPose;
+				Attachment originalAttachment = slotPose.Attachment;
 				AtlasRegion region = atlas.FindRegion(entry.region);
 
 				if (region == null) {
-					slot.Attachment = null;
+					slotPose.Attachment = null;
 				} else if (inheritProperties && originalAttachment != null) {
-					slot.Attachment = originalAttachment.GetRemappedClone(region, true, true, scale);
+					slotPose.Attachment = originalAttachment.GetRemappedClone(region, true, true, scale);
 				} else {
 					RegionAttachment newRegionAttachment = region.ToRegionAttachment(region.name, scale);
-					slot.Attachment = newRegionAttachment;
+					slotPose.Attachment = newRegionAttachment;
 				}
 			}
 		}

+ 2 - 2
spine-unity/Assets/Spine Examples/Scripts/Sample Components/Legacy/SpriteAttacher.cs

@@ -142,7 +142,7 @@ namespace Spine.Unity.Examples {
 		/// <summary>Update the slot's attachment to the Attachment generated from the sprite.</summary>
 		public void Attach () {
 			if (spineSlot != null)
-				spineSlot.Attachment = attachment;
+				spineSlot.AppliedPose.Attachment = attachment;
 		}
 
 	}
@@ -162,7 +162,7 @@ namespace Spine.Unity.Examples {
 		[System.Obsolete]
 		public static RegionAttachment AttachUnitySprite (this Skeleton skeleton, string slotName, Sprite sprite, Shader shader, bool applyPMA, float rotation = 0f) {
 			RegionAttachment att = applyPMA ? sprite.ToRegionAttachmentPMAClone(shader, rotation: rotation) : sprite.ToRegionAttachment(new Material(shader), rotation: rotation);
-			skeleton.FindSlot(slotName).Attachment = att;
+			skeleton.FindSlot(slotName).AppliedPose.Attachment = att;
 			return att;
 		}
 

+ 1 - 1
spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonAnimationMulti/SkeletonAnimationMulti.cs

@@ -144,7 +144,7 @@ namespace Spine.Unity {
 
 			if (skeletonAnimation != null) {
 				SetActiveSkeleton(skeletonAnimation);
-				skeletonAnimation.skeleton.SetToSetupPose();
+				skeletonAnimation.skeleton.SetupPose();
 				TrackEntry trackEntry = skeletonAnimation.state.SetAnimation(MainTrackIndex, animation, loop);
 				skeletonAnimation.Update(0);
 				return trackEntry;

+ 1 - 1
spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonColorInitialize.cs

@@ -50,7 +50,7 @@ namespace Spine.Unity.Prototyping {
 		void OnValidate () {
 			ISkeletonComponent skeletonComponent = GetComponent<ISkeletonComponent>();
 			if (skeletonComponent != null) {
-				skeletonComponent.Skeleton.SetSlotsToSetupPose();
+				skeletonComponent.Skeleton.SetupPoseSlots();
 				IAnimationStateComponent animationStateComponent = GetComponent<IAnimationStateComponent>();
 				if (animationStateComponent != null && animationStateComponent.AnimationState != null) {
 					animationStateComponent.AnimationState.Apply(skeletonComponent.Skeleton);

+ 51 - 43
spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/SkeletonRagdoll.cs

@@ -153,8 +153,9 @@ namespace Spine.Unity.Examples {
 						ragdollRoot.localPosition = new Vector3(skeleton.X, skeleton.Y, 0);
 						ragdollRoot.localRotation = (skeleton.ScaleX < 0) ? Quaternion.Euler(0, 0, 180.0f) : Quaternion.identity;
 					} else {
-						ragdollRoot.localPosition = new Vector3(b.Parent.WorldX, b.Parent.WorldY, 0);
-						ragdollRoot.localRotation = Quaternion.Euler(0, 0, b.Parent.WorldRotationX - b.Parent.ShearX);
+						var parentPose = b.Parent.AppliedPose;
+						ragdollRoot.localPosition = new Vector3(parentPose.WorldX, parentPose.WorldY, 0);
+						ragdollRoot.localRotation = Quaternion.Euler(0, 0, parentPose.WorldRotationX - parentPose.ShearX);
 					}
 					parentTransform = ragdollRoot;
 					rootOffset = t.position - transform.position;
@@ -186,7 +187,7 @@ namespace Spine.Unity.Examples {
 			for (int x = 0; x < boneColliders.Count; x++) {
 				for (int y = 0; y < boneColliders.Count; y++) {
 					if (x == y) continue;
-					Physics.IgnoreCollision(boneColliders[x], boneColliders[y]);
+					UnityEngine.Physics.IgnoreCollision(boneColliders[x], boneColliders[y]);
 				}
 			}
 
@@ -213,28 +214,30 @@ namespace Spine.Unity.Examples {
 			}
 
 			// Disable skeleton constraints.
-			if (disableIK) {
-				ExposedList<IkConstraint> ikConstraints = skeleton.IkConstraints;
-				for (int i = 0, n = ikConstraints.Count; i < n; i++)
-					ikConstraints.Items[i].Mix = 0;
-			}
-
-			if (disableOtherConstraints) {
-				ExposedList<TransformConstraint> transformConstraints = skeleton.TransformConstraints;
-				for (int i = 0, n = transformConstraints.Count; i < n; i++) {
-					transformConstraints.Items[i].MixRotate = 0;
-					transformConstraints.Items[i].MixScaleX = 0;
-					transformConstraints.Items[i].MixScaleY = 0;
-					transformConstraints.Items[i].MixShearY = 0;
-					transformConstraints.Items[i].MixX = 0;
-					transformConstraints.Items[i].MixY = 0;
-				}
-
-				ExposedList<PathConstraint> pathConstraints = skeleton.PathConstraints;
-				for (int i = 0, n = pathConstraints.Count; i < n; i++) {
-					pathConstraints.Items[i].MixRotate = 0;
-					pathConstraints.Items[i].MixX = 0;
-					pathConstraints.Items[i].MixY = 0;
+			ExposedList<IConstraint> constraints = skeleton.Constraints;
+			IConstraint[] constraintsItems = constraints.Items;
+			for (int i = 0, n = constraints.Count; i < n; i++) {
+				var constraint = constraintsItems[i];
+				if (constraint is IkConstraint && disableIK) {
+					var ikConstraint = ((IkConstraint)constraint);
+					ikConstraint.Pose.Mix = 0;
+				} else if (disableOtherConstraints) {
+					if (constraint is TransformConstraint) {
+						var transformConstraint = (TransformConstraint)constraint;
+						var constraintPose = transformConstraint.Pose;
+						constraintPose.MixRotate = 0;
+						constraintPose.MixScaleX = 0;
+						constraintPose.MixScaleY = 0;
+						constraintPose.MixShearY = 0;
+						constraintPose.MixX = 0;
+						constraintPose.MixY = 0;
+					} else if (constraint is PathConstraint) {
+						var pathConstraint = (PathConstraint)constraint;
+						var constraintPose = pathConstraint.Pose;
+						constraintPose.MixRotate = 0;
+						constraintPose.MixX = 0;
+						constraintPose.MixY = 0;
+					}
 				}
 			}
 
@@ -250,7 +253,7 @@ namespace Spine.Unity.Examples {
 			float startTime = Time.time;
 			float startMix = mix;
 			while (mix > 0) {
-				skeleton.SetBonesToSetupPose();
+				skeleton.SetupPoseBones();
 				mix = Mathf.SmoothStep(startMix, target, (Time.time - startTime) / duration);
 				yield return null;
 			}
@@ -269,7 +272,7 @@ namespace Spine.Unity.Examples {
 				t.position -= offset;
 
 			UpdateSpineSkeleton(null);
-			skeleton.UpdateWorldTransform(Skeleton.Physics.Update);
+			skeleton.UpdateWorldTransform(Physics.Update);
 		}
 
 		/// <summary>Removes the ragdoll instance and effect from the animated skeleton.</summary>
@@ -301,9 +304,10 @@ namespace Spine.Unity.Examples {
 			boneTable.Add(b, t);
 
 			t.parent = transform;
-			t.localPosition = new Vector3(b.WorldX, b.WorldY, 0);
-			t.localRotation = Quaternion.Euler(0, 0, b.WorldRotationX - b.ShearX);
-			t.localScale = new Vector3(b.WorldScaleX, b.WorldScaleY, 1);
+			var bonePose = b.AppliedPose;
+			t.localPosition = new Vector3(bonePose.WorldX, bonePose.WorldY, 0);
+			t.localRotation = Quaternion.Euler(0, 0, bonePose.WorldRotationX - bonePose.ShearX);
+			t.localScale = new Vector3(bonePose.WorldScaleX, bonePose.WorldScaleY, 1);
 
 			List<Collider> colliders = AttachBoundingBoxRagdollColliders(b);
 			if (colliders.Count == 0) {
@@ -340,8 +344,9 @@ namespace Spine.Unity.Examples {
 					parentFlipX = parentBoneFlip.flipX;
 					parentFlipY = parentBoneFlip.flipY;
 				}
-				bool flipX = parentFlipX ^ (b.ScaleX < 0);
-				bool flipY = parentFlipY ^ (b.ScaleY < 0);
+				var bonePose = b.Pose;
+				bool flipX = parentFlipX ^ (bonePose.ScaleX < 0);
+				bool flipY = parentFlipY ^ (bonePose.ScaleY < 0);
 
 				BoneFlipEntry boneFlip;
 				boneFlipTable.TryGetValue(b, out boneFlip);
@@ -354,9 +359,10 @@ namespace Spine.Unity.Examples {
 
 				if (!oldRagdollBehaviour && isStartingBone) {
 					if (b != skeleton.RootBone) { // RagdollRoot is not skeleton root.
-						ragdollRoot.localPosition = new Vector3(parentBone.WorldX, parentBone.WorldY, 0);
-						ragdollRoot.localRotation = Quaternion.Euler(0, 0, parentBone.WorldRotationX - parentBone.ShearX);
-						ragdollRoot.localScale = new Vector3(parentBone.WorldScaleX, parentBone.WorldScaleY, 1);
+						var parentPose = parentBone.AppliedPose;
+						ragdollRoot.localPosition = new Vector3(parentPose.WorldX, parentPose.WorldY, 0);
+						ragdollRoot.localRotation = Quaternion.Euler(0, 0, parentPose.WorldRotationX - parentPose.ShearX);
+						ragdollRoot.localScale = new Vector3(parentPose.WorldScaleX, parentPose.WorldScaleY, 1);
 					}
 				}
 				Vector3 parentTransformWorldPosition = parentTransform.position;
@@ -368,10 +374,11 @@ namespace Spine.Unity.Examples {
 
 				if (oldRagdollBehaviour) {
 					if (isStartingBone && b != skeleton.RootBone) {
-						Vector3 localPosition = new Vector3(b.Parent.WorldX, b.Parent.WorldY, 0);
+						var parentPose = b.Parent.AppliedPose;
+						Vector3 localPosition = new Vector3(parentPose.WorldX, parentPose.WorldY, 0);
 						parentSpaceHelper.position = ragdollRoot.TransformPoint(localPosition);
-						parentSpaceHelper.localRotation = Quaternion.Euler(0, 0, parentBone.WorldRotationX - parentBone.ShearX);
-						parentSpaceHelper.localScale = new Vector3(parentBone.WorldScaleX, parentBone.WorldScaleY, 1);
+						parentSpaceHelper.localRotation = Quaternion.Euler(0, 0, parentPose.WorldRotationX - parentPose.ShearX);
+						parentSpaceHelper.localScale = new Vector3(parentPose.WorldScaleX, parentPose.WorldScaleY, 1);
 					}
 				}
 
@@ -387,9 +394,9 @@ namespace Spine.Unity.Examples {
 				if (parentFlipXOR) boneLocalRotation *= -1f;
 				if (parentFlipX != flipX) boneLocalRotation += 180;
 
-				b.X = Mathf.Lerp(b.X, boneLocalPosition.x, mix);
-				b.Y = Mathf.Lerp(b.Y, boneLocalPosition.y, mix);
-				b.Rotation = Mathf.Lerp(b.Rotation, boneLocalRotation, mix);
+				bonePose.X = Mathf.Lerp(bonePose.X, boneLocalPosition.x, mix);
+				bonePose.Y = Mathf.Lerp(bonePose.Y, boneLocalPosition.y, mix);
+				bonePose.Rotation = Mathf.Lerp(bonePose.Rotation, boneLocalRotation, mix);
 				//b.AppliedRotation = Mathf.Lerp(b.AppliedRotation, boneLocalRotation, mix);
 			}
 		}
@@ -399,8 +406,9 @@ namespace Spine.Unity.Examples {
 			parentFlipY = skeleton.ScaleY < 0;
 			Bone parent = this.StartingBone == null ? null : this.StartingBone.Parent;
 			while (parent != null) {
-				parentFlipX ^= parent.ScaleX < 0;
-				parentFlipY ^= parent.ScaleY < 0;
+				var parentPose = parent.Pose;
+				parentFlipX ^= parentPose.ScaleX < 0;
+				parentFlipY ^= parentPose.ScaleY < 0;
 				parent = parent.Parent;
 			}
 		}

+ 51 - 43
spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/SkeletonRagdoll2D.cs

@@ -155,8 +155,9 @@ namespace Spine.Unity.Examples {
 						ragdollRoot.localPosition = new Vector3(skeleton.X, skeleton.Y, 0);
 						ragdollRoot.localRotation = (skeleton.ScaleX < 0) ? Quaternion.Euler(0, 0, 180.0f) : Quaternion.identity;
 					} else {
-						ragdollRoot.localPosition = new Vector3(b.Parent.WorldX, b.Parent.WorldY, 0);
-						ragdollRoot.localRotation = Quaternion.Euler(0, 0, b.Parent.WorldRotationX - b.Parent.ShearX);
+						var parentPose = b.Parent.AppliedPose;
+						ragdollRoot.localPosition = new Vector3(parentPose.WorldX, parentPose.WorldY, 0);
+						ragdollRoot.localRotation = Quaternion.Euler(0, 0, parentPose.WorldRotationX - parentPose.ShearX);
 					}
 					parentTransform = ragdollRoot;
 					rootOffset = t.position - transform.position;
@@ -225,28 +226,30 @@ namespace Spine.Unity.Examples {
 			}
 
 			// Disable skeleton constraints.
-			if (disableIK) {
-				ExposedList<IkConstraint> ikConstraints = skeleton.IkConstraints;
-				for (int i = 0, n = ikConstraints.Count; i < n; i++)
-					ikConstraints.Items[i].Mix = 0;
-			}
-
-			if (disableOtherConstraints) {
-				ExposedList<TransformConstraint> transformConstraints = skeleton.TransformConstraints;
-				for (int i = 0, n = transformConstraints.Count; i < n; i++) {
-					transformConstraints.Items[i].MixRotate = 0;
-					transformConstraints.Items[i].MixScaleX = 0;
-					transformConstraints.Items[i].MixScaleY = 0;
-					transformConstraints.Items[i].MixShearY = 0;
-					transformConstraints.Items[i].MixX = 0;
-					transformConstraints.Items[i].MixY = 0;
-				}
-
-				ExposedList<PathConstraint> pathConstraints = skeleton.PathConstraints;
-				for (int i = 0, n = pathConstraints.Count; i < n; i++) {
-					pathConstraints.Items[i].MixRotate = 0;
-					pathConstraints.Items[i].MixX = 0;
-					pathConstraints.Items[i].MixY = 0;
+			ExposedList<IConstraint> constraints = skeleton.Constraints;
+			IConstraint[] constraintsItems = constraints.Items;
+			for (int i = 0, n = constraints.Count; i < n; i++) {
+				var constraint = constraintsItems[i];
+				if (constraint is IkConstraint && disableIK) {
+					var ikConstraint = ((IkConstraint)constraint);
+					ikConstraint.Pose.Mix = 0;
+				} else if (disableOtherConstraints) {
+					if (constraint is TransformConstraint) {
+						var transformConstraint = (TransformConstraint)constraint;
+						var constraintPose = transformConstraint.Pose;
+						constraintPose.MixRotate = 0;
+						constraintPose.MixScaleX = 0;
+						constraintPose.MixScaleY = 0;
+						constraintPose.MixShearY = 0;
+						constraintPose.MixX = 0;
+						constraintPose.MixY = 0;
+					} else if (constraint is PathConstraint) {
+						var pathConstraint = (PathConstraint)constraint;
+						var constraintPose = pathConstraint.Pose;
+						constraintPose.MixRotate = 0;
+						constraintPose.MixX = 0;
+						constraintPose.MixY = 0;
+					}
 				}
 			}
 
@@ -262,7 +265,7 @@ namespace Spine.Unity.Examples {
 			float startTime = Time.time;
 			float startMix = mix;
 			while (mix > 0) {
-				skeleton.SetBonesToSetupPose();
+				skeleton.SetupPoseBones();
 				mix = Mathf.SmoothStep(startMix, target, (Time.time - startTime) / duration);
 				yield return null;
 			}
@@ -281,7 +284,7 @@ namespace Spine.Unity.Examples {
 				t.position -= offset;
 
 			UpdateSpineSkeleton(null);
-			skeleton.UpdateWorldTransform(Skeleton.Physics.Update);
+			skeleton.UpdateWorldTransform(Physics.Update);
 		}
 
 		/// <summary>Removes the ragdoll instance and effect from the animated skeleton.</summary>
@@ -313,9 +316,10 @@ namespace Spine.Unity.Examples {
 			boneTable.Add(b, t);
 
 			t.parent = transform;
-			t.localPosition = new Vector3(b.WorldX, b.WorldY, 0);
-			t.localRotation = Quaternion.Euler(0, 0, b.WorldRotationX - b.ShearX);
-			t.localScale = new Vector3(b.WorldScaleX, b.WorldScaleY, 1);
+			var bonePose = b.AppliedPose;
+			t.localPosition = new Vector3(bonePose.WorldX, bonePose.WorldY, 0);
+			t.localRotation = Quaternion.Euler(0, 0, bonePose.WorldRotationX - bonePose.ShearX);
+			t.localScale = new Vector3(bonePose.WorldScaleX, bonePose.WorldScaleY, 1);
 
 			List<Collider2D> colliders = AttachBoundingBoxRagdollColliders(b, boneGameObject, skeleton, this.gravityScale);
 			if (colliders.Count == 0) {
@@ -356,8 +360,9 @@ namespace Spine.Unity.Examples {
 					parentFlipX = parentBoneFlip.flipX;
 					parentFlipY = parentBoneFlip.flipY;
 				}
-				bool flipX = parentFlipX ^ (b.ScaleX < 0);
-				bool flipY = parentFlipY ^ (b.ScaleY < 0);
+				var bonePose = b.Pose;
+				bool flipX = parentFlipX ^ (bonePose.ScaleX < 0);
+				bool flipY = parentFlipY ^ (bonePose.ScaleY < 0);
 
 				BoneFlipEntry boneFlip;
 				boneFlipTable.TryGetValue(b, out boneFlip);
@@ -370,9 +375,10 @@ namespace Spine.Unity.Examples {
 
 				if (!oldRagdollBehaviour && isStartingBone) {
 					if (b != skeleton.RootBone) { // RagdollRoot is not skeleton root.
-						ragdollRoot.localPosition = new Vector3(parentBone.WorldX, parentBone.WorldY, 0);
-						ragdollRoot.localRotation = Quaternion.Euler(0, 0, parentBone.WorldRotationX - parentBone.ShearX);
-						ragdollRoot.localScale = new Vector3(parentBone.WorldScaleX, parentBone.WorldScaleY, 1);
+						var parentPose = parentBone.AppliedPose;
+						ragdollRoot.localPosition = new Vector3(parentPose.WorldX, parentPose.WorldY, 0);
+						ragdollRoot.localRotation = Quaternion.Euler(0, 0, parentPose.WorldRotationX - parentPose.ShearX);
+						ragdollRoot.localScale = new Vector3(parentPose.WorldScaleX, parentPose.WorldScaleY, 1);
 					}
 				}
 
@@ -385,10 +391,11 @@ namespace Spine.Unity.Examples {
 
 				if (oldRagdollBehaviour) {
 					if (isStartingBone && b != skeleton.RootBone) {
-						Vector3 localPosition = new Vector3(b.Parent.WorldX, b.Parent.WorldY, 0);
+						var parentPose = b.Parent.AppliedPose;
+						Vector3 localPosition = new Vector3(parentPose.WorldX, parentPose.WorldY, 0);
 						parentSpaceHelper.position = ragdollRoot.TransformPoint(localPosition);
-						parentSpaceHelper.localRotation = Quaternion.Euler(0, 0, parentBone.WorldRotationX - parentBone.ShearX);
-						parentSpaceHelper.localScale = new Vector3(parentBone.WorldScaleX, parentBone.WorldScaleY, 1);
+						parentSpaceHelper.localRotation = Quaternion.Euler(0, 0, parentPose.WorldRotationX - parentPose.ShearX);
+						parentSpaceHelper.localScale = new Vector3(parentPose.WorldScaleX, parentPose.WorldScaleY, 1);
 					}
 				}
 
@@ -404,9 +411,9 @@ namespace Spine.Unity.Examples {
 				if (parentFlipXOR) boneLocalRotation *= -1f;
 				if (parentFlipX != flipX) boneLocalRotation += 180;
 
-				b.X = Mathf.Lerp(b.X, boneLocalPosition.x, mix);
-				b.Y = Mathf.Lerp(b.Y, boneLocalPosition.y, mix);
-				b.Rotation = Mathf.Lerp(b.Rotation, boneLocalRotation, mix);
+				bonePose.X = Mathf.Lerp(bonePose.X, boneLocalPosition.x, mix);
+				bonePose.Y = Mathf.Lerp(bonePose.Y, boneLocalPosition.y, mix);
+				bonePose.Rotation = Mathf.Lerp(bonePose.Rotation, boneLocalRotation, mix);
 				//b.AppliedRotation = Mathf.Lerp(b.AppliedRotation, boneLocalRotation, mix);
 			}
 		}
@@ -416,8 +423,9 @@ namespace Spine.Unity.Examples {
 			parentFlipY = skeleton.ScaleY < 0;
 			Bone parent = this.StartingBone == null ? null : this.StartingBone.Parent;
 			while (parent != null) {
-				parentFlipX ^= parent.ScaleX < 0;
-				parentFlipY ^= parent.ScaleY < 0;
+				var parentPose = parent.Pose;
+				parentFlipX ^= parentPose.ScaleX < 0;
+				parentFlipY ^= parentPose.ScaleY < 0;
 				parent = parent.Parent;
 			}
 		}
@@ -440,7 +448,7 @@ namespace Spine.Unity.Examples {
 								continue;
 
 							bbAttachmentAdded = true;
-							PolygonCollider2D bbCollider = SkeletonUtility.AddBoundingBoxAsComponent(bbAttachment, slot, go, isTrigger: false);
+							PolygonCollider2D bbCollider = SkeletonUtility.AddBoundingBoxAsComponent(bbAttachment, skeleton, slot, go, isTrigger: false);
 							colliders.Add(bbCollider);
 						}
 					}

+ 5 - 4
spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/SkeletonUtilityGroundConstraint.cs

@@ -97,9 +97,9 @@ namespace Spine.Unity.Examples {
 				bool validHit = false;
 
 				if (useRadius)
-					validHit = Physics.SphereCast(rayOrigin, castRadius, rayDir, out hit, castDistance + groundOffset, groundMask);
+					validHit = UnityEngine.Physics.SphereCast(rayOrigin, castRadius, rayDir, out hit, castDistance + groundOffset, groundMask);
 				else
-					validHit = Physics.Raycast(rayOrigin, rayDir, out hit, castDistance + groundOffset, groundMask);
+					validHit = UnityEngine.Physics.Raycast(rayOrigin, rayDir, out hit, castDistance + groundOffset, groundMask);
 
 				if (validHit) {
 					hitY = hit.point.y + groundOffset;
@@ -116,8 +116,9 @@ namespace Spine.Unity.Examples {
 			v.y = Mathf.Clamp(v.y, Mathf.Min(lastHitY, hitY), float.MaxValue);
 			transform.position = v;
 
-			bone.bone.X = transform.localPosition.x / hierarchy.PositionScale;
-			bone.bone.Y = transform.localPosition.y / hierarchy.PositionScale;
+			var bonePose = bone.bone.Pose;
+			bonePose.X = transform.localPosition.x / hierarchy.PositionScale;
+			bonePose.Y = transform.localPosition.y / hierarchy.PositionScale;
 
 			lastHitY = hitY;
 		}

+ 1 - 1
spine-unity/Assets/Spine Examples/Scripts/SpawnSkeletonGraphicExample.cs

@@ -54,7 +54,7 @@ namespace Spine.Unity.Examples {
 			// Extra Stuff
 			sg.Initialize(false);
 			sg.Skeleton.SetSkin(startingSkin);
-			sg.Skeleton.SetSlotsToSetupPose();
+			sg.Skeleton.SetupPoseSlots();
 			sg.AnimationState.SetAnimation(0, startingAnimation, true);
 		}
 	}

+ 2 - 2
spine-unity/Assets/Spine Examples/Scripts/SpineGauge.cs

@@ -55,8 +55,8 @@ namespace Spine.Unity.Examples {
 			if (skeletonRenderer == null) return;
 			Skeleton skeleton = skeletonRenderer.skeleton; if (skeleton == null) return;
 
-			fillAnimation.Animation.Apply(skeleton, 0, percent, false, null, 1f, MixBlend.Setup, MixDirection.In);
-			skeleton.UpdateWorldTransform(Skeleton.Physics.Update);
+			fillAnimation.Animation.Apply(skeleton, 0, percent, false, null, 1f, MixBlend.Setup, MixDirection.In, false);
+			skeleton.UpdateWorldTransform(Physics.Update);
 		}
 	}
 

+ 3 - 3
spine-unity/Assets/Spine Examples/Scripts/SpineboyBodyTilt.cs

@@ -56,7 +56,7 @@ namespace Spine.Unity.Examples {
 
 			hipBone = skeleton.FindBone(hip);
 			headBone = skeleton.FindBone(head);
-			baseHeadRotation = headBone.Rotation;
+			baseHeadRotation = headBone.Pose.Rotation;
 
 			skeletonAnimation.UpdateLocal += UpdateLocal;
 		}
@@ -64,8 +64,8 @@ namespace Spine.Unity.Examples {
 		private void UpdateLocal (ISkeletonAnimation animated) {
 			hipRotationTarget = planter.Balance * hipTiltScale;
 			hipRotationSmoothed = Mathf.MoveTowards(hipRotationSmoothed, hipRotationTarget, Time.deltaTime * hipRotationMoveScale * Mathf.Abs(2f * planter.Balance / planter.offBalanceThreshold));
-			hipBone.Rotation = hipRotationSmoothed;
-			headBone.Rotation = baseHeadRotation + (-hipRotationSmoothed * headTiltScale);
+			hipBone.Pose.Rotation = hipRotationSmoothed;
+			headBone.Pose.Rotation = baseHeadRotation + (-hipRotationSmoothed * headTiltScale);
 		}
 	}
 

+ 4 - 4
spine-unity/Assets/Spine Examples/Scripts/SpineboyFacialExpression.cs

@@ -77,11 +77,11 @@ namespace Spine.Unity.Examples {
 				shockTimer -= Time.deltaTime;
 
 			if (shockTimer > 0) {
-				eyeSlot.Attachment = shockEye;
-				mouthSlot.Attachment = shockMouth;
+				eyeSlot.AppliedPose.Attachment = shockEye;
+				mouthSlot.AppliedPose.Attachment = shockMouth;
 			} else {
-				eyeSlot.Attachment = normalEye;
-				mouthSlot.Attachment = normalMouth;
+				eyeSlot.AppliedPose.Attachment = normalEye;
+				mouthSlot.AppliedPose.Attachment = normalMouth;
 			}
 		}
 	}

+ 21 - 8
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SkeletonDataAssetInspector.cs

@@ -36,6 +36,7 @@
 
 using System;
 using System.Collections.Generic;
+using System.Linq;
 using System.Reflection;
 using UnityEditor;
 using UnityEngine;
@@ -340,8 +341,19 @@ namespace Spine.Unity.Editor {
 				EditorGUILayout.LabelField("SkeletonData", EditorStyles.boldLabel);
 				if (targetSkeletonData != null) {
 					SkeletonData sd = targetSkeletonData;
-					string m = string.Format("{8} - {0} {1}\nBones: {2}\nConstraints: \n {5} IK \n {6} Path \n {7} Transform\n\nSlots: {3}\nSkins: {4}\n\nAnimations: {9}",
-						sd.Version, string.IsNullOrEmpty(sd.Version) ? "" : "export          ", sd.Bones.Count, sd.Slots.Count, sd.Skins.Count, sd.IkConstraints.Count, sd.PathConstraints.Count, sd.TransformConstraints.Count, skeletonJSON.objectReferenceValue.name, sd.Animations.Count);
+					var ikConstraints = sd.Constraints.OfType<IkConstraintData>();
+					var pathConstraints = sd.Constraints.OfType<PathConstraintData>();
+					var transformConstraints = sd.Constraints.OfType<TransformConstraintData>();
+					var physicsConstraints = sd.Constraints.OfType<PhysicsConstraintData>();
+					int ikCount = ikConstraints.Count();
+					int pathCount = pathConstraints.Count();
+					int tcCount = transformConstraints.Count();
+					int physicsCount = physicsConstraints.Count();
+
+					string m = string.Format("{9} - {0} {1}\nBones: {2}\nConstraints: \n {5} IK \n {6} Path \n {7} Transform, {8} Physics, \n\nSlots: {3}\nSkins: {4}\n\nAnimations: {10}",
+						sd.Version, string.IsNullOrEmpty(sd.Version) ? "" : "export          ",
+						sd.Bones.Count, sd.Slots.Count, sd.Skins.Count, ikCount, pathCount, tcCount, physicsCount,
+						skeletonJSON.objectReferenceValue.name, sd.Animations.Count);
 					EditorGUILayout.LabelField(GUIContent.none, new GUIContent(Icons.info, m), GUILayout.Width(30f));
 				}
 			}
@@ -552,14 +564,15 @@ namespace Spine.Unity.Editor {
 							for (int a = 0; a < slotAttachments.Count; a++) {
 								Skin.SkinEntry skinEntry = slotAttachments[a];
 								Attachment attachment = skinEntry.Attachment;
+								var slotPose = slot.AppliedPose;
 								string attachmentName = skinEntry.Name;
 								bool attachmentIsFromSkin = !defaultSkinAttachments.Contains(skinEntry);
 
 								Texture2D attachmentTypeIcon = Icons.GetAttachmentIcon(attachment);
-								bool initialState = slot.Attachment == attachment;
+								bool initialState = slotPose.Attachment == attachment;
 
 								Texture2D iconToUse = attachmentIsFromSkin ? Icons.skinPlaceholder : attachmentTypeIcon;
-								bool toggled = EditorGUILayout.ToggleLeft(SpineInspectorUtility.TempContent(attachmentName, iconToUse), slot.Attachment == attachment, GUILayout.MinWidth(150f));
+								bool toggled = EditorGUILayout.ToggleLeft(SpineInspectorUtility.TempContent(attachmentName, iconToUse), slotPose.Attachment == attachment, GUILayout.MinWidth(150f));
 
 								if (attachmentIsFromSkin) {
 									Rect extraIconRect = GUILayoutUtility.GetLastRect();
@@ -570,7 +583,7 @@ namespace Spine.Unity.Editor {
 								}
 
 								if (toggled != initialState) {
-									slot.Attachment = toggled ? attachment : null;
+									slotPose.Attachment = toggled ? attachment : null;
 									preview.RefreshOnNextUpdate();
 								}
 							}
@@ -1027,7 +1040,7 @@ namespace Spine.Unity.Editor {
 			}
 
 			skeletonAnimation.AnimationState.ClearTracks();
-			skeletonAnimation.Skeleton.SetToSetupPose();
+			skeletonAnimation.Skeleton.SetupPose();
 		}
 
 		public void PlayPauseAnimation (string animationName, bool loop) {
@@ -1041,7 +1054,7 @@ namespace Spine.Unity.Editor {
 			if (!skeletonAnimation.valid) return;
 
 			if (string.IsNullOrEmpty(animationName)) {
-				skeletonAnimation.Skeleton.SetToSetupPose();
+				skeletonAnimation.Skeleton.SetupPose();
 				skeletonAnimation.AnimationState.ClearTracks();
 				return;
 			}
@@ -1056,7 +1069,7 @@ namespace Spine.Unity.Editor {
 				AnimationState animationState = skeletonAnimation.AnimationState;
 
 				if (isEmpty) {
-					skeleton.SetToSetupPose();
+					skeleton.SetupPose();
 					animationState.SetAnimation(0, targetAnimation, loop);
 				} else {
 					bool sameAnimation = (currentTrack.Animation == targetAnimation);

+ 1 - 1
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonAnimationInspector.cs

@@ -104,7 +104,7 @@ namespace Spine.Unity.Editor {
 
 					if (!Application.isPlaying) {
 						if (state != null) state.ClearTrack(0);
-						skeleton.SetToSetupPose();
+						skeleton.SetupPose();
 					}
 
 					Spine.Animation animationToUse = skeleton.Data.FindAnimation(animationName.stringValue);

+ 2 - 2
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicInspector.cs

@@ -657,7 +657,7 @@ namespace Spine.Unity.Editor {
 			SlotData[] slotsItems = skeletonGraphic.SkeletonData.Slots.Items;
 			for (int i = 0, count = skeletonGraphic.SkeletonData.Slots.Count; i < count; ++i) {
 				SlotData slotData = slotsItems[i];
-				if (slotData.HasSecondColor)
+				if (slotData.GetSetupPose().GetDarkColor().HasValue)
 					return true;
 			}
 			return false;
@@ -857,7 +857,7 @@ namespace Spine.Unity.Editor {
 			graphic.Initialize(false);
 			if (skin != null) graphic.Skeleton.SetSkin(skin);
 			graphic.initialSkinName = skin.Name;
-			graphic.Skeleton.UpdateWorldTransform(Skeleton.Physics.Update);
+			graphic.Skeleton.UpdateWorldTransform(Physics.Update);
 			graphic.UpdateMesh();
 			return graphic;
 		}

+ 2 - 2
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimInspector.cs

@@ -123,10 +123,10 @@ namespace Spine.Unity.Editor {
 				Skeleton skeleton = skeletonRenderer.Skeleton;
 				SkeletonData skeletonData = skeleton.Data;
 
-				skeleton.SetToSetupPose();
+				skeleton.SetupPose();
 				if (clip != null) {
 					Spine.Animation animation = skeletonData.FindAnimation(clip.name);
-					animation.Apply(skeleton, 0, time, false, null, 1.0f, MixBlend.First, MixDirection.In);
+					animation.Apply(skeleton, 0, time, false, null, 1.0f, MixBlend.First, MixDirection.In, false);
 				}
 				skeletonRenderer.LateUpdate();
 			}

+ 1 - 1
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRendererInspector.cs

@@ -636,7 +636,7 @@ namespace Spine.Unity.Editor {
 			if (!fieldMatchesSkin) {
 				Skin skinToSet = string.IsNullOrEmpty(componentSkinName) ? null : skeletonRenderer.Skeleton.Data.FindSkin(componentSkinName);
 				skeletonRenderer.Skeleton.SetSkin(skinToSet);
-				skeletonRenderer.Skeleton.SetSlotsToSetupPose();
+				skeletonRenderer.Skeleton.SetupPoseSlots();
 
 				// Note: the UpdateIfSkinMismatch concept shall be replaced with e.g. an OnValidate based
 				// solution or in a separate commit. The current solution does not repaint the Game view because

+ 6 - 5
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonUtilityBoneInspector.cs

@@ -85,7 +85,7 @@ namespace Spine.Unity.Editor {
 			if (multiObject) return;
 			if (utilityBone.bone == null) return;
 
-			Skeleton skeleton = utilityBone.bone.Skeleton;
+			Skeleton skeleton = skeletonUtility.SkeletonComponent.Skeleton;
 			int slotCount = skeleton.Slots.Count;
 			Skin skin = skeleton.Skin;
 			if (skeleton.Skin == null)
@@ -208,21 +208,22 @@ namespace Spine.Unity.Editor {
 					EditorGUILayout.LabelField(slot.Data.Name);
 					EditorGUI.indentLevel++;
 					{
+						Skeleton skeleton = skeletonUtility.SkeletonComponent.Skeleton;
 						foreach (BoundingBoxAttachment box in boundingBoxes) {
 							using (new GUILayout.HorizontalScope()) {
 								GUILayout.Space(30);
 								string buttonLabel = box.IsWeighted() ? box.Name + " (!)" : box.Name;
 								if (GUILayout.Button(buttonLabel, GUILayout.Width(200))) {
-									utilityBone.bone.Skeleton.UpdateWorldTransform(Skeleton.Physics.Update);
+									skeleton.UpdateWorldTransform(Physics.Update);
 									Transform bbTransform = utilityBone.transform.Find("[BoundingBox]" + box.Name); // Use FindChild in older versions of Unity.
 									if (bbTransform != null) {
 										PolygonCollider2D originalCollider = bbTransform.GetComponent<PolygonCollider2D>();
 										if (originalCollider != null)
-											SkeletonUtility.SetColliderPointsLocal(originalCollider, slot, box);
+											SkeletonUtility.SetColliderPointsLocal(originalCollider, skeleton, slot, box);
 										else
-											SkeletonUtility.AddBoundingBoxAsComponent(box, slot, bbTransform.gameObject);
+											SkeletonUtility.AddBoundingBoxAsComponent(box, skeleton, slot, bbTransform.gameObject);
 									} else {
-										PolygonCollider2D newPolygonCollider = SkeletonUtility.AddBoundingBoxGameObject(null, box, slot, utilityBone.transform);
+										PolygonCollider2D newPolygonCollider = SkeletonUtility.AddBoundingBoxGameObject(null, box, skeleton, slot, utilityBone.transform);
 										bbTransform = newPolygonCollider.transform;
 									}
 									EditorGUIUtility.PingObject(bbTransform);

+ 3 - 1
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonUtilityInspector.cs

@@ -35,6 +35,7 @@
 #define PUBLIC_SET_ICON_FOR_OBJECT
 #endif
 
+using System.Linq;
 using System.Reflection;
 using UnityEditor;
 using UnityEngine;
@@ -144,7 +145,8 @@ namespace Spine.Unity.Editor {
 			Skeleton skeleton = boneComponent.hierarchy.Skeleton;
 			Texture2D icon = boneComponent.bone.Data.Length == 0 ? Icons.nullBone : Icons.boneNib;
 
-			foreach (IkConstraint c in skeleton.IkConstraints)
+			var ikConstraints = skeleton.Constraints.OfType<IkConstraint>();
+			foreach (IkConstraint c in ikConstraints)
 				if (c.Target == boneComponent.bone) {
 					icon = Icons.constraintNib;
 					break;

+ 12 - 15
spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineAttributeDrawers.cs

@@ -463,17 +463,16 @@ namespace Spine.Unity.Editor {
 		protected override Texture2D Icon { get { return SpineEditorUtilities.Icons.constraintIK; } }
 
 		protected override bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) {
-			return skeletonData.FindIkConstraint(property.stringValue) != null;
+			return skeletonData.FindConstraint<IkConstraintData>(property.stringValue) != null;
 		}
 
 		protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpineIkConstraint targetAttribute, SkeletonData data) {
-			ExposedList<IkConstraintData> constraints = skeletonDataAsset.GetSkeletonData(false).IkConstraints;
-
 			if (TargetAttribute.includeNone)
 				menu.AddItem(new GUIContent(NoneString), !property.hasMultipleDifferentValues && string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property));
 
-			for (int i = 0; i < constraints.Count; i++) {
-				string name = constraints.Items[i].Name;
+			var ikConstraints = skeletonDataAsset.GetSkeletonData(false).Constraints.OfType<IkConstraintData>();
+			foreach (var ikConstraint in ikConstraints) {
+				string name = ikConstraint.Name;
 				if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal))
 					menu.AddItem(new GUIContent(name), !property.hasMultipleDifferentValues && name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
 			}
@@ -487,17 +486,16 @@ namespace Spine.Unity.Editor {
 		protected override Texture2D Icon { get { return SpineEditorUtilities.Icons.constraintTransform; } }
 
 		protected override bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) {
-			return skeletonData.FindTransformConstraint(property.stringValue) != null;
+			return skeletonData.FindConstraint<TransformConstraintData>(property.stringValue) != null;
 		}
 
 		protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpineTransformConstraint targetAttribute, SkeletonData data) {
-			ExposedList<TransformConstraintData> constraints = skeletonDataAsset.GetSkeletonData(false).TransformConstraints;
-
 			if (TargetAttribute.includeNone)
 				menu.AddItem(new GUIContent(NoneString), !property.hasMultipleDifferentValues && string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property));
 
-			for (int i = 0; i < constraints.Count; i++) {
-				string name = constraints.Items[i].Name;
+			var transformConstraints = skeletonDataAsset.GetSkeletonData(false).Constraints.OfType<TransformConstraintData>();
+			foreach (var constraint in transformConstraints) {
+				string name = constraint.Name;
 				if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal))
 					menu.AddItem(new GUIContent(name), !property.hasMultipleDifferentValues && name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
 			}
@@ -510,17 +508,16 @@ namespace Spine.Unity.Editor {
 		protected override Texture2D Icon { get { return SpineEditorUtilities.Icons.constraintPath; } }
 
 		protected override bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) {
-			return skeletonData.FindPathConstraint(property.stringValue) != null;
+			return skeletonData.FindConstraint<PathConstraintData>(property.stringValue) != null;
 		}
 
 		protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpinePathConstraint targetAttribute, SkeletonData data) {
-			ExposedList<PathConstraintData> constraints = skeletonDataAsset.GetSkeletonData(false).PathConstraints;
-
 			if (TargetAttribute.includeNone)
 				menu.AddItem(new GUIContent(NoneString), !property.hasMultipleDifferentValues && string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property));
 
-			for (int i = 0; i < constraints.Count; i++) {
-				string name = constraints.Items[i].Name;
+			var pathConstraints = skeletonDataAsset.GetSkeletonData(false).Constraints.OfType<TransformConstraintData>();
+			foreach (var constraint in pathConstraints) {
+				string name = constraint.Name;
 				if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal))
 					menu.AddItem(new GUIContent(name), !property.hasMultipleDifferentValues && name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
 			}

+ 2 - 2
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/AssetUtility.cs

@@ -1414,7 +1414,7 @@ namespace Spine.Unity.Editor {
 			newSkeletonAnimation.loop = SpineEditorUtilities.Preferences.defaultInstantiateLoop;
 			newSkeletonAnimation.state.Update(0);
 			newSkeletonAnimation.state.Apply(newSkeletonAnimation.skeleton);
-			newSkeletonAnimation.skeleton.UpdateWorldTransform(Skeleton.Physics.Update);
+			newSkeletonAnimation.skeleton.UpdateWorldTransform(Physics.Update);
 
 			return newSkeletonAnimation;
 		}
@@ -1497,7 +1497,7 @@ namespace Spine.Unity.Editor {
 				throw e;
 			}
 
-			newSkeletonMecanim.skeleton.UpdateWorldTransform(Skeleton.Physics.Update);
+			newSkeletonMecanim.skeleton.UpdateWorldTransform(Physics.Update);
 			newSkeletonMecanim.LateUpdate();
 
 			return newSkeletonMecanim;

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

@@ -171,12 +171,13 @@ namespace Spine.Unity.Editor {
 
 			Vector2 offset = positionOffset == null ? Vector2.zero : positionOffset.Value;
 			GUIStyle style = BoneNameStyle;
-			foreach (Bone b in skeleton.Bones) {
-				if (!b.Active) continue;
-				Vector3 pos = new Vector3(b.WorldX * positionScale + offset.x, b.WorldY * positionScale + offset.y, 0)
-					+ (new Vector3(b.A, b.C) * (b.Data.Length * 0.5f));
+			foreach (Bone bone in skeleton.Bones) {
+				if (!bone.Active) continue;
+				var bonePose = bone.AppliedPose;
+				Vector3 pos = new Vector3(bonePose.WorldX * positionScale + offset.x, bonePose.WorldY * positionScale + offset.y, 0)
+					+ (new Vector3(bonePose.A, bonePose.C) * (bone.Data.Length * 0.5f));
 				pos = transform.TransformPoint(pos);
-				Handles.Label(pos, b.Data.Name, style);
+				Handles.Label(pos, bone.Data.Name, style);
 			}
 		}
 
@@ -203,18 +204,19 @@ namespace Spine.Unity.Editor {
 			_boneWireBuffer[4] = _boneWireBuffer[0]; // closed polygon.
 			return _boneWireBuffer;
 		}
-		public static void DrawBoneWireframe (Transform transform, Bone b, Color color, float skeletonRenderScale = 1f,
+		public static void DrawBoneWireframe (Transform transform, Bone bone, Color color, float skeletonRenderScale = 1f,
 			Vector2? positionOffset = null) {
 			if (UnityEngine.Event.current.type != EventType.Repaint) return;
 
 			Vector2 offset = positionOffset == null ? Vector2.zero : positionOffset.Value;
 			Handles.color = color;
-			Vector3 pos = new Vector3(b.WorldX * skeletonRenderScale + offset.x, b.WorldY * skeletonRenderScale + offset.y, 0);
-			float length = b.Data.Length;
+			var bonePose = bone.AppliedPose;
+			Vector3 pos = new Vector3(bonePose.WorldX * skeletonRenderScale + offset.x, bonePose.WorldY * skeletonRenderScale + offset.y, 0);
+			float length = bone.Data.Length;
 
 			if (length > 0) {
-				Quaternion rot = Quaternion.Euler(0, 0, b.WorldRotationX);
-				Vector3 scale = Vector3.one * length * b.WorldScaleX * skeletonRenderScale;
+				Quaternion rot = Quaternion.Euler(0, 0, bonePose.WorldRotationX);
+				Vector3 scale = Vector3.one * length * bonePose.WorldScaleX * skeletonRenderScale;
 				const float my = 1.5f;
 				scale.y *= (SpineEditorUtilities.Preferences.handleScale + 1) * 0.5f;
 				scale.y = Mathf.Clamp(scale.x, -my * skeletonRenderScale, my * skeletonRenderScale);
@@ -227,16 +229,17 @@ namespace Spine.Unity.Editor {
 			}
 		}
 
-		public static void DrawBone (Transform transform, Bone b, float boneScale, float skeletonRenderScale = 1f,
+		public static void DrawBone (Transform transform, Bone bone, float boneScale, float skeletonRenderScale = 1f,
 			Vector2? positionOffset = null) {
 			if (UnityEngine.Event.current.type != EventType.Repaint) return;
-
+			
+			var bonePose = bone.AppliedPose;
 			Vector2 offset = positionOffset == null ? Vector2.zero : positionOffset.Value;
-			Vector3 pos = new Vector3(b.WorldX * skeletonRenderScale + offset.x, b.WorldY * skeletonRenderScale + offset.y, 0);
-			float length = b.Data.Length;
+			Vector3 pos = new Vector3(bonePose.WorldX * skeletonRenderScale + offset.x, bonePose.WorldY * skeletonRenderScale + offset.y, 0);
+			float length = bone.Data.Length;
 			if (length > 0) {
-				Quaternion rot = Quaternion.Euler(0, 0, b.WorldRotationX);
-				Vector3 scale = Vector3.one * length * b.WorldScaleX * skeletonRenderScale;
+				Quaternion rot = Quaternion.Euler(0, 0, bonePose.WorldRotationX);
+				Vector3 scale = Vector3.one * length * bonePose.WorldScaleX * skeletonRenderScale;
 				const float my = 1.5f;
 				scale.y *= (SpineEditorUtilities.Preferences.handleScale + 1f) * 0.5f;
 				scale.y = Mathf.Clamp(scale.x, -my * skeletonRenderScale, my * skeletonRenderScale);
@@ -248,16 +251,17 @@ namespace Spine.Unity.Editor {
 			}
 		}
 
-		public static void DrawBone (Transform transform, Bone b, float boneScale, Color color, float skeletonRenderScale = 1f,
+		public static void DrawBone (Transform transform, Bone bone, float boneScale, Color color, float skeletonRenderScale = 1f,
 			Vector2? positionOffset = null) {
 			if (UnityEngine.Event.current.type != EventType.Repaint) return;
 
+			var bonePose = bone.AppliedPose;
 			Vector2 offset = positionOffset == null ? Vector2.zero : positionOffset.Value;
-			Vector3 pos = new Vector3(b.WorldX * skeletonRenderScale + offset.x, b.WorldY * skeletonRenderScale + offset.y, 0);
-			float length = b.Data.Length;
+			Vector3 pos = new Vector3(bonePose.WorldX * skeletonRenderScale + offset.x, bonePose.WorldY * skeletonRenderScale + offset.y, 0);
+			float length = bone.Data.Length;
 			if (length > 0) {
-				Quaternion rot = Quaternion.Euler(0, 0, b.WorldRotationX);
-				Vector3 scale = Vector3.one * length * b.WorldScaleX;
+				Quaternion rot = Quaternion.Euler(0, 0, bonePose.WorldRotationX);
+				Vector3 scale = Vector3.one * length * bonePose.WorldScaleX;
 				const float my = 1.5f;
 				scale.y *= (SpineEditorUtilities.Preferences.handleScale + 1f) * 0.5f;
 				scale.y = Mathf.Clamp(scale.x, -my, my);
@@ -273,13 +277,13 @@ namespace Spine.Unity.Editor {
 			if (UnityEngine.Event.current.type != EventType.Repaint) return;
 
 			foreach (Slot s in skeleton.DrawOrder) {
-				PathAttachment p = s.Attachment as PathAttachment;
-				if (p != null) SpineHandles.DrawPath(s, p, transform, true);
+				PathAttachment p = s.AppliedPose.Attachment as PathAttachment;
+				if (p != null) SpineHandles.DrawPath(skeleton, s, p, transform, true);
 			}
 		}
 
 		static float[] pathVertexBuffer;
-		public static void DrawPath (Slot s, PathAttachment p, Transform t, bool includeName) {
+		public static void DrawPath (Skeleton skeleton, Slot s, PathAttachment p, Transform t, bool includeName) {
 			if (UnityEngine.Event.current.type != EventType.Repaint) return;
 
 			int worldVerticesLength = p.WorldVerticesLength;
@@ -288,7 +292,7 @@ namespace Spine.Unity.Editor {
 				pathVertexBuffer = new float[worldVerticesLength];
 
 			float[] pv = pathVertexBuffer;
-			p.ComputeWorldVertices(s, pv);
+			p.ComputeWorldVertices(skeleton, s, pv);
 
 			Color ocolor = Handles.color;
 			Handles.color = SpineHandles.PathColor;
@@ -334,18 +338,18 @@ namespace Spine.Unity.Editor {
 			if (UnityEngine.Event.current.type != EventType.Repaint) return;
 
 			foreach (Slot slot in skeleton.Slots) {
-				BoundingBoxAttachment bba = slot.Attachment as BoundingBoxAttachment;
-				if (bba != null) SpineHandles.DrawBoundingBox(slot, bba, transform);
+				BoundingBoxAttachment bba = slot.AppliedPose.Attachment as BoundingBoxAttachment;
+				if (bba != null) SpineHandles.DrawBoundingBox(skeleton, slot, bba, transform);
 			}
 		}
 
-		public static void DrawBoundingBox (Slot slot, BoundingBoxAttachment box, Transform t) {
+		public static void DrawBoundingBox (Skeleton skeleton, Slot slot, BoundingBoxAttachment box, Transform t) {
 			if (UnityEngine.Event.current.type != EventType.Repaint) return;
 
 			if (box.Vertices.Length <= 2) return; // Handle cases where user creates a BoundingBoxAttachment but doesn't actually define it.
 
 			float[] worldVerts = new float[box.WorldVerticesLength];
-			box.ComputeWorldVertices(slot, worldVerts);
+			box.ComputeWorldVertices(skeleton, slot, worldVerts);
 
 			Handles.color = Color.green;
 			Vector3 lastVert = Vector3.zero;
@@ -374,8 +378,9 @@ namespace Spine.Unity.Editor {
 			if (pointAttachment == null) return;
 
 			Vector2 localPos;
-			pointAttachment.ComputeWorldPosition(bone, out localPos.x, out localPos.y);
-			float localRotation = pointAttachment.ComputeWorldRotation(bone);
+			var bonePose = bone.AppliedPose;
+			pointAttachment.ComputeWorldPosition(bonePose, out localPos.x, out localPos.y);
+			float localRotation = pointAttachment.ComputeWorldRotation(bonePose);
 			Matrix4x4 m = Matrix4x4.TRS(localPos, Quaternion.Euler(0, 0, localRotation), Vector3.one) * Matrix4x4.TRS(Vector3.right * 0.25f, Quaternion.identity, Vector3.one);
 
 			DrawBoneCircle(skeletonTransform.TransformPoint(localPos), SpineHandles.PointColor, Vector3.back, 1.3f);
@@ -396,31 +401,41 @@ namespace Spine.Unity.Editor {
 
 			// Transform Constraints
 			handleColor = SpineHandles.TransformContraintColor;
-			foreach (TransformConstraint tc in skeleton.TransformConstraints) {
-				Bone sourceBone = tc.Source;
+			foreach (IConstraint constraint in skeleton.Constraints) {
+				var transformConstraint = constraint as TransformConstraint;
+				if (transformConstraint == null) continue;
+
+				Bone sourceBone = transformConstraint.Source;
+				var sourcePose = sourceBone.AppliedPose;
 				targetPos = sourceBone.GetWorldPosition(transform, skeletonRenderScale, offset);
 
+				var tc = transformConstraint.AppliedPose;
 				if (tc.MixX > 0 || tc.MixY > 0) {
 					if ((tc.MixX > 0 && tc.MixX != 1f) ||
 						(tc.MixY > 0 && tc.MixY != 1f)) {
 						Handles.color = handleColor;
-						foreach (Bone b in tc.Bones) {
+						foreach (BonePose b in transformConstraint.Bones) {
 							pos = b.GetWorldPosition(transform, skeletonRenderScale, offset);
 							Handles.DrawDottedLine(targetPos, pos, Thickness);
 						}
 					}
 					SpineHandles.DrawBoneCircle(targetPos, handleColor, normal, 1.3f * skeletonRenderScale);
 					Handles.color = handleColor;
-					SpineHandles.DrawCrosshairs(targetPos, 0.2f, sourceBone.A, sourceBone.B, sourceBone.C, sourceBone.D, transform, skeletonRenderScale);
+					SpineHandles.DrawCrosshairs(targetPos, 0.2f, sourcePose.A, sourcePose.B, sourcePose.C, sourcePose.D, transform, skeletonRenderScale);
 				}
 			}
 
 			// IK Constraints
 			handleColor = SpineHandles.IkColor;
-			foreach (IkConstraint ikc in skeleton.IkConstraints) {
-				Bone targetBone = ikc.Target;
+			foreach (IConstraint constraint in skeleton.Constraints) {
+				var ikConstraint = constraint as IkConstraint;
+				if (ikConstraint == null) continue;
+
+				var ikc = ikConstraint.AppliedPose;
+				Bone targetBone = ikConstraint.Target;
+				BonePose targetBonePose = targetBone.AppliedPose;
 				targetPos = targetBone.GetWorldPosition(transform, skeletonRenderScale, offset);
-				ExposedList<Bone> bones = ikc.Bones;
+				ExposedList<BonePose> bones = ikConstraint.Bones;
 				active = ikc.Mix > 0;
 				if (active) {
 					pos = bones.Items[0].GetWorldPosition(transform, skeletonRenderScale, offset);
@@ -430,13 +445,13 @@ namespace Spine.Unity.Editor {
 						Handles.DrawLine(targetPos, pos);
 						SpineHandles.DrawBoneCircle(targetPos, handleColor, normal);
 						Matrix4x4 m = bones.Items[0].GetMatrix4x4();
-						m.m03 = targetBone.WorldX * skeletonRenderScale + offset.x;
-						m.m13 = targetBone.WorldY * skeletonRenderScale + offset.y;
+						m.m03 = targetBonePose.WorldX * skeletonRenderScale + offset.x;
+						m.m13 = targetBonePose.WorldY * skeletonRenderScale + offset.y;
 						SpineHandles.DrawArrowhead(transform.localToWorldMatrix * m);
 						break;
 					}
 					case 2: {
-						Bone childBone = bones.Items[1];
+						BonePose childBone = bones.Items[1];
 						Vector3 child = childBone.GetWorldPosition(transform, skeletonRenderScale, offset);
 						Handles.color = handleColor;
 						Handles.DrawLine(child, pos);
@@ -445,8 +460,8 @@ namespace Spine.Unity.Editor {
 						SpineHandles.DrawBoneCircle(child, handleColor, normal, 0.5f);
 						SpineHandles.DrawBoneCircle(targetPos, handleColor, normal);
 						Matrix4x4 m = childBone.GetMatrix4x4();
-						m.m03 = targetBone.WorldX * skeletonRenderScale + offset.x;
-						m.m13 = targetBone.WorldY * skeletonRenderScale + offset.y;
+						m.m03 = targetBonePose.WorldX * skeletonRenderScale + offset.x;
+						m.m13 = targetBonePose.WorldY * skeletonRenderScale + offset.y;
 						SpineHandles.DrawArrowhead(transform.localToWorldMatrix * m);
 						break;
 					}
@@ -457,10 +472,14 @@ namespace Spine.Unity.Editor {
 
 			// Path Constraints
 			handleColor = SpineHandles.PathColor;
-			foreach (PathConstraint pc in skeleton.PathConstraints) {
+			foreach (IConstraint constraint in skeleton.Constraints) {
+				var pathConstraint = constraint as PathConstraint;
+				if (pathConstraint == null) continue;
+
+				var pc = pathConstraint.AppliedPose;
 				active = pc.MixX > 0 || pc.MixY > 0 || pc.MixRotate > 0;
 				if (active)
-					foreach (Bone b in pc.Bones)
+					foreach (BonePose b in pathConstraint.Bones)
 						SpineHandles.DrawBoneCircle(b.GetWorldPosition(transform, skeletonRenderScale, offset),
 							handleColor, normal, 1f * skeletonRenderScale);
 			}

+ 110 - 91
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SkeletonBaker.cs

@@ -323,6 +323,7 @@ namespace Spine.Unity.Editor {
 				for (int i = 0; i < skeletonData.Bones.Count; i++) {
 
 					BoneData boneData = skeletonData.Bones.Items[i];
+					var setup = boneData.GetSetupPose();
 					Transform boneTransform = boneTable[boneData.Name];
 					Transform parentTransform = null;
 					if (i > 0)
@@ -331,15 +332,15 @@ namespace Spine.Unity.Editor {
 						parentTransform = boneTransform.parent;
 
 					boneTransform.parent = parentTransform;
-					boneTransform.localPosition = new Vector3(boneData.X, boneData.Y, 0);
-					Inherit inherit = boneData.Inherit;
+					boneTransform.localPosition = new Vector3(setup.X, setup.Y, 0);
+					Inherit inherit = setup.Inherit;
 					if (inherit.InheritsRotation())
-						boneTransform.localRotation = Quaternion.Euler(0, 0, boneData.Rotation);
+						boneTransform.localRotation = Quaternion.Euler(0, 0, setup.Rotation);
 					else
-						boneTransform.rotation = Quaternion.Euler(0, 0, boneData.Rotation);
+						boneTransform.rotation = Quaternion.Euler(0, 0, setup.Rotation);
 
 					if (inherit.InheritsScale())
-						boneTransform.localScale = new Vector3(boneData.ScaleX, boneData.ScaleY, 1);
+						boneTransform.localScale = new Vector3(setup.ScaleX, setup.ScaleY, 1);
 				}
 
 				//create slots and attachments
@@ -477,31 +478,36 @@ namespace Spine.Unity.Editor {
 
 			SkeletonData skelData = new SkeletonData();
 			BoneData data = new BoneData(0, "temp", null) {
-				ScaleX = 1,
-				ScaleY = 1,
 				Length = 100
 			};
+			var setup = data.GetSetupPose();
+			setup.ScaleX = setup.ScaleY = 1;
 
 			skelData.Bones.Add(data);
 
 			Skeleton skeleton = new Skeleton(skelData);
 
-			Bone bone = new Bone(data, skeleton, null);
-			bone.UpdateWorldTransform();
+			Bone bone = new Bone(data, null);
+			bone.AppliedPose.UpdateWorldTransform(skeleton);
 
 			DummyBone = bone;
 
 			return DummyBone;
 		}
 
+		internal static Skeleton GetDummySkeleton () {
+			SkeletonData skelData = new SkeletonData();
+			return new Skeleton(skelData);
+		}
+
 		internal static Slot GetDummySlot () {
 			if (DummySlot != null)
 				return DummySlot;
 
 			Bone bone = GetDummyBone();
-
+			Skeleton skeleton = GetDummySkeleton();
 			SlotData data = new SlotData(0, "temp", bone.Data);
-			Slot slot = new Slot(data, bone);
+			Slot slot = new Slot(data, skeleton);
 			DummySlot = slot;
 			return DummySlot;
 		}
@@ -511,11 +517,12 @@ namespace Spine.Unity.Editor {
 			Bone bone = slot.Bone;
 
 			if (centered) {
-				bone.X = -attachment.X;
-				bone.Y = -attachment.Y;
+				bone.Pose.X = -attachment.X;
+				bone.Pose.Y = -attachment.Y;
 			}
 
-			bone.UpdateWorldTransform();
+			Skeleton skeleton = GetDummySkeleton();
+			bone.AppliedPose.UpdateWorldTransform(skeleton);
 
 			float[] floatVerts = new float[8];
 			attachment.ComputeWorldVertices(slot, floatVerts, 0);
@@ -549,13 +556,14 @@ namespace Spine.Unity.Editor {
 
 		internal static Mesh ExtractMeshAttachment (string name, MeshAttachment attachment, Mesh mesh = null) {
 			Slot slot = GetDummySlot();
+			Skeleton skeleton = GetDummySkeleton();
 
-			slot.Bone.X = 0;
-			slot.Bone.Y = 0;
-			slot.Bone.UpdateWorldTransform();
+			slot.Bone.Pose.X = 0;
+			slot.Bone.Pose.Y = 0;
+			slot.Bone.AppliedPose.UpdateWorldTransform(skeleton);
 
 			float[] floatVerts = new float[attachment.WorldVerticesLength];
-			attachment.ComputeWorldVertices(slot, floatVerts);
+			attachment.ComputeWorldVertices(skeleton, slot, floatVerts);
 			Vector2[] uvs = ExtractUV(attachment.UVs);
 			Vector3[] verts = ExtractVerts(floatVerts);
 
@@ -617,15 +625,15 @@ namespace Spine.Unity.Editor {
 				throw new System.ArgumentException("Mesh is not weighted.", "attachment");
 
 			Skeleton skeleton = new Skeleton(skeletonData);
-			skeleton.UpdateWorldTransform(Skeleton.Physics.Update);
+			skeleton.UpdateWorldTransform(Physics.Update);
 
 			float[] floatVerts = new float[attachment.WorldVerticesLength];
-			attachment.ComputeWorldVertices(skeleton.Slots.Items[slotIndex], floatVerts);
+			attachment.ComputeWorldVertices(skeleton, skeleton.Slots.Items[slotIndex], floatVerts);
 			Vector2[] uvs = ExtractUV(attachment.UVs);
 			Vector3[] verts = ExtractVerts(floatVerts);
 
 			int[] triangles = attachment.Triangles;
-			Color color = new Color(attachment.R, attachment.G, attachment.B, attachment.A);
+			Color color = attachment.GetColor();
 
 			mesh = (mesh == null) ? new Mesh() : mesh;
 
@@ -768,26 +776,28 @@ namespace Spine.Unity.Editor {
 			List<int> ignoreRotateTimelineIndexes = new List<int>();
 
 			if (bakeIK) {
-				foreach (IkConstraint i in skeleton.IkConstraints) {
-					foreach (Bone b in i.Bones) {
-						ignoreRotateTimelineIndexes.Add(b.Data.Index);
-						BakeBoneConstraints(b, animation, clip);
+				var ikConstraints = skeleton.Constraints.OfType<IkConstraint>();
+				foreach (IkConstraint i in ikConstraints) {
+					foreach (BonePose b in i.Bones) {
+
+						ignoreRotateTimelineIndexes.Add(b.bone.Data.Index);
+						BakeBoneConstraints(skeleton, b.bone, animation, clip);
 					}
 				}
 			}
 
 			foreach (Bone b in skeleton.Bones) {
-				if (!b.Data.Inherit.InheritsRotation()) {
+				if (!b.Pose.Inherit.InheritsRotation()) {
 					int index = b.Data.Index;
 					if (ignoreRotateTimelineIndexes.Contains(index) == false) {
 						ignoreRotateTimelineIndexes.Add(index);
-						BakeBoneConstraints(b, animation, clip);
+						BakeBoneConstraints(skeleton, b, animation, clip);
 					}
 				}
 			}
 
 			foreach (Timeline t in timelines) {
-				skeleton.SetToSetupPose();
+				skeleton.SetupPose();
 
 				if (t is ScaleTimeline) {
 					ParseScaleTimeline(skeleton, (ScaleTimeline)t, clip);
@@ -833,19 +843,18 @@ namespace Spine.Unity.Editor {
 			return n - 1;
 		}
 
-		static void BakeBoneConstraints (Bone bone, Spine.Animation animation, AnimationClip clip) {
-			Skeleton skeleton = bone.Skeleton;
-			bool inheritRotation = bone.Data.Inherit.InheritsRotation();
+		static void BakeBoneConstraints (Skeleton skeleton, Bone bone, Spine.Animation animation, AnimationClip clip) {
+			bool inheritRotation = bone.Pose.Inherit.InheritsRotation();
 
-			animation.Apply(skeleton, 0, 0, false, null, 1f, MixBlend.Setup, MixDirection.In);
-			skeleton.UpdateWorldTransform(Skeleton.Physics.Update);
+			animation.Apply(skeleton, 0, 0, false, null, 1f, MixBlend.Setup, MixDirection.In, false);
+			skeleton.UpdateWorldTransform(Physics.Update);
 			float duration = animation.Duration;
 
 			AnimationCurve curve = new AnimationCurve();
 
 			List<Keyframe> keys = new List<Keyframe>();
 
-			float rotation = bone.AppliedRotation;
+			float rotation = bone.AppliedPose.Rotation;
 			if (!inheritRotation)
 				rotation = GetUninheritedAppliedRotation(bone);
 
@@ -865,8 +874,8 @@ namespace Spine.Unity.Editor {
 				if (i == steps)
 					currentTime = duration;
 
-				animation.Apply(skeleton, 0, currentTime, true, null, 1f, MixBlend.Setup, MixDirection.In);
-				skeleton.UpdateWorldTransform(Skeleton.Physics.Update);
+				animation.Apply(skeleton, 0, currentTime, true, null, 1f, MixBlend.Setup, MixDirection.In, false);
+				skeleton.UpdateWorldTransform(Physics.Update);
 
 				int pIndex = listIndex;
 
@@ -874,7 +883,7 @@ namespace Spine.Unity.Editor {
 
 				pk = keys[pIndex];
 
-				rotation = inheritRotation ? bone.AppliedRotation : GetUninheritedAppliedRotation(bone);
+				rotation = inheritRotation ? bone.AppliedPose.Rotation : GetUninheritedAppliedRotation(bone);
 
 				angle += Mathf.DeltaAngle(angle, rotation);
 
@@ -906,7 +915,9 @@ namespace Spine.Unity.Editor {
 
 		static void ParseTranslateTimeline (Skeleton skeleton, TranslateTimeline timeline, AnimationClip clip) {
 			BoneData boneData = skeleton.Data.Bones.Items[timeline.BoneIndex];
+			var setup = boneData.GetSetupPose();
 			Bone bone = skeleton.Bones.Items[timeline.BoneIndex];
+			var bonePose = bone.Pose;
 
 			AnimationCurve xCurve = new AnimationCurve();
 			AnimationCurve yCurve = new AnimationCurve();
@@ -919,14 +930,14 @@ namespace Spine.Unity.Editor {
 			List<Keyframe> xKeys = new List<Keyframe>();
 			List<Keyframe> yKeys = new List<Keyframe>();
 
-			xKeys.Add(new Keyframe(timeline.Frames[0], timeline.Frames[1] + boneData.X, 0, 0));
-			yKeys.Add(new Keyframe(timeline.Frames[0], timeline.Frames[2] + boneData.Y, 0, 0));
+			xKeys.Add(new Keyframe(timeline.Frames[0], timeline.Frames[1] + setup.X, 0, 0));
+			yKeys.Add(new Keyframe(timeline.Frames[0], timeline.Frames[2] + setup.Y, 0, 0));
 
 			int listIndex = 0;
 			int frameIndex = 0;
 			int f = TranslateTimeline.ENTRIES;
 			float[] frames = timeline.Frames;
-			skeleton.SetToSetupPose();
+			skeleton.SetupPose();
 			float lastTime = 0;
 			while (currentTime < endTime) {
 				int pIndex = listIndex;
@@ -938,8 +949,8 @@ namespace Spine.Unity.Editor {
 					Keyframe py = yKeys[pIndex];
 
 					float time = frames[f];
-					float x = frames[f + 1] + boneData.X;
-					float y = frames[f + 2] + boneData.Y;
+					float x = frames[f + 1] + setup.X;
+					float y = frames[f + 2] + setup.Y;
 
 					float xOut = (x - px.value) / (time - px.time);
 					float yOut = (y - py.value) / (time - py.time);
@@ -955,7 +966,7 @@ namespace Spine.Unity.Editor {
 
 					currentTime = time;
 
-					timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In);
+					timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false);
 
 					lastTime = time;
 					listIndex++;
@@ -965,8 +976,8 @@ namespace Spine.Unity.Editor {
 					Keyframe py = yKeys[pIndex];
 
 					float time = frames[f];
-					float x = frames[f + 1] + boneData.X;
-					float y = frames[f + 2] + boneData.Y;
+					float x = frames[f + 1] + setup.X;
+					float y = frames[f + 2] + setup.Y;
 
 					float xOut = float.PositiveInfinity;
 					float yOut = float.PositiveInfinity;
@@ -982,7 +993,7 @@ namespace Spine.Unity.Editor {
 
 					currentTime = time;
 
-					timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In);
+					timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false);
 
 					lastTime = time;
 					listIndex++;
@@ -1000,19 +1011,19 @@ namespace Spine.Unity.Editor {
 						if (i == steps)
 							currentTime = time;
 
-						timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In);
+						timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false);
 
 						px = xKeys[listIndex];
 						py = yKeys[listIndex];
 
-						float xOut = (bone.X - px.value) / (currentTime - px.time);
-						float yOut = (bone.Y - py.value) / (currentTime - py.time);
+						float xOut = (bonePose.X - px.value) / (currentTime - px.time);
+						float yOut = (bonePose.Y - py.value) / (currentTime - py.time);
 
 						px.outTangent = xOut;
 						py.outTangent = yOut;
 
-						xKeys.Add(new Keyframe(currentTime, bone.X, xOut, 0));
-						yKeys.Add(new Keyframe(currentTime, bone.Y, yOut, 0));
+						xKeys.Add(new Keyframe(currentTime, bonePose.X, xOut, 0));
+						yKeys.Add(new Keyframe(currentTime, bonePose.Y, yOut, 0));
 
 						xKeys[listIndex] = px;
 						yKeys[listIndex] = py;
@@ -1049,8 +1060,10 @@ namespace Spine.Unity.Editor {
 			IBoneTimeline boneTimeline = isXTimeline ? timelineX : timelineY as IBoneTimeline;
 
 			BoneData boneData = skeleton.Data.Bones.Items[boneTimeline.BoneIndex];
+			var setup = boneData.GetSetupPose();
 			Bone bone = skeleton.Bones.Items[boneTimeline.BoneIndex];
-			float boneDataOffset = isXTimeline ? boneData.X : boneData.Y;
+			var bonePose = bone.Pose;
+			float boneDataOffset = isXTimeline ? setup.X : setup.Y;
 
 			AnimationCurve curve = new AnimationCurve();
 			float endTime = timeline.Frames[(timeline.FrameCount * TranslateXTimeline.ENTRIES) - TranslateXTimeline.ENTRIES];
@@ -1062,7 +1075,7 @@ namespace Spine.Unity.Editor {
 			int frameIndex = 0;
 			int f = TranslateXTimeline.ENTRIES;
 			float[] frames = timeline.Frames;
-			skeleton.SetToSetupPose();
+			skeleton.SetupPose();
 			float lastTime = 0;
 			while (currentTime < endTime) {
 				int pIndex = listIndex;
@@ -1080,7 +1093,7 @@ namespace Spine.Unity.Editor {
 
 					keys[pIndex] = p;
 					currentTime = time;
-					timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In);
+					timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false);
 
 					lastTime = time;
 					listIndex++;
@@ -1096,7 +1109,7 @@ namespace Spine.Unity.Editor {
 
 					keys[pIndex] = p;
 					currentTime = time;
-					timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In);
+					timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false);
 
 					lastTime = time;
 					listIndex++;
@@ -1113,10 +1126,10 @@ namespace Spine.Unity.Editor {
 						if (i == steps)
 							currentTime = time;
 
-						timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In);
+						timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false);
 
 						p = keys[listIndex];
-						float boneOffset = isXTimeline ? bone.X : bone.Y;
+						float boneOffset = isXTimeline ? bonePose.X : bonePose.Y;
 						float valueOut = (boneOffset - p.value) / (currentTime - p.time);
 						p.outTangent = valueOut;
 						keys.Add(new Keyframe(currentTime, boneOffset, valueOut, 0));
@@ -1142,7 +1155,9 @@ namespace Spine.Unity.Editor {
 
 		static void ParseScaleTimeline (Skeleton skeleton, ScaleTimeline timeline, AnimationClip clip) {
 			BoneData boneData = skeleton.Data.Bones.Items[timeline.BoneIndex];
+			var setup = boneData.GetSetupPose();
 			Bone bone = skeleton.Bones.Items[timeline.BoneIndex];
+			var bonePose = bone.Pose;
 
 			AnimationCurve xCurve = new AnimationCurve();
 			AnimationCurve yCurve = new AnimationCurve();
@@ -1155,14 +1170,14 @@ namespace Spine.Unity.Editor {
 			List<Keyframe> xKeys = new List<Keyframe>();
 			List<Keyframe> yKeys = new List<Keyframe>();
 
-			xKeys.Add(new Keyframe(timeline.Frames[0], timeline.Frames[1] * boneData.ScaleX, 0, 0));
-			yKeys.Add(new Keyframe(timeline.Frames[0], timeline.Frames[2] * boneData.ScaleY, 0, 0));
+			xKeys.Add(new Keyframe(timeline.Frames[0], timeline.Frames[1] * setup.ScaleX, 0, 0));
+			yKeys.Add(new Keyframe(timeline.Frames[0], timeline.Frames[2] * setup.ScaleY, 0, 0));
 
 			int listIndex = 0;
 			int frameIndex = 0;
 			int f = ScaleTimeline.ENTRIES;
 			float[] frames = timeline.Frames;
-			skeleton.SetToSetupPose();
+			skeleton.SetupPose();
 			float lastTime = 0;
 			while (currentTime < endTime) {
 				int pIndex = listIndex;
@@ -1173,8 +1188,8 @@ namespace Spine.Unity.Editor {
 					Keyframe py = yKeys[pIndex];
 
 					float time = frames[f];
-					float x = frames[f + 1] * boneData.ScaleX;
-					float y = frames[f + 2] * boneData.ScaleY;
+					float x = frames[f + 1] * setup.ScaleX;
+					float y = frames[f + 2] * setup.ScaleY;
 
 					float xOut = (x - px.value) / (time - px.time);
 					float yOut = (y - py.value) / (time - py.time);
@@ -1190,7 +1205,7 @@ namespace Spine.Unity.Editor {
 
 					currentTime = time;
 
-					timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In);
+					timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false);
 
 					lastTime = time;
 					listIndex++;
@@ -1200,8 +1215,8 @@ namespace Spine.Unity.Editor {
 					Keyframe py = yKeys[pIndex];
 
 					float time = frames[f];
-					float x = frames[f + 1] * boneData.ScaleX;
-					float y = frames[f + 2] * boneData.ScaleY;
+					float x = frames[f + 1] * setup.ScaleX;
+					float y = frames[f + 2] * setup.ScaleY;
 
 					float xOut = float.PositiveInfinity;
 					float yOut = float.PositiveInfinity;
@@ -1217,7 +1232,7 @@ namespace Spine.Unity.Editor {
 
 					currentTime = time;
 
-					timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In);
+					timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false);
 
 					lastTime = time;
 					listIndex++;
@@ -1235,19 +1250,19 @@ namespace Spine.Unity.Editor {
 						if (i == steps)
 							currentTime = time;
 
-						timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In);
+						timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false);
 
 						px = xKeys[listIndex];
 						py = yKeys[listIndex];
 
-						float xOut = (bone.ScaleX - px.value) / (currentTime - px.time);
-						float yOut = (bone.ScaleY - py.value) / (currentTime - py.time);
+						float xOut = (bonePose.ScaleX - px.value) / (currentTime - px.time);
+						float yOut = (bonePose.ScaleY - py.value) / (currentTime - py.time);
 
 						px.outTangent = xOut;
 						py.outTangent = yOut;
 
-						xKeys.Add(new Keyframe(currentTime, bone.ScaleX, xOut, 0));
-						yKeys.Add(new Keyframe(currentTime, bone.ScaleY, yOut, 0));
+						xKeys.Add(new Keyframe(currentTime, bonePose.ScaleX, xOut, 0));
+						yKeys.Add(new Keyframe(currentTime, bonePose.ScaleY, yOut, 0));
 
 						xKeys[listIndex] = px;
 						yKeys[listIndex] = py;
@@ -1280,8 +1295,10 @@ namespace Spine.Unity.Editor {
 			IBoneTimeline boneTimeline = isXTimeline ? timelineX : timelineY as IBoneTimeline;
 
 			BoneData boneData = skeleton.Data.Bones.Items[boneTimeline.BoneIndex];
+			var setup = boneData.GetSetupPose();
 			Bone bone = skeleton.Bones.Items[boneTimeline.BoneIndex];
-			float boneDataOffset = isXTimeline ? boneData.ScaleX : boneData.ScaleY;
+			var bonePose = bone.Pose;
+			float boneDataOffset = isXTimeline ? setup.ScaleX : setup.ScaleY;
 
 			AnimationCurve curve = new AnimationCurve();
 			float endTime = timeline.Frames[(timeline.FrameCount * ScaleXTimeline.ENTRIES) - ScaleXTimeline.ENTRIES];
@@ -1293,7 +1310,7 @@ namespace Spine.Unity.Editor {
 			int frameIndex = 0;
 			int f = ScaleXTimeline.ENTRIES;
 			float[] frames = timeline.Frames;
-			skeleton.SetToSetupPose();
+			skeleton.SetupPose();
 			float lastTime = 0;
 			while (currentTime < endTime) {
 				int pIndex = listIndex;
@@ -1310,7 +1327,7 @@ namespace Spine.Unity.Editor {
 
 					keys[pIndex] = p;
 					currentTime = time;
-					timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In);
+					timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false);
 
 					lastTime = time;
 					listIndex++;
@@ -1326,7 +1343,7 @@ namespace Spine.Unity.Editor {
 
 					keys[pIndex] = p;
 					currentTime = time;
-					timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In);
+					timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false);
 
 					lastTime = time;
 					listIndex++;
@@ -1341,11 +1358,11 @@ namespace Spine.Unity.Editor {
 						if (i == steps)
 							currentTime = time;
 
-						timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In);
+						timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false);
 
 						p = keys[listIndex];
 
-						float boneScale = isXTimeline ? bone.ScaleX : bone.ScaleY;
+						float boneScale = isXTimeline ? bonePose.ScaleX : bonePose.ScaleY;
 						float valueOut = (boneScale - p.value) / (currentTime - p.time);
 						p.outTangent = valueOut;
 						keys.Add(new Keyframe(currentTime, boneScale, valueOut, 0));
@@ -1371,7 +1388,9 @@ namespace Spine.Unity.Editor {
 
 		static void ParseRotateTimeline (Skeleton skeleton, RotateTimeline timeline, AnimationClip clip) {
 			BoneData boneData = skeleton.Data.Bones.Items[timeline.BoneIndex];
+			var setup = boneData.GetSetupPose();
 			Bone bone = skeleton.Bones.Items[timeline.BoneIndex];
+			var bonePose = bone.Pose;
 
 			AnimationCurve curve = new AnimationCurve();
 
@@ -1381,7 +1400,7 @@ namespace Spine.Unity.Editor {
 
 			List<Keyframe> keys = new List<Keyframe>();
 
-			float rotation = timeline.Frames[1] + boneData.Rotation;
+			float rotation = timeline.Frames[1] + setup.Rotation;
 
 			keys.Add(new Keyframe(timeline.Frames[0], rotation, 0, 0));
 
@@ -1389,7 +1408,7 @@ namespace Spine.Unity.Editor {
 			int frameIndex = 0;
 			int f = 2;
 			float[] frames = timeline.Frames;
-			skeleton.SetToSetupPose();
+			skeleton.SetupPose();
 			float lastTime = 0;
 			float angle = rotation;
 			while (currentTime < endTime) {
@@ -1402,7 +1421,7 @@ namespace Spine.Unity.Editor {
 
 					float time = frames[f];
 
-					rotation = frames[f + 1] + boneData.Rotation;
+					rotation = frames[f + 1] + setup.Rotation;
 					angle += Mathf.DeltaAngle(angle, rotation);
 					float r = angle;
 
@@ -1416,7 +1435,7 @@ namespace Spine.Unity.Editor {
 
 					currentTime = time;
 
-					timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In);
+					timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false);
 
 					lastTime = time;
 					listIndex++;
@@ -1427,7 +1446,7 @@ namespace Spine.Unity.Editor {
 
 					float time = frames[f];
 
-					rotation = frames[f + 1] + boneData.Rotation;
+					rotation = frames[f + 1] + setup.Rotation;
 					angle += Mathf.DeltaAngle(angle, rotation);
 					float r = angle;
 
@@ -1441,7 +1460,7 @@ namespace Spine.Unity.Editor {
 
 					currentTime = time;
 
-					timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In);
+					timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false);
 
 					lastTime = time;
 					listIndex++;
@@ -1451,10 +1470,10 @@ namespace Spine.Unity.Editor {
 
 					float time = frames[f];
 
-					timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In);
-					skeleton.UpdateWorldTransform(Skeleton.Physics.Update);
+					timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false);
+					skeleton.UpdateWorldTransform(Physics.Update);
 
-					rotation = frames[f + 1] + boneData.Rotation;
+					rotation = frames[f + 1] + setup.Rotation;
 					angle += Mathf.DeltaAngle(angle, rotation);
 					float r = angle;
 
@@ -1465,11 +1484,11 @@ namespace Spine.Unity.Editor {
 						if (i == steps)
 							currentTime = time;
 
-						timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In);
-						skeleton.UpdateWorldTransform(Skeleton.Physics.Update);
+						timeline.Apply(skeleton, lastTime, currentTime, null, 1, MixBlend.Setup, MixDirection.In, false);
+						skeleton.UpdateWorldTransform(Physics.Update);
 						pk = keys[listIndex];
 
-						rotation = bone.Rotation;
+						rotation = bonePose.Rotation;
 						angle += Mathf.DeltaAngle(angle, rotation);
 						r = angle;
 
@@ -1621,10 +1640,10 @@ namespace Spine.Unity.Editor {
 
 		static float GetUninheritedAppliedRotation (Bone b) {
 			Bone parent = b.Parent;
-			float angle = b.AppliedRotation;
+			float angle = b.AppliedPose.Rotation;
 
 			while (parent != null) {
-				angle -= parent.AppliedRotation;
+				angle -= parent.AppliedPose.Rotation;
 				parent = parent.Parent;
 			}
 

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

@@ -36,6 +36,7 @@
 #endif
 
 using System.Collections.Generic;
+using System.Linq;
 using UnityEditor;
 using UnityEditor.AnimatedValues;
 using UnityEngine;
@@ -218,8 +219,8 @@ namespace Spine.Unity.Editor {
 			scrollPos = EditorGUILayout.BeginScrollView(scrollPos);
 
 			using (new SpineInspectorUtility.BoxScope(false)) {
-				if (SpineInspectorUtility.CenteredButton(SpineInspectorUtility.TempContent("Skeleton.SetToSetupPose()"))) {
-					skeleton.SetToSetupPose();
+				if (SpineInspectorUtility.CenteredButton(SpineInspectorUtility.TempContent("Skeleton.SetupPose()"))) {
+					skeleton.SetupPose();
 					requireRepaint = true;
 				}
 
@@ -277,10 +278,12 @@ namespace Spine.Unity.Editor {
 									using (new EditorGUI.DisabledGroupScope(true)) {
 										bool wm = EditorGUIUtility.wideMode;
 										EditorGUIUtility.wideMode = true;
-										EditorGUILayout.Slider("Local Rotation", ViewRound(bone.Rotation), -180f, 180f);
-										EditorGUILayout.Vector2Field("Local Position", RoundVector2(bone.X, bone.Y));
-										EditorGUILayout.Vector2Field("Local Scale", RoundVector2(bone.ScaleX, bone.ScaleY));
-										EditorGUILayout.Vector2Field("Local Shear", RoundVector2(bone.ShearX, bone.ShearY));
+										var bonePose = bone.Pose;
+										var appliedPose = bone.AppliedPose;
+										EditorGUILayout.Slider("Local Rotation", ViewRound(bonePose.Rotation), -180f, 180f);
+										EditorGUILayout.Vector2Field("Local Position", RoundVector2(bonePose.X, bonePose.Y));
+										EditorGUILayout.Vector2Field("Local Scale", RoundVector2(bonePose.ScaleX, bonePose.ScaleY));
+										EditorGUILayout.Vector2Field("Local Shear", RoundVector2(bonePose.ShearX, bonePose.ShearY));
 
 										EditorGUILayout.Space();
 
@@ -296,21 +299,21 @@ namespace Spine.Unity.Editor {
 
 										EditorGUILayout.BeginHorizontal();
 										EditorGUILayout.Space();
-										EditorGUILayout.TextField(".A", bone.A.ToString(RoundFormat));
-										EditorGUILayout.TextField(".B", bone.B.ToString(RoundFormat));
+										EditorGUILayout.TextField(".A", appliedPose.A.ToString(RoundFormat));
+										EditorGUILayout.TextField(".B", appliedPose.B.ToString(RoundFormat));
 										EditorGUILayout.EndHorizontal();
 										EditorGUILayout.BeginHorizontal();
 										EditorGUILayout.Space();
-										EditorGUILayout.TextField(".C", bone.C.ToString(RoundFormat));
-										EditorGUILayout.TextField(".D", bone.D.ToString(RoundFormat));
+										EditorGUILayout.TextField(".C", appliedPose.C.ToString(RoundFormat));
+										EditorGUILayout.TextField(".D", appliedPose.D.ToString(RoundFormat));
 										EditorGUILayout.EndHorizontal();
 
 										EditorGUIUtility.labelWidth = lw * 0.5f;
 										EditorGUILayout.BeginHorizontal();
 										EditorGUILayout.Space();
 										EditorGUILayout.Space();
-										EditorGUILayout.TextField(".WorldX", bone.WorldX.ToString(RoundFormat));
-										EditorGUILayout.TextField(".WorldY", bone.WorldY.ToString(RoundFormat));
+										EditorGUILayout.TextField(".WorldX", appliedPose.WorldX.ToString(RoundFormat));
+										EditorGUILayout.TextField(".WorldY", appliedPose.WorldY.ToString(RoundFormat));
 										EditorGUILayout.EndHorizontal();
 
 										EditorGUIUtility.labelWidth = lw;
@@ -332,8 +335,8 @@ namespace Spine.Unity.Editor {
 				showSlotsTree.target = EditorGUILayout.Foldout(showSlotsTree.target, SlotsRootLabel, BoldFoldoutStyle);
 				if (showSlotsTree.faded > 0) {
 					using (new EditorGUILayout.FadeGroupScope(showSlotsTree.faded)) {
-						if (SpineInspectorUtility.CenteredButton(SpineInspectorUtility.TempContent("Skeleton.SetSlotsToSetupPose()"))) {
-							skeleton.SetSlotsToSetupPose();
+						if (SpineInspectorUtility.CenteredButton(SpineInspectorUtility.TempContent("Skeleton.SetupPoseSlots()"))) {
+							skeleton.SetupPose();
 							requireRepaint = true;
 						}
 
@@ -345,7 +348,7 @@ namespace Spine.Unity.Editor {
 								EditorGUI.indentLevel = baseIndent + 1;
 								EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(slot.Data.Name, Icons.slot), GUILayout.ExpandWidth(false));
 								EditorGUI.BeginChangeCheck();
-								Color c = EditorGUILayout.ColorField(new Color(slot.R, slot.G, slot.B, slot.A), GUILayout.Width(60));
+								Color c = EditorGUILayout.ColorField(slot.GetColor(), GUILayout.Width(60));
 								if (EditorGUI.EndChangeCheck()) {
 									slot.SetColor(c);
 									requireRepaint = true;
@@ -354,13 +357,14 @@ namespace Spine.Unity.Editor {
 
 							foreach (Skin.SkinEntry skinEntry in pair.Value) {
 								Attachment attachment = skinEntry.Attachment;
-								GUI.contentColor = slot.Attachment == attachment ? Color.white : Color.grey;
+								var slotPose = slot.AppliedPose; 
+								GUI.contentColor = slotPose.Attachment == attachment ? Color.white : Color.grey;
 								EditorGUI.indentLevel = baseIndent + 2;
 								Texture2D icon = Icons.GetAttachmentIcon(attachment);
-								bool isAttached = (attachment == slot.Attachment);
-								bool swap = EditorGUILayout.ToggleLeft(SpineInspectorUtility.TempContent(attachment.Name, icon), attachment == slot.Attachment);
+								bool isAttached = (attachment == slotPose.Attachment);
+								bool swap = EditorGUILayout.ToggleLeft(SpineInspectorUtility.TempContent(attachment.Name, icon), attachment == slotPose.Attachment);
 								if (isAttached != swap) {
-									slot.Attachment = isAttached ? null : attachment;
+									slotPose.Attachment = isAttached ? null : attachment;
 									requireRepaint = true;
 								}
 								GUI.contentColor = Color.white;
@@ -371,6 +375,13 @@ namespace Spine.Unity.Editor {
 				EditorGUI.indentLevel = preSlotsIndent;
 
 				// Constraints
+				var ikConstraints = skeleton.Constraints.OfType<IkConstraint>();
+				int ikConstraintsCount = ikConstraints.Count();
+				var transformConstraints = skeleton.Constraints.OfType<TransformConstraint>();
+				int transformConstraintsCount = transformConstraints.Count();
+				var pathConstraints = skeleton.Constraints.OfType<PathConstraint>();
+				int pathConstraintsCount = pathConstraints.Count();
+
 				const string NoneText = "<none>";
 				showConstraintsTree.target = EditorGUILayout.Foldout(showConstraintsTree.target, SpineInspectorUtility.TempContent("Constraints", Icons.constraintRoot), BoldFoldoutStyle);
 				if (showConstraintsTree.faded > 0) {
@@ -384,14 +395,15 @@ namespace Spine.Unity.Editor {
 
 							EditorGUILayout.Space();
 
-							EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(string.Format("IK Constraints ({0})", skeleton.IkConstraints.Count), Icons.constraintIK), EditorStyles.boldLabel);
+							EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(string.Format("IK Constraints ({0})", ikConstraintsCount), Icons.constraintIK), EditorStyles.boldLabel);
 							using (new SpineInspectorUtility.IndentScope()) {
-								if (skeleton.IkConstraints.Count > 0) {
-									foreach (IkConstraint c in skeleton.IkConstraints) {
-										EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(c.Data.Name, Icons.constraintIK));
-										FalseDropDown("Goal", c.Data.Target.Name, Icons.bone, true);
+								if (ikConstraintsCount > 0) {
+									foreach (IkConstraint ikConstraint in ikConstraints) {
+										var c = ikConstraint.AppliedPose;
+										EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(ikConstraint.Data.Name, Icons.constraintIK));
+										FalseDropDown("Goal", ikConstraint.Target.Data.Name, Icons.bone, true);
 										using (new EditorGUI.DisabledGroupScope(true)) {
-											EditorGUILayout.Toggle(SpineInspectorUtility.TempContent("Data.Uniform", tooltip: "Uniformly scales a bone when Ik stretches or compresses."), c.Data.Uniform);
+											EditorGUILayout.Toggle(SpineInspectorUtility.TempContent("Data.Uniform", tooltip: "Uniformly scales a bone when Ik stretches or compresses."), ikConstraint.Data.Uniform);
 										}
 
 										EditorGUI.BeginChangeCheck();
@@ -409,13 +421,14 @@ namespace Spine.Unity.Editor {
 								}
 							}
 
-							EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(string.Format("Transform Constraints ({0})", skeleton.TransformConstraints.Count), Icons.constraintTransform), EditorStyles.boldLabel);
+							EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(string.Format("Transform Constraints ({0})", transformConstraintsCount), Icons.constraintTransform), EditorStyles.boldLabel);
 							using (new SpineInspectorUtility.IndentScope()) {
-								if (skeleton.TransformConstraints.Count > 0) {
-									foreach (TransformConstraint c in skeleton.TransformConstraints) {
-										EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(c.Data.Name, Icons.constraintTransform));
+								if (transformConstraintsCount > 0) {
+									foreach (TransformConstraint transformConstraint in transformConstraints) {
+										var c = transformConstraint.AppliedPose;
+										EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(transformConstraint.Data.Name, Icons.constraintTransform));
 										EditorGUI.BeginDisabledGroup(true);
-										FalseDropDown("Source", c.Data.Source.Name, Icons.bone);
+										FalseDropDown("Source", transformConstraint.Source.Data.Name, Icons.bone);
 										EditorGUI.EndDisabledGroup();
 
 										EditorGUI.BeginChangeCheck();
@@ -434,13 +447,14 @@ namespace Spine.Unity.Editor {
 								}
 							}
 
-							EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(string.Format("Path Constraints ({0})", skeleton.PathConstraints.Count), Icons.constraintPath), EditorStyles.boldLabel);
+							EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(string.Format("Path Constraints ({0})", pathConstraintsCount), Icons.constraintPath), EditorStyles.boldLabel);
 
 							EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(string.Format("Physics Constraints ({0})", skeleton.PhysicsConstraints.Count), Icons.constraintIK), EditorStyles.boldLabel);
 							using (new SpineInspectorUtility.IndentScope()) {
 								if (skeleton.PhysicsConstraints.Count > 0) {
-									foreach (PhysicsConstraint c in skeleton.PhysicsConstraints) {
-										EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(c.Data.Name, Icons.constraintIK));
+									foreach (PhysicsConstraint physicsConstraint in skeleton.PhysicsConstraints) {
+										var c = physicsConstraint.AppliedPose;
+										EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(physicsConstraint.Data.Name, Icons.constraintIK));
 
 										EditorGUI.BeginChangeCheck();
 										c.Mix = EditorGUILayout.Slider("Mix", c.Mix, MixMin, MixMax);
@@ -465,16 +479,18 @@ namespace Spine.Unity.Editor {
 							requireRepaint |= EditorGUI.EndChangeCheck();
 
 							using (new SpineInspectorUtility.IndentScope()) {
-								if (skeleton.PathConstraints.Count > 0) {
-									foreach (PathConstraint c in skeleton.PathConstraints) {
-										EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(c.Data.Name, Icons.constraintPath));
+								if (pathConstraintsCount > 0) {
+									foreach (PathConstraint pathConstraint in pathConstraints) {
+										var c = pathConstraint.AppliedPose;
+										var data = pathConstraint.Data;
+										EditorGUILayout.LabelField(SpineInspectorUtility.TempContent(pathConstraint.Data.Name, Icons.constraintPath));
 										EditorGUI.BeginDisabledGroup(true);
-										FalseDropDown("Path Slot", c.Data.Slot.Name, Icons.slot);
-										Attachment activeAttachment = c.Target.Attachment;
+										FalseDropDown("Path Slot", pathConstraint.Slot.Data.Name, Icons.slot);
+										Attachment activeAttachment = pathConstraint.Slot.AppliedPose.Attachment;
 										FalseDropDown("Active Path", activeAttachment != null ? activeAttachment.Name : "<None>", activeAttachment is PathAttachment ? Icons.path : null);
-										EditorGUILayout.LabelField("PositionMode." + c.Data.PositionMode);
-										EditorGUILayout.LabelField("SpacingMode." + c.Data.SpacingMode);
-										EditorGUILayout.LabelField("RotateMode." + c.Data.RotateMode);
+										EditorGUILayout.LabelField("PositionMode." + data.PositionMode);
+										EditorGUILayout.LabelField("SpacingMode." + data.SpacingMode);
+										EditorGUILayout.LabelField("RotateMode." + data.RotateMode);
 										EditorGUI.EndDisabledGroup();
 
 										EditorGUI.BeginChangeCheck();
@@ -555,9 +571,9 @@ namespace Spine.Unity.Editor {
 								EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("Slots", Icons.slotRoot, "Skeleton.Data.Slots"), new GUIContent(skeletonData.Slots.Count.ToString()));
 								EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("Skins", Icons.skinsRoot, "Skeleton.Data.Skins"), new GUIContent(skeletonData.Skins.Count.ToString()));
 								EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("Events", Icons.userEvent, "Skeleton.Data.Events"), new GUIContent(skeletonData.Events.Count.ToString()));
-								EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("IK Constraints", Icons.constraintIK, "Skeleton.Data.IkConstraints"), new GUIContent(skeletonData.IkConstraints.Count.ToString()));
-								EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("Transform Constraints", Icons.constraintTransform, "Skeleton.Data.TransformConstraints"), new GUIContent(skeletonData.TransformConstraints.Count.ToString()));
-								EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("Path Constraints", Icons.constraintPath, "Skeleton.Data.PathConstraints"), new GUIContent(skeletonData.PathConstraints.Count.ToString()));
+								EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("IK Constraints", Icons.constraintIK, "Skeleton.Data.IkConstraints"), new GUIContent(ikConstraintsCount.ToString()));
+								EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("Transform Constraints", Icons.constraintTransform, "Skeleton.Data.TransformConstraints"), new GUIContent(transformConstraintsCount.ToString()));
+								EditorGUILayout.LabelField(SpineInspectorUtility.TempContent("Path Constraints", Icons.constraintPath, "Skeleton.Data.PathConstraints"), new GUIContent(pathConstraintsCount.ToString()));
 							}
 						}
 					}

+ 14 - 14
spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoneFollower.cs

@@ -147,10 +147,10 @@ namespace Spine.Unity {
 			if (!Application.isPlaying)
 				skeletonTransformIsParent = Transform.ReferenceEquals(skeletonTransform, transform.parent);
 #endif
-
+			Skeleton skeleton = skeletonRenderer.skeleton;
 			if (bone == null) {
 				if (string.IsNullOrEmpty(boneName)) return;
-				bone = skeletonRenderer.skeleton.FindBone(boneName);
+				bone = skeleton.FindBone(boneName);
 				if (!SetBone(boneName)) return;
 			}
 
@@ -167,29 +167,29 @@ namespace Spine.Unity {
 					float cumulativeScaleY = 1.0f;
 					Bone p = parentBone;
 					while (p != null) {
-						cumulativeScaleX *= p.ScaleX;
-						cumulativeScaleY *= p.ScaleY;
+						cumulativeScaleX *= p.AppliedPose.ScaleX;
+						cumulativeScaleY *= p.AppliedPose.ScaleY;
 						p = p.Parent;
 					};
 					scaleSignX = Mathf.Sign(cumulativeScaleX);
 					scaleSignY = Mathf.Sign(cumulativeScaleY);
-					localScale = new Vector3(parentBone.WorldScaleX * scaleSignX, parentBone.WorldScaleY * scaleSignY, 1f);
+					localScale = new Vector3(parentBone.AppliedPose.WorldScaleX * scaleSignX, parentBone.AppliedPose.WorldScaleY * scaleSignY, 1f);
 				}
 				if (followLocalScale)
-					localScale.Scale(new Vector3(bone.ScaleX, bone.ScaleY, 1f));
+					localScale.Scale(new Vector3(bone.AppliedPose.ScaleX, bone.AppliedPose.ScaleY, 1f));
 				if (followSkeletonFlip)
-					localScale.y *= Mathf.Sign(bone.Skeleton.ScaleX * bone.Skeleton.ScaleY) * additionalFlipScale;
+					localScale.y *= Mathf.Sign(skeleton.ScaleX * skeleton.ScaleY) * additionalFlipScale;
 				thisTransform.localScale = localScale;
 			}
 
 			if (skeletonTransformIsParent) {
 				// Recommended setup: Use local transform properties if Spine GameObject is the immediate parent
-				thisTransform.localPosition = new Vector3(followXYPosition ? bone.WorldX : thisTransform.localPosition.x,
-														followXYPosition ? bone.WorldY : thisTransform.localPosition.y,
+				thisTransform.localPosition = new Vector3(followXYPosition ? bone.AppliedPose.WorldX : thisTransform.localPosition.x,
+														followXYPosition ? bone.AppliedPose.WorldY : thisTransform.localPosition.y,
 														followZPosition ? 0f : thisTransform.localPosition.z);
 				if (followBoneRotation) {
-					float halfRotation = Mathf.Atan2(bone.C, bone.A) * 0.5f;
-					if (followLocalScale && bone.ScaleX < 0) // Negate rotation from negative scaleX. Don't use negative determinant. local scaleY doesn't factor into used rotation.
+					float halfRotation = Mathf.Atan2(bone.AppliedPose.C, bone.AppliedPose.A) * 0.5f;
+					if (followLocalScale && bone.AppliedPose.ScaleX < 0) // Negate rotation from negative scaleX. Don't use negative determinant. local scaleY doesn't factor into used rotation.
 						halfRotation += Mathf.PI * 0.5f;
 					if (followParentWorldScale && scaleSignX < 0)
 						halfRotation += Mathf.PI * 0.5f;
@@ -201,7 +201,7 @@ namespace Spine.Unity {
 				}
 			} else {
 				// For special cases: Use transform world properties if transform relationship is complicated
-				Vector3 targetWorldPosition = skeletonTransform.TransformPoint(new Vector3(bone.WorldX, bone.WorldY, 0f));
+				Vector3 targetWorldPosition = skeletonTransform.TransformPoint(new Vector3(bone.AppliedPose.WorldX, bone.AppliedPose.WorldY, 0f));
 				if (!followZPosition) targetWorldPosition.z = thisTransform.position.z;
 				if (!followXYPosition) {
 					targetWorldPosition.x = thisTransform.position.x;
@@ -212,7 +212,7 @@ namespace Spine.Unity {
 				Transform transformParent = thisTransform.parent;
 				Vector3 parentLossyScale = transformParent != null ? transformParent.lossyScale : Vector3.one;
 				if (followBoneRotation) {
-					float boneWorldRotation = bone.WorldRotationX;
+					float boneWorldRotation = bone.AppliedPose.WorldRotationX;
 
 					if ((skeletonLossyScale.x * skeletonLossyScale.y) < 0)
 						boneWorldRotation = -boneWorldRotation;
@@ -228,7 +228,7 @@ namespace Spine.Unity {
 						boneWorldRotation += 180f;
 
 					Vector3 worldRotation = skeletonTransform.rotation.eulerAngles;
-					if (followLocalScale && bone.ScaleX < 0) boneWorldRotation += 180f;
+					if (followLocalScale && bone.AppliedPose.ScaleX < 0) boneWorldRotation += 180f;
 					thisTransform.SetPositionAndRotation(targetWorldPosition, Quaternion.Euler(worldRotation.x, worldRotation.y, worldRotation.z + boneWorldRotation));
 				} else {
 					thisTransform.position = targetWorldPosition;

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.