Bläddra i källkod

[csharp][monogame][unity][xna] Updated to 3.3.x (#623)

* [spine-csharp] Ported 3.3 changes of Animation and inner classes. Added MathUtils.Clamp

* [spine-csharp] Ported 3.3 changes of AnimationStateData. Updated .gitignore to exclude .meta files from spine-csharp

* [spine-csharp] Ported 3.3 changes of Bone

* [spine-csharp] Ported 3.3 changes of BoneData

* [spine-csharp] Ported 3.3 changes of Event

* [spine-csharp] Ported 3.3 changes to IKConstraint. Also got rid of the hideous labeled break. Replaced with while and sprinkled continues and break :D

* [spine-csharp] Mario is not good with labeled breaks. Fixed with goto

* [spine-csharp] Ported more 3.3 changes, only SkeletonJson and SkeletonBinary left. Unity runtime also needs updating with new changes

* [spine-csharp] fixed compilation errors initially not reported by Mono CS

* Minor clean up.

Every space has its place.™

* Match csharp 3.3 refactorings.

* [Unity] Minor cleanup in SkeletonBaker.

* Better exception messages.

* Removed extra usings. Matched whitespace style.

* Fixed Bone.cs method PascalCase and xml documentation.

* Fixed Exception ctor arguments.

* Fixed single bone IK mixing.

Matched libgdx commit:
https://github.com/EsotericSoftware/spine-runtimes/commit/e0ee18a0880cf48f4f8cb68e381de6d7e2fd8378

* [spine-csharp] Port of 3.3 changes to SkeletonJson, incomplete

* [Unity] Ragdoll: account for shear and new constraints.

* [spine-csharp] Ported 3.3 changes to SkeletonJson

* [spine-csharp] Ported 3.3 changes to SkeletonBinary. Time for testing and debugging

* [spine-csharp] Fixed up XNA runtime

* Added simple example data in new exports/ folder

* [spine-csharp] Fixed bug in Slot construtor, wasn't assigning index. Fixed bug in CurveTimeline, argument guard was wrong. Fixed bugs in SkeletonJson, indexing to get duration of a timeline was wrong. Added simple example to spine-xna for easier debugging

* [spine-csharp] Fixed porting bug in ColorTimeline#apply, indexing was wrong

* [spine-csharp] Fixed SkeletonJson#ReadVertices, calling resize on temp lists was wrong. Updated goblin sample in spine-xna, fixed mesh attachment rendering in spine-xna.

* [spine-xna] Added binary exports, modified XNA game

* [spine-xna] Added tank example

* Some cleanup. Relabeled generic todos.

* Prevent SpineEditorUtilities from orphaning failed instantiations.

* Ignore PathAttachment when checking for required atlas regions.

* [spine-csharp] the great spaces to tabs battle

* [spine-csharp] Fixed inherit deform.

* [spine-csharp] Match Skeleton.java properties and stuff.

* [spine-csharp] Fixed enums, all upper cased now, using Enum.ParseType in case-insensitive mode

* [csharp] Ported 206e7f983c4df4d27fee6cac05d152eb2295c8b0 to csharp runtime. Fixes attachment keys on different tracks

* [csharp] Updated README.md

* [xna] [monogame] [unity] Updated README.md

* Revert "[csharp] Ported 206e7f983c4df4d27fee6cac05d152eb2295c8b0 to csharp runtime. Fixes attachment keys on different tracks"

This reverts commit 175216868dd00b4ae31cc717022242308c150f6a.

* [csharp] Fix to AttachmentTimeline#apply, fix for the fix for the fix for the revert for the fix

* [csharp] Matched and fixed more comments, summaries, exception messages and formatting.

* [csharp] Fix deformed weighted vertices condition + match libgdx closer.

* [csharp] Use internal ExposedList array for critical methods.

* [csharp] SkeletonJson and SkeletonBinary minor formatting and fixes.

* [unity] Match changes and fixes in spine-csharp 3.3 + better editor messages.

* [unity] Updated sample scenes and files.

* [csharp] Some formatting got left behind.

* [unity] New readme links + Removed redundant info.

* [exports] Remove dummy project.
John 9 år sedan
förälder
incheckning
1c19365325
100 ändrade filer med 3327 tillägg och 1296 borttagningar
  1. 3 0
      .gitignore
  2. 2 2
      spine-csharp/README.md
  3. 4 2
      spine-csharp/spine-csharp.csproj
  4. 270 165
      spine-csharp/src/Animation.cs
  5. 10 6
      spine-csharp/src/AnimationState.cs
  6. 3 2
      spine-csharp/src/AnimationStateData.cs
  7. 5 20
      spine-csharp/src/Attachments/AtlasAttachmentLoader.cs
  8. 1 1
      spine-csharp/src/Attachments/Attachment.cs
  9. 3 3
      spine-csharp/src/Attachments/AttachmentLoader.cs
  10. 1 1
      spine-csharp/src/Attachments/AttachmentType.cs
  11. 1 21
      spine-csharp/src/Attachments/BoundingBoxAttachment.cs
  12. 10 24
      spine-csharp/src/Attachments/MeshAttachment.cs
  13. 16 3
      spine-csharp/src/Attachments/PathAttachment.cs
  14. 14 13
      spine-csharp/src/Attachments/RegionAttachment.cs
  15. 114 0
      spine-csharp/src/Attachments/VertexAttachment.cs
  16. 0 158
      spine-csharp/src/Attachments/WeightedMeshAttachment.cs
  17. 96 27
      spine-csharp/src/Bone.cs
  18. 12 7
      spine-csharp/src/BoneData.cs
  19. 1 0
      spine-csharp/src/Event.cs
  20. 1 1
      spine-csharp/src/EventData.cs
  21. 7 1
      spine-csharp/src/ExposedList.cs
  22. 56 39
      spine-csharp/src/IkConstraint.cs
  23. 1 1
      spine-csharp/src/IkConstraintData.cs
  24. 12 6
      spine-csharp/src/MathUtils.cs
  25. 400 0
      spine-csharp/src/PathConstraint.cs
  26. 74 0
      spine-csharp/src/PathConstraintData.cs
  27. 202 64
      spine-csharp/src/Skeleton.cs
  28. 235 198
      spine-csharp/src/SkeletonBinary.cs
  29. 1 2
      spine-csharp/src/SkeletonBounds.cs
  30. 35 11
      spine-csharp/src/SkeletonData.cs
  31. 353 291
      spine-csharp/src/SkeletonJson.cs
  32. 6 5
      spine-csharp/src/Skin.cs
  33. 10 22
      spine-csharp/src/Slot.cs
  34. 7 3
      spine-csharp/src/SlotData.cs
  35. 58 65
      spine-csharp/src/TransformConstraint.cs
  36. 4 4
      spine-csharp/src/TransformConstraintData.cs
  37. 4 4
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java
  38. 1 1
      spine-monogame/README.md
  39. BIN
      spine-unity/Assets/Examples/Getting Started/1 The Spine GameObject.unity
  40. BIN
      spine-unity/Assets/Examples/Getting Started/2 Controlling Animation.unity
  41. BIN
      spine-unity/Assets/Examples/Getting Started/3 Controlling Animation Continued.unity
  42. BIN
      spine-unity/Assets/Examples/Getting Started/4 Object Oriented Sample.unity
  43. BIN
      spine-unity/Assets/Examples/Getting Started/5 Basic Platformer.unity
  44. BIN
      spine-unity/Assets/Examples/Getting Started/6 SkeletonGraphic.unity
  45. 20 2
      spine-unity/Assets/Examples/Getting Started/Scripts/SpineboyBeginnerModel.cs
  46. BIN
      spine-unity/Assets/Examples/Other Examples/AtlasRegionAttacher.unity
  47. BIN
      spine-unity/Assets/Examples/Other Examples/Dragon.unity
  48. BIN
      spine-unity/Assets/Examples/Other Examples/Goblins.unity
  49. BIN
      spine-unity/Assets/Examples/Other Examples/Mix and Match.unity
  50. BIN
      spine-unity/Assets/Examples/Other Examples/SkeletonRenderSeparator.unity
  51. BIN
      spine-unity/Assets/Examples/Other Examples/SkeletonUtility Animated Physics.unity
  52. BIN
      spine-unity/Assets/Examples/Other Examples/SkeletonUtility Eyes.unity
  53. BIN
      spine-unity/Assets/Examples/Other Examples/SkeletonUtility GroundConstraint.unity
  54. BIN
      spine-unity/Assets/Examples/Other Examples/SkeletonUtility Ragdoll.unity
  55. BIN
      spine-unity/Assets/Examples/Other Examples/SpineGauge.unity
  56. 0 0
      spine-unity/Assets/Examples/Spine/Dragon/dragon.json
  57. 1 1
      spine-unity/Assets/Examples/Spine/Dragon/dragon.png.meta
  58. 1 1
      spine-unity/Assets/Examples/Spine/Dragon/dragon2.png.meta
  59. BIN
      spine-unity/Assets/Examples/Spine/Dragon/dragon_Atlas.asset
  60. BIN
      spine-unity/Assets/Examples/Spine/Dragon/dragon_SkeletonData.asset
  61. BIN
      spine-unity/Assets/Examples/Spine/Dragon/dragon_dragon.mat
  62. BIN
      spine-unity/Assets/Examples/Spine/Dragon/dragon_dragon2.mat
  63. 36 1
      spine-unity/Assets/Examples/Spine/Eyes/eyes.json
  64. 1 1
      spine-unity/Assets/Examples/Spine/Eyes/eyes.png.meta
  65. BIN
      spine-unity/Assets/Examples/Spine/Eyes/eyes_Atlas.asset
  66. BIN
      spine-unity/Assets/Examples/Spine/Eyes/eyes_Material.mat
  67. BIN
      spine-unity/Assets/Examples/Spine/Eyes/eyes_SkeletonData.asset
  68. 1 1
      spine-unity/Assets/Examples/Spine/FootSoldier/Equipment/Equipment.png.meta
  69. BIN
      spine-unity/Assets/Examples/Spine/FootSoldier/Equipment/Equipment_Atlas.asset
  70. BIN
      spine-unity/Assets/Examples/Spine/FootSoldier/Equipment/Equipment_Material.mat
  71. 1 1
      spine-unity/Assets/Examples/Spine/FootSoldier/FS_White.png.meta
  72. BIN
      spine-unity/Assets/Examples/Spine/FootSoldier/FS_White_Atlas.asset
  73. BIN
      spine-unity/Assets/Examples/Spine/FootSoldier/FS_White_Material.mat
  74. 21 21
      spine-unity/Assets/Examples/Spine/FootSoldier/FootSoldier.json
  75. BIN
      spine-unity/Assets/Examples/Spine/FootSoldier/FootSoldier_SkeletonData.asset
  76. 1 1
      spine-unity/Assets/Examples/Spine/Gauge/Gauge.json
  77. 1 1
      spine-unity/Assets/Examples/Spine/Gauge/Gauge.png.meta
  78. BIN
      spine-unity/Assets/Examples/Spine/Gauge/Gauge_Atlas.asset
  79. BIN
      spine-unity/Assets/Examples/Spine/Gauge/Gauge_Material.mat
  80. BIN
      spine-unity/Assets/Examples/Spine/Gauge/Gauge_SkeletonData.asset
  81. 0 4
      spine-unity/Assets/Examples/Spine/Goblins/goblins-mesh.atlas.txt.meta
  82. 0 0
      spine-unity/Assets/Examples/Spine/Goblins/goblins-mesh.json
  83. 0 4
      spine-unity/Assets/Examples/Spine/Goblins/goblins-mesh.json.meta
  84. BIN
      spine-unity/Assets/Examples/Spine/Goblins/goblins-mesh.png
  85. BIN
      spine-unity/Assets/Examples/Spine/Goblins/goblins-mesh_Atlas.asset
  86. 0 4
      spine-unity/Assets/Examples/Spine/Goblins/goblins-mesh_Atlas.asset.meta
  87. BIN
      spine-unity/Assets/Examples/Spine/Goblins/goblins-mesh_Material.mat
  88. 0 4
      spine-unity/Assets/Examples/Spine/Goblins/goblins-mesh_Material.mat.meta
  89. BIN
      spine-unity/Assets/Examples/Spine/Goblins/goblins-mesh_SkeletonData.asset
  90. 0 4
      spine-unity/Assets/Examples/Spine/Goblins/goblins-mesh_SkeletonData.asset.meta
  91. 66 66
      spine-unity/Assets/Examples/Spine/Goblins/goblins.atlas.txt
  92. 8 0
      spine-unity/Assets/Examples/Spine/Goblins/goblins.atlas.txt.meta
  93. 1060 0
      spine-unity/Assets/Examples/Spine/Goblins/goblins.json
  94. 8 0
      spine-unity/Assets/Examples/Spine/Goblins/goblins.json.meta
  95. BIN
      spine-unity/Assets/Examples/Spine/Goblins/goblins.png
  96. 6 6
      spine-unity/Assets/Examples/Spine/Goblins/goblins.png.meta
  97. 16 0
      spine-unity/Assets/Examples/Spine/Goblins/goblins_Atlas.asset
  98. 8 0
      spine-unity/Assets/Examples/Spine/Goblins/goblins_Atlas.asset.meta
  99. 30 0
      spine-unity/Assets/Examples/Spine/Goblins/goblins_Material.mat
  100. 8 0
      spine-unity/Assets/Examples/Spine/Goblins/goblins_Material.mat.meta

+ 3 - 0
.gitignore

@@ -12,6 +12,7 @@ target
 *.user
 .DS_Store
 
+
 .idea/
 build/
 
@@ -39,7 +40,9 @@ spine-cocos2d-iphone/spine-cocos2d-iphone-ios.xcodeproj/project.xcworkspace/xcsh
 
 spine-csharp/bin
 spine-csharp/obj
+spine-csharp/src/*.meta
 spine-csharp/src/*.cs.meta
+spine-csharp/src/Attachments/*.cs.meta
 
 spine-monogame/xamarinstudio-ios/src/bin
 spine-monogame/xamarinstudio-ios/src/obj

+ 2 - 2
spine-csharp/README.md

@@ -10,14 +10,14 @@ The Spine Runtimes are developed with the intent to be used with data exported f
 
 ## Spine version
 
-spine-csharp works with data exported from Spine 3.2.01. Updating spine-csharp to [v3.3](https://github.com/EsotericSoftware/spine-runtimes/issues/613) is in progress.
+spine-csharp works with data exported from Spine 3.3.07.
 
 spine-csharp supports all Spine features.
 
 ## Setup
 
 1. Download the Spine Runtimes source using [git](https://help.github.com/articles/set-up-git) or by downloading it [as a zip](https://github.com/EsotericSoftware/spine-runtimes/archive/master.zip).
-1. Open the `spine-csharp.sln` Visual C# 2010 Express project file.
+1. Open the `spine-csharp.sln` Visual Studio 2015 Community project file.
 
 Alternatively, the contents of the `spine-csharp/src` directory can be copied into your project.
 

+ 4 - 2
spine-csharp/spine-csharp.csproj

@@ -70,10 +70,10 @@
     <Compile Include="src\Attachments\AttachmentLoader.cs" />
     <Compile Include="src\Attachments\AttachmentType.cs" />
     <Compile Include="src\Attachments\BoundingBoxAttachment.cs" />
-    <Compile Include="src\Attachments\IFfdAttachment.cs" />
     <Compile Include="src\Attachments\MeshAttachment.cs" />
+    <Compile Include="src\Attachments\PathAttachment.cs" />
     <Compile Include="src\Attachments\RegionAttachment.cs" />
-    <Compile Include="src\Attachments\WeightedMeshAttachment.cs" />
+    <Compile Include="src\Attachments\VertexAttachment.cs" />
     <Compile Include="src\BlendMode.cs">
       <SubType>Code</SubType>
     </Compile>
@@ -105,6 +105,8 @@
     <Compile Include="src\MathUtils.cs">
       <SubType>Code</SubType>
     </Compile>
+    <Compile Include="src\PathConstraint.cs" />
+    <Compile Include="src\PathConstraintData.cs" />
     <Compile Include="src\Skeleton.cs">
       <SubType>Code</SubType>
     </Compile>

+ 270 - 165
spine-csharp/src/Animation.cs

@@ -43,8 +43,8 @@ namespace Spine {
 		public float Duration { get { return duration; } set { duration = value; } }
 
 		public Animation (String name, ExposedList<Timeline> timelines, float duration) {
-			if (name == null) throw new ArgumentNullException("name cannot be null.");
-			if (timelines == null) throw new ArgumentNullException("timelines cannot be null.");
+			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
+			if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null.");
 			this.name = name;
 			this.timelines = timelines;
 			this.duration = duration;
@@ -52,9 +52,9 @@ namespace Spine {
 
 		/// <summary>Poses the skeleton at the specified time for this animation.</summary>
 		/// <param name="lastTime">The last time the animation was applied.</param>
-		/// <param name="events">Any triggered events are added.</param>
+		/// <param name="events">Any triggered events are added. May be null.</param>
 		public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList<Event> events) {
-			if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null.");
+			if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
 
 			if (loop && duration != 0) {
 				time %= duration;
@@ -68,10 +68,10 @@ namespace Spine {
 
 		/// <summary>Poses the skeleton at the specified time for this animation mixed with the current pose.</summary>
 		/// <param name="lastTime">The last time the animation was applied.</param>
-		/// <param name="events">Any triggered events are added.</param>
+		/// <param name="events">Any triggered events are added. May be null.</param>
 		/// <param name="alpha">The amount of this animation that affects the current pose.</param>
 		public void Mix (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList<Event> events, float alpha) {
-			if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null.");
+			if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
 
 			if (loop && duration != 0) {
 				time %= duration;
@@ -131,12 +131,13 @@ namespace Spine {
 	/// <summary>Base class for frames that use an interpolation bezier curve.</summary>
 	abstract public class CurveTimeline : Timeline {
 		protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2;
-		protected const int BEZIER_SEGMENTS = 10, BEZIER_SIZE = BEZIER_SEGMENTS * 2 - 1;
+		protected const int BEZIER_SIZE = 10 * 2 - 1;
 
 		private float[] curves; // type, x, y, ...
 		public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } }
 
 		public CurveTimeline (int frameCount) {
+			if (frameCount <= 0) throw new ArgumentException("frameCount must be > 0: " + frameCount, "frameCount");
 			curves = new float[(frameCount - 1) * BEZIER_SIZE];
 		}
 
@@ -154,12 +155,10 @@ namespace Spine {
 		/// cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of
 		/// the difference between the keyframe's values.</summary>
 		public void SetCurve (int frameIndex, float cx1, float cy1, float cx2, float cy2) {
-			float subdiv1 = 1f / BEZIER_SEGMENTS, subdiv2 = subdiv1 * subdiv1, subdiv3 = subdiv2 * subdiv1;
-			float pre1 = 3 * subdiv1, pre2 = 3 * subdiv2, pre4 = 6 * subdiv2, pre5 = 6 * subdiv3;
-			float tmp1x = -cx1 * 2 + cx2, tmp1y = -cy1 * 2 + cy2, tmp2x = (cx1 - cx2) * 3 + 1, tmp2y = (cy1 - cy2) * 3 + 1;
-			float dfx = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv3, dfy = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv3;
-			float ddfx = tmp1x * pre4 + tmp2x * pre5, ddfy = tmp1y * pre4 + tmp2y * pre5;
-			float dddfx = tmp2x * pre5, dddfy = tmp2y * pre5;
+			float tmpx = (-cx1 * 2 + cx2) * 0.03f, tmpy = (-cy1 * 2 + cy2) * 0.03f;
+			float dddfx = ((cx1 - cx2) * 3 + 1) * 0.006f, dddfy = ((cy1 - cy2) * 3 + 1) * 0.006f;
+			float ddfx = tmpx * 2 + dddfx, ddfy = tmpy * 2 + dddfy;
+			float dfx = cx1 * 0.3f + tmpx + dddfx * 0.16666667f, dfy = cy1 * 0.3f + tmpy + dddfy * 0.16666667f;
 
 			int i = frameIndex * BEZIER_SIZE;
 			float[] curves = this.curves;
@@ -179,6 +178,7 @@ namespace Spine {
 		}
 
 		public float GetCurvePercent (int frameIndex, float percent) {
+			percent = MathUtils.Clamp (percent, 0, 1);
 			float[] curves = this.curves;
 			int i = frameIndex * BEZIER_SIZE;
 			float type = curves[i];
@@ -209,8 +209,9 @@ namespace Spine {
 	}
 
 	public class RotateTimeline : CurveTimeline {
-		internal const int PREV_TIME = -2;
-		internal const int VALUE = 1;
+		public const int ENTRIES = 2;
+		internal const int PREV_TIME = -2, PREV_ROTATION = -1;
+		internal const int ROTATION = 1;
 
 		internal int boneIndex;
 		internal float[] frames;
@@ -224,10 +225,10 @@ namespace Spine {
 		}
 
 		/// <summary>Sets the time and value of the specified keyframe.</summary>
-		public void SetFrame (int frameIndex, float time, float angle) {
-			frameIndex *= 2;
+		public void SetFrame (int frameIndex, float time, float degrees) {
+			frameIndex <<= 1;
 			frames[frameIndex] = time;
-			frames[frameIndex + 1] = angle;
+			frames[frameIndex + ROTATION] = degrees;
 		}
 
 		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
@@ -238,8 +239,8 @@ namespace Spine {
 
 			float amount;
 
-			if (time >= frames[frames.Length - 2]) { // Time is after last frame.
-				amount = bone.data.rotation + frames[frames.Length - 1] - bone.rotation;
+			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
+				amount = bone.data.rotation + frames[frames.Length + PREV_ROTATION] - bone.rotation;
 				while (amount > 180)
 					amount -= 360;
 				while (amount < -180)
@@ -249,18 +250,17 @@ namespace Spine {
 			}
 
 			// Interpolate between the previous frame and the current frame.
-			int frame = Animation.binarySearch(frames, time, 2);
-			float prevFrameValue = frames[frame - 1];
+			int frame = Animation.binarySearch(frames, time, ENTRIES);
+			float prevRotation = frames[frame + PREV_ROTATION];
 			float frameTime = frames[frame];
-			float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime);
-			percent = GetCurvePercent((frame >> 1) - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
+			float percent = GetCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
 
-			amount = frames[frame + VALUE] - prevFrameValue;
+			amount = frames[frame + ROTATION] - prevRotation;
 			while (amount > 180)
 				amount -= 360;
 			while (amount < -180)
 				amount += 360;
-			amount = bone.data.rotation + (prevFrameValue + amount * percent) - bone.rotation;
+			amount = bone.data.rotation + (prevRotation + amount * percent) - bone.rotation;
 			while (amount > 180)
 				amount -= 360;
 			while (amount < -180)
@@ -270,9 +270,9 @@ namespace Spine {
 	}
 
 	public class TranslateTimeline : CurveTimeline {
-		protected const int PREV_TIME = -3;
-		protected const int X = 1;
-		protected const int Y = 2;
+		public const int ENTRIES = 3;
+		protected const int PREV_TIME = -3, PREV_X = -2, PREV_Y = -1;
+		protected const int X = 1, Y = 2;
 
 		internal int boneIndex;
 		internal float[] frames;
@@ -282,15 +282,15 @@ namespace Spine {
 
 		public TranslateTimeline (int frameCount)
 			: base(frameCount) {
-			frames = new float[frameCount * 3];
+			frames = new float[frameCount * ENTRIES];
 		}
 
 		/// <summary>Sets the time and value of the specified keyframe.</summary>
 		public void SetFrame (int frameIndex, float time, float x, float y) {
-			frameIndex *= 3;
+			frameIndex *= ENTRIES;
 			frames[frameIndex] = time;
-			frames[frameIndex + 1] = x;
-			frames[frameIndex + 2] = y;
+			frames[frameIndex + X] = x;
+			frames[frameIndex + Y] = y;
 		}
 
 		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
@@ -299,22 +299,21 @@ namespace Spine {
 
 			Bone bone = skeleton.bones.Items[boneIndex];
 
-			if (time >= frames[frames.Length - 3]) { // Time is after last frame.
-				bone.x += (bone.data.x + frames[frames.Length - 2] - bone.x) * alpha;
-				bone.y += (bone.data.y + frames[frames.Length - 1] - bone.y) * alpha;
+			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
+				bone.x += (bone.data.x + frames[frames.Length + PREV_X] - bone.x) * alpha;
+				bone.y += (bone.data.y + frames[frames.Length + PREV_Y] - bone.y) * alpha;
 				return;
 			}
 
 			// Interpolate between the previous frame and the current frame.
-			int frame = Animation.binarySearch(frames, time, 3);
-			float prevFrameX = frames[frame - 2];
-			float prevFrameY = frames[frame - 1];
+			int frame = Animation.binarySearch(frames, time, ENTRIES);
+			float prevX = frames[frame + PREV_X];
+			float prevY = frames[frame + PREV_Y];
 			float frameTime = frames[frame];
-			float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime);
-			percent = GetCurvePercent(frame / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
+			float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
 
-			bone.x += (bone.data.x + prevFrameX + (frames[frame + X] - prevFrameX) * percent - bone.x) * alpha;
-			bone.y += (bone.data.y + prevFrameY + (frames[frame + Y] - prevFrameY) * percent - bone.y) * alpha;
+			bone.x += (bone.data.x + prevX + (frames[frame + X] - prevX) * percent - bone.x) * alpha;
+			bone.y += (bone.data.y + prevY + (frames[frame + Y] - prevY) * percent - bone.y) * alpha;
 		}
 	}
 
@@ -328,28 +327,27 @@ namespace Spine {
 			if (time < frames[0]) return; // Time is before first frame.
 
 			Bone bone = skeleton.bones.Items[boneIndex];
-			if (time >= frames[frames.Length - 3]) { // Time is after last frame.
-				bone.scaleX += (bone.data.scaleX * frames[frames.Length - 2] - bone.scaleX) * alpha;
-				bone.scaleY += (bone.data.scaleY * frames[frames.Length - 1] - bone.scaleY) * alpha;
+			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
+				bone.scaleX += (bone.data.scaleX * frames[frames.Length + PREV_X] - bone.scaleX) * alpha;
+				bone.scaleY += (bone.data.scaleY * frames[frames.Length + PREV_Y] - bone.scaleY) * alpha;
 				return;
 			}
 
 			// Interpolate between the previous frame and the current frame.
-			int frame = Animation.binarySearch(frames, time, 3);
-			float prevFrameX = frames[frame - 2];
-			float prevFrameY = frames[frame - 1];
+			int frame = Animation.binarySearch(frames, time, ENTRIES);
+			float prevX = frames[frame + PREV_X];
+			float prevY = frames[frame + PREV_Y];
 			float frameTime = frames[frame];
-			float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime);
-			percent = GetCurvePercent(frame / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
+			float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
 
-			bone.scaleX += (bone.data.scaleX * (prevFrameX + (frames[frame + X] - prevFrameX) * percent) - bone.scaleX) * alpha;
-			bone.scaleY += (bone.data.scaleY * (prevFrameY + (frames[frame + Y] - prevFrameY) * percent) - bone.scaleY) * alpha;
+			bone.scaleX += (bone.data.scaleX * (prevX + (frames[frame + X] - prevX) * percent) - bone.scaleX) * alpha;
+			bone.scaleY += (bone.data.scaleY * (prevY + (frames[frame + Y] - prevY) * percent) - bone.scaleY) * alpha;
 		}
 	}
 
 	public class ShearTimeline : TranslateTimeline {
 		public ShearTimeline (int frameCount)
-			: base (frameCount) {
+			: base(frameCount) {
 		}
 
 		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
@@ -357,31 +355,28 @@ namespace Spine {
 			if (time < frames[0]) return; // Time is before first frame.
 
 			Bone bone = skeleton.bones.Items[boneIndex];
-			if (time >= frames[frames.Length - 3]) { // Time is after last frame.
-				bone.shearX += (bone.data.shearX + frames[frames.Length - 2] - bone.shearX) * alpha;
-				bone.shearY += (bone.data.shearY + frames[frames.Length - 1] - bone.shearY) * alpha;
+			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
+				bone.shearX += (bone.data.shearX + frames[frames.Length + PREV_X] - bone.shearX) * alpha;
+				bone.shearY += (bone.data.shearY + frames[frames.Length + PREV_Y] - bone.shearY) * alpha;
 				return;
 			}
 
 			// Interpolate between the previous frame and the current frame.
-			int frame = Animation.binarySearch(frames, time, 3);
-			float prevFrameX = frames[frame - 2];
-			float prevFrameY = frames[frame - 1];
+			int frame = Animation.binarySearch(frames, time, ENTRIES);
+			float prevX = frames[frame + PREV_X];
+			float prevY = frames[frame + PREV_Y];
 			float frameTime = frames[frame];
-			float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime);
-			percent = GetCurvePercent(frame / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
+			float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
 
-			bone.shearX += (bone.data.shearX + (prevFrameX + (frames[frame + X] - prevFrameX) * percent) - bone.shearX) * alpha;
-			bone.shearY += (bone.data.shearY + (prevFrameY + (frames[frame + Y] - prevFrameY) * percent) - bone.shearY) * alpha;
+			bone.shearX += (bone.data.shearX + (prevX + (frames[frame + X] - prevX) * percent) - bone.shearX) * alpha;
+			bone.shearY += (bone.data.shearY + (prevY + (frames[frame + Y] - prevY) * percent) - bone.shearY) * alpha;
 		}
 	}
 
 	public class ColorTimeline : CurveTimeline {
-		protected const int PREV_TIME = -5;
-		protected const int R = 1;
-		protected const int G = 2;
-		protected const int B = 3;
-		protected const int A = 4;
+		public const int ENTRIES = 5;
+		protected const int PREV_TIME = -5, PREV_R = -4, PREV_G = -3, PREV_B = -2, PREV_A = -1;
+		protected const int R = 1, G = 2, B = 3, A = 4;
 
 		internal int slotIndex;
 		internal float[] frames;
@@ -391,17 +386,17 @@ namespace Spine {
 
 		public ColorTimeline (int frameCount)
 			: base(frameCount) {
-			frames = new float[frameCount * 5];
+			frames = new float[frameCount * ENTRIES];
 		}
 
 		/// <summary>Sets the time and value of the specified keyframe.</summary>
 		public void SetFrame (int frameIndex, float time, float r, float g, float b, float a) {
-			frameIndex *= 5;
+			frameIndex *= ENTRIES;
 			frames[frameIndex] = time;
-			frames[frameIndex + 1] = r;
-			frames[frameIndex + 2] = g;
-			frames[frameIndex + 3] = b;
-			frames[frameIndex + 4] = a;
+			frames[frameIndex + R] = r;
+			frames[frameIndex + G] = g;
+			frames[frameIndex + B] = b;
+			frames[frameIndex + A] = a;
 		}
 
 		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
@@ -409,23 +404,23 @@ namespace Spine {
 			if (time < frames[0]) return; // Time is before first frame.
 
 			float r, g, b, a;
-			if (time >= frames[frames.Length - 5]) { // Time is after last frame.
-				int i = frames.Length - 1;
-				r = frames[i - 3];
-				g = frames[i - 2];
-				b = frames[i - 1];
-				a = frames[i];
+			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
+				int i = frames.Length;
+				r = frames[i + PREV_R];
+				g = frames[i + PREV_G];
+				b = frames[i + PREV_B];
+				a = frames[i + PREV_A];
 			} else {
 				// Interpolate between the previous frame and the current frame.
-				int frame = Animation.binarySearch(frames, time, 5);
+				int frame = Animation.binarySearch(frames, time, ENTRIES);
+				r = frames[frame + PREV_R];
+				g = frames[frame + PREV_G];
+				b = frames[frame + PREV_B];
+				a = frames[frame + PREV_A];
 				float frameTime = frames[frame];
-				float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime);
-				percent = GetCurvePercent(frame / 5 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
+				float percent = GetCurvePercent(frame / ENTRIES - 1,
+					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
 
-				r = frames[frame - 4];
-				g = frames[frame - 3];
-				b = frames[frame - 2];
-				a = frames[frame - 1];
 				r += (frames[frame + R] - r) * percent;
 				g += (frames[frame + G] - g) * percent;
 				b += (frames[frame + B] - b) * percent;
@@ -469,18 +464,17 @@ namespace Spine {
 
 		public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
 			float[] frames = this.frames;
-			if (time < frames[0]) {
-				if (lastTime > time) Apply(skeleton, lastTime, int.MaxValue, null, 0);
-				return;
-			} else if (lastTime > time) //
-				lastTime = -1;
+			if (time < frames[0]) return; // Time is before first frame.
 
-			int frameIndex = (time >= frames[frames.Length - 1] ? frames.Length : Animation.binarySearch(frames, time)) - 1;
-			if (frames[frameIndex] < lastTime) return;
+			int frameIndex;
+			if (time >= frames[frames.Length - 1]) // Time is after last frame.
+				frameIndex = frames.Length - 1;
+			else
+				frameIndex = Animation.binarySearch(frames, time, 1) - 1;
 
 			String attachmentName = attachmentNames[frameIndex];
-			skeleton.slots.Items[slotIndex].Attachment =
-				attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName);
+			skeleton.slots.Items[slotIndex]
+				.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName);
 		}
 	}
 
@@ -503,7 +497,7 @@ namespace Spine {
 			events[frameIndex] = e;
 		}
 
-		/// <summary>Fires events for frames > lastTime and <= time.</summary>
+		/// <summary>Fires events for frames &gt; lastTime and &lt;= time.</summary>
 		public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
 			if (firedEvents == null) return;
 			float[] frames = this.frames;
@@ -570,24 +564,26 @@ namespace Spine {
 				for (int i = 0, n = slots.Count; i < n; i++)
 					drawOrder.Add(slots.Items[i]);
 			} else {
+				var drawOrderItems = drawOrder.Items;
+				var slotsItems = slots.Items;
 				for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++)
-					drawOrder.Items[i] = slots.Items[drawOrderToSetupIndex[i]];
+					drawOrderItems[i] = slotsItems[drawOrderToSetupIndex[i]];
 			}
 		}
 	}
 
-	public class FfdTimeline : CurveTimeline {
+	public class DeformTimeline : CurveTimeline {
 		internal int slotIndex;
 		internal float[] frames;
 		private float[][] frameVertices;
-		internal Attachment attachment;
+		internal VertexAttachment attachment;
 
 		public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } }
 		public float[] Frames { get { return frames; } set { frames = value; } } // time, ...
 		public float[][] Vertices { get { return frameVertices; } set { frameVertices = value; } }
-		public Attachment Attachment { get { return attachment; } set { attachment = value; } }
+		public VertexAttachment Attachment { get { return attachment; } set { attachment = value; } }
 
-		public FfdTimeline (int frameCount)
+		public DeformTimeline (int frameCount)
 			: base(frameCount) {
 			frames = new float[frameCount];
 			frameVertices = new float[frameCount][];
@@ -601,8 +597,8 @@ namespace Spine {
 
 		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
 			Slot slot = skeleton.slots.Items[slotIndex];
-			IFfdAttachment ffdAttachment = slot.attachment as IFfdAttachment;
-			if (ffdAttachment == null || !ffdAttachment.ApplyFFD(attachment)) return;
+			VertexAttachment slotAttachment = slot.attachment as VertexAttachment;
+			if (slotAttachment == null || !slotAttachment.ApplyDeform(attachment)) return;
 
 			float[] frames = this.frames;
 			if (time < frames[0]) return; // Time is before first frame.
@@ -610,15 +606,12 @@ namespace Spine {
 			float[][] frameVertices = this.frameVertices;
 			int vertexCount = frameVertices[0].Length;
 
-			float[] vertices = slot.attachmentVertices;
-			if (slot.attachmentVerticesCount != vertexCount) alpha = 1; // Don't mix from uninitialized slot vertices.
-
-			// Ensure capacity
-			if (vertices.Length < vertexCount) {
-				vertices = new float[vertexCount];
-				slot.attachmentVertices = vertices;
-			}
-			slot.attachmentVerticesCount = vertexCount;
+			var verticesArray = slot.attachmentVertices;
+			if (verticesArray.Count != vertexCount) alpha = 1; // Don't mix from uninitialized slot vertices.
+			// verticesArray.SetSize(vertexCount) // Ensure size and preemptively set count.
+			if (verticesArray.Capacity < vertexCount) verticesArray.Capacity = vertexCount;
+			verticesArray.Count = vertexCount;
+			float[] vertices = verticesArray.Items;
 
 			if (time >= frames[frames.Length - 1]) { // Time is after last frame.
 				float[] lastVertices = frameVertices[frames.Length - 1];
@@ -634,12 +627,10 @@ namespace Spine {
 
 			// Interpolate between the previous frame and the current frame.
 			int frame = Animation.binarySearch(frames, time);
-			float frameTime = frames[frame];
-			float percent = 1 - (time - frameTime) / (frames[frame - 1] - frameTime);
-			percent = GetCurvePercent(frame - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
-
 			float[] prevVertices = frameVertices[frame - 1];
 			float[] nextVertices = frameVertices[frame];
+			float frameTime = frames[frame];
+			float percent = GetCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime));
 
 			if (alpha < 1) {
 				for (int i = 0; i < vertexCount; i++) {
@@ -657,10 +648,9 @@ namespace Spine {
 	}
 
 	public class IkConstraintTimeline : CurveTimeline {
-		private const int PREV_TIME = -3;
-		private const int PREV_MIX = -2;
-		private const int PREV_BEND_DIRECTION = -1;
-		private const int MIX = 1;
+		public const int ENTRIES = 3;
+		private const int PREV_TIME = -3, PREV_MIX = -2, PREV_BEND_DIRECTION = -1;
+		private const int MIX = 1, BEND_DIRECTION = 2;
 
 		internal int ikConstraintIndex;
 		internal float[] frames;
@@ -670,15 +660,15 @@ namespace Spine {
 
 		public IkConstraintTimeline (int frameCount)
 			: base(frameCount) {
-			frames = new float[frameCount * 3];
+			frames = new float[frameCount * ENTRIES];
 		}
 			
 		/// <summary>Sets the time, mix and bend direction of the specified keyframe.</summary>
 		public void SetFrame (int frameIndex, float time, float mix, int bendDirection) {
-			frameIndex *= 3;
+			frameIndex *= ENTRIES;
 			frames[frameIndex] = time;
-			frames[frameIndex + 1] = mix;
-			frames[frameIndex + 2] = bendDirection;
+			frames[frameIndex + MIX] = mix;
+			frames[frameIndex + BEND_DIRECTION] = bendDirection;
 		}
 
 		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
@@ -687,34 +677,27 @@ namespace Spine {
 
 			IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex];
 
-			if (time >= frames[frames.Length - 3]) { // Time is after last frame.
+			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
 				constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha;
 				constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION];
 				return;
 			}
 
 			// Interpolate between the previous frame and the current frame.
-			int frame = Animation.binarySearch(frames, time, 3);
+			int frame = Animation.binarySearch(frames, time, ENTRIES);
+			float mix = frames[frame + PREV_MIX];
 			float frameTime = frames[frame];
-			float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime);
-			percent = GetCurvePercent(frame / 3 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
+			float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
 
-			float mix = frames[frame + PREV_MIX];
 			constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha;
 			constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION];
 		}
 	}
 
 	public class TransformConstraintTimeline : CurveTimeline {
-		private const int PREV_TIME = -5;
-		private const int PREV_ROTATE_MIX = -4;
-		private const int PREV_TRANSLATE_MIX = -3;
-		private const int PREV_SCALE_MIX = -2;
-		private const int PREV_SHEAR_MIX = -1;
-		private const int ROTATE_MIX = 1;
-		private const int TRANSLATE_MIX = 2;
-		private const int SCALE_MIX = 3;
-		private const int SHEAR_MIX = 4;
+		public const int ENTRIES = 5;
+		private const int PREV_TIME = -5, PREV_ROTATE = -4, PREV_TRANSLATE = -3, PREV_SCALE = -2, PREV_SHEAR = -1;
+		private const int ROTATE = 1, TRANSLATE = 2, SCALE = 3, SHEAR = 4;
 
 		internal int transformConstraintIndex;
 		internal float[] frames;
@@ -724,16 +707,16 @@ namespace Spine {
 
 		public TransformConstraintTimeline (int frameCount)
 			: base(frameCount) {
-			frames = new float[frameCount * 5];
+			frames = new float[frameCount * ENTRIES];
 		}
-			
+
 		public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix, float scaleMix, float shearMix) {
-			frameIndex *= 5;
+			frameIndex *= ENTRIES;
 			frames[frameIndex] = time;
-			frames[frameIndex + 1] = rotateMix;
-			frames[frameIndex + 2] = translateMix;
-			frames[frameIndex + 3] = scaleMix;
-			frames[frameIndex + 4] = shearMix;
+			frames[frameIndex + ROTATE] = rotateMix;
+			frames[frameIndex + TRANSLATE] = translateMix;
+			frames[frameIndex + SCALE] = scaleMix;
+			frames[frameIndex + SHEAR] = shearMix;
 		}
 
 		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
@@ -742,29 +725,151 @@ namespace Spine {
 
 			TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex];
 
-			if (time >= frames[frames.Length - 5]) { // Time is after last frame.
-				int i = frames.Length - 1;
-				constraint.rotateMix += (frames[i - 3] - constraint.rotateMix) * alpha;
-				constraint.translateMix += (frames[i - 2] - constraint.translateMix) * alpha;
-				constraint.scaleMix += (frames[i - 1] - constraint.scaleMix) * alpha;
-				constraint.shearMix += (frames[i] - constraint.shearMix) * alpha;
+			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
+				int i = frames.Length;
+				constraint.rotateMix += (frames[i + PREV_ROTATE] - constraint.rotateMix) * alpha;
+				constraint.translateMix += (frames[i + PREV_TRANSLATE] - constraint.translateMix) * alpha;
+				constraint.scaleMix += (frames[i + PREV_SCALE] - constraint.scaleMix) * alpha;
+				constraint.shearMix += (frames[i + PREV_SHEAR] - constraint.shearMix) * alpha;
+				return;
+			}
+
+			// Interpolate between the previous frame and the current frame.
+			int frame = Animation.binarySearch(frames, time, ENTRIES);
+			float frameTime = frames[frame];
+			float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+
+			float rotate = frames[frame + PREV_ROTATE];
+			float translate = frames[frame + PREV_TRANSLATE];
+			float scale = frames[frame + PREV_SCALE];
+			float shear = frames[frame + PREV_SHEAR];
+			constraint.rotateMix += (rotate + (frames[frame + ROTATE] - rotate) * percent - constraint.rotateMix) * alpha;
+			constraint.translateMix += (translate + (frames[frame + TRANSLATE] - translate) * percent - constraint.translateMix)
+				* alpha;
+			constraint.scaleMix += (scale + (frames[frame + SCALE] - scale) * percent - constraint.scaleMix) * alpha;
+			constraint.shearMix += (shear + (frames[frame + SHEAR] - shear) * percent - constraint.shearMix) * alpha;
+		}
+	}
+
+	public class PathConstraintPositionTimeline : CurveTimeline {
+		public const int ENTRIES = 2;
+		protected const int PREV_TIME = -2, PREV_VALUE = -1;
+		protected const int VALUE = 1;
+
+		internal int pathConstraintIndex;
+		internal float[] frames;
+
+		public PathConstraintPositionTimeline (int frameCount)
+			: base(frameCount) {
+			frames = new float[frameCount * ENTRIES];
+		}
+
+		public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } }
+		public float[] Frames { get { return frames; } set { frames = value; } } // time, position, ...
+
+		/// <summary>Sets the time and value of the specified keyframe.</summary>
+		public void SetFrame (int frameIndex, float time, float value) {
+			frameIndex *= ENTRIES;
+			frames[frameIndex] = time;
+			frames[frameIndex + VALUE] = value;
+		}
+
+		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> events, float alpha) {
+			float[] frames = this.frames;
+			if (time < frames[0]) return; // Time is before first frame.
+
+			PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
+
+			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
+				int i = frames.Length;
+				constraint.position += (frames[i + PREV_VALUE] - constraint.position) * alpha;
+				return;
+			}
+
+			// Interpolate between the previous frame and the current frame.
+			int frame = Animation.binarySearch(frames, time, ENTRIES);
+			float position = frames[frame + PREV_VALUE];
+			float frameTime = frames[frame];
+			float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+
+			constraint.position += (position + (frames[frame + VALUE] - position) * percent - constraint.position) * alpha;
+		}
+	}
+
+	public class PathConstraintSpacingTimeline : PathConstraintPositionTimeline {
+		public PathConstraintSpacingTimeline (int frameCount)
+			: base(frameCount) {
+		}
+
+		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> events, float alpha) {
+			float[] frames = this.frames;
+			if (time < frames[0]) return; // Time is before first frame.
+
+			PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
+
+			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
+				int i = frames.Length;
+				constraint.spacing += (frames[i + PREV_VALUE] - constraint.spacing) * alpha;
+				return;
+			}
+
+			// Interpolate between the previous frame and the current frame.
+			int frame = Animation.binarySearch(frames, time, ENTRIES);
+			float spacing = frames[frame + PREV_VALUE];
+			float frameTime = frames[frame];
+			float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+
+			constraint.spacing += (spacing + (frames[frame + VALUE] - spacing) * percent - constraint.spacing) * alpha;
+		}
+	}
+
+	public class PathConstraintMixTimeline : CurveTimeline {
+		public const int ENTRIES = 3;
+		private const int PREV_TIME = -3, PREV_ROTATE = -2, PREV_TRANSLATE = -1;
+		private const int ROTATE = 1, TRANSLATE = 2;
+
+		internal int pathConstraintIndex;
+		internal float[] frames;
+
+		public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } }
+		public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, ...
+
+		public PathConstraintMixTimeline (int frameCount)
+			: base(frameCount) {
+			frames = new float[frameCount * ENTRIES];
+		}			
+
+		/** Sets the time and mixes of the specified keyframe. */
+		public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix) {
+			frameIndex *= ENTRIES;
+			frames[frameIndex] = time;
+			frames[frameIndex + ROTATE] = rotateMix;
+			frames[frameIndex + TRANSLATE] = translateMix;
+		}
+
+		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> events, float alpha) {
+			float[] frames = this.frames;
+			if (time < frames[0]) return; // Time is before first frame.
+
+			PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
+
+			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
+				int i = frames.Length;
+				constraint.rotateMix += (frames[i + PREV_ROTATE] - constraint.rotateMix) * alpha;
+				constraint.translateMix += (frames[i + PREV_TRANSLATE] - constraint.translateMix) * alpha;
 				return;
 			}
 
 			// Interpolate between the previous frame and the current frame.
-			int frame = Animation.binarySearch(frames, time, 5);
+			int frame = Animation.binarySearch(frames, time, ENTRIES);
+			float rotate = frames[frame + PREV_ROTATE];
+			float translate = frames[frame + PREV_TRANSLATE];
 			float frameTime = frames[frame];
-			float percent = 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime);
-			percent = GetCurvePercent(frame / 5 - 1, percent < 0 ? 0 : (percent > 1 ? 1 : percent));
-
-			float rotate = frames[frame + PREV_ROTATE_MIX];
-			float translate = frames[frame + PREV_TRANSLATE_MIX];
-			float scale = frames[frame + PREV_SCALE_MIX];
-			float shear = frames[frame + PREV_SHEAR_MIX];
-			constraint.rotateMix += (rotate + (frames[frame + ROTATE_MIX] - rotate) * percent - constraint.rotateMix) * alpha;
-			constraint.translateMix += (translate + (frames[frame + TRANSLATE_MIX] - translate) * percent - constraint.translateMix) * alpha;
-			constraint.scaleMix += (scale + (frames[frame + SCALE_MIX] - scale) * percent - constraint.scaleMix) * alpha;
-			constraint.shearMix += (shear + (frames[frame + SHEAR_MIX] - shear) * percent - constraint.shearMix) * alpha;
+			float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+
+			constraint.rotateMix += (rotate + (frames[frame + ROTATE] - rotate) * percent - constraint.rotateMix) * alpha;
+			constraint.translateMix += (translate + (frames[frame + TRANSLATE] - translate) * percent - constraint.translateMix)
+				* alpha;
 		}
 	}
 }

+ 10 - 6
spine-csharp/src/AnimationState.cs

@@ -41,6 +41,8 @@ namespace Spine {
 		private float timeScale = 1;
 
 		public AnimationStateData Data { get { return data; } }
+		/// <summary>A list of tracks that have animations, which may contain nulls.</summary>
+		public ExposedList<TrackEntry> Tracks { get { return tracks; } }
 		public float TimeScale { get { return timeScale; } set { timeScale = value; } }
 
 		public delegate void StartEndDelegate (AnimationState state, int trackIndex);
@@ -54,7 +56,7 @@ namespace Spine {
 		public event CompleteDelegate Complete;
 
 		public AnimationState (AnimationStateData data) {
-			if (data == null) throw new ArgumentNullException("data cannot be null.");
+			if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
 			this.data = data;
 		}
 
@@ -187,15 +189,16 @@ namespace Spine {
 			if (Start != null) Start(this, index);
 		}
 
+		/// <seealso cref="SetAnimation(int, Animation, bool)" />
 		public TrackEntry SetAnimation (int trackIndex, String animationName, bool loop) {
 			Animation animation = data.skeletonData.FindAnimation(animationName);
-			if (animation == null) throw new ArgumentException("Animation not found: " + animationName);
+			if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName");
 			return SetAnimation(trackIndex, animation, loop);
 		}
 
 		/// <summary>Set the current animation. Any queued animations are cleared.</summary>
 		public TrackEntry SetAnimation (int trackIndex, Animation animation, bool loop) {
-			if (animation == null) throw new ArgumentException("animation cannot be null.");
+			if (animation == null) throw new ArgumentNullException("animation", "animation cannot be null.");
 			TrackEntry entry = new TrackEntry();
 			entry.animation = animation;
 			entry.loop = loop;
@@ -205,16 +208,17 @@ namespace Spine {
 			return entry;
 		}
 
+		/// <seealso cref="AddAnimation(int, Animation, bool, float)" />
 		public TrackEntry AddAnimation (int trackIndex, String animationName, bool loop, float delay) {
 			Animation animation = data.skeletonData.FindAnimation(animationName);
-			if (animation == null) throw new ArgumentException("Animation not found: " + animationName);
+			if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName");
 			return AddAnimation(trackIndex, animation, loop, delay);
 		}
 
 		/// <summary>Adds an animation to be played delay seconds after the current or last queued animation.</summary>
-		/// <param name="delay">May be <= 0 to use duration of previous animation minus any mix duration plus the negative delay.</param>
+		/// <param name="delay">May be &lt;= 0 to use duration of previous animation minus any mix duration plus the negative delay.</param>
 		public TrackEntry AddAnimation (int trackIndex, Animation animation, bool loop, float delay) {
-			if (animation == null) throw new ArgumentException("animation cannot be null.");
+			if (animation == null) throw new ArgumentNullException("animation", "animation cannot be null.");
 			TrackEntry entry = new TrackEntry();
 			entry.animation = animation;
 			entry.loop = loop;

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

@@ -42,6 +42,7 @@ namespace Spine {
 		public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } }
 
 		public AnimationStateData (SkeletonData skeletonData) {
+			if (skeletonData == null) throw new ArgumentException ("skeletonData cannot be null.");
 			this.skeletonData = skeletonData;
 		}
 
@@ -54,8 +55,8 @@ namespace Spine {
 		}
 
 		public void SetMix (Animation from, Animation to, float duration) {
-			if (from == null) throw new ArgumentNullException("from cannot be null.");
-			if (to == null) throw new ArgumentNullException("to cannot be null.");
+			if (from == null) throw new ArgumentNullException("from", "from cannot be null.");
+			if (to == null) throw new ArgumentNullException("to", "to cannot be null.");
 			AnimationPair key = new AnimationPair(from, to);
 			animationToMixTime.Remove(key);
 			animationToMixTime.Add(key, duration);

+ 5 - 20
spine-csharp/src/Attachments/AtlasAttachmentLoader.cs

@@ -72,31 +72,16 @@ namespace Spine {
 			attachment.regionOriginalWidth = region.originalWidth;
 			attachment.regionOriginalHeight = region.originalHeight;
 			return attachment;
-		}
-
-		public WeightedMeshAttachment NewWeightedMeshAttachment (Skin skin, String name, String path) {
-			AtlasRegion region = FindRegion(path);
-			if (region == null) throw new Exception("Region not found in atlas: " + path + " (weighted mesh attachment: " + name + ")");
-			WeightedMeshAttachment attachment = new WeightedMeshAttachment(name);
-			attachment.RendererObject = region;
-			attachment.RegionU = region.u;
-			attachment.RegionV = region.v;
-			attachment.RegionU2 = region.u2;
-			attachment.RegionV2 = region.v2;
-			attachment.RegionRotate = region.rotate;
-			attachment.regionOffsetX = region.offsetX;
-			attachment.regionOffsetY = region.offsetY;
-			attachment.regionWidth = region.width;
-			attachment.regionHeight = region.height;
-			attachment.regionOriginalWidth = region.originalWidth;
-			attachment.regionOriginalHeight = region.originalHeight;
-			return attachment;
-		}
+		}			
 
 		public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, String name) {
 			return new BoundingBoxAttachment(name);
 		}
 
+		public PathAttachment NewPathAttachment (Skin skin, String name) {
+			return new PathAttachment (name);
+		}
+
 		public AtlasRegion FindRegion (string name) {
 			AtlasRegion region;
 

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

@@ -36,7 +36,7 @@ namespace Spine {
 		public String Name { get; private set; }
 
 		public Attachment (String name) {
-			if (name == null) throw new ArgumentNullException("name cannot be null.");
+			if (name == null) throw new ArgumentNullException("name", "name cannot be null");
 			Name = name;
 		}
 

+ 3 - 3
spine-csharp/src/Attachments/AttachmentLoader.cs

@@ -39,10 +39,10 @@ namespace Spine {
 		/// <return>May be null to not load any attachment.</return>
 		MeshAttachment NewMeshAttachment (Skin skin, String name, String path);
 
-		/// <return>May be null to not load any attachment.</return>
-		WeightedMeshAttachment NewWeightedMeshAttachment (Skin skin, String name, String path);
-
 		/// <return>May be null to not load any attachment.</return>
 		BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, String name);
+
+		/// <returns>May be null to not load any attachment</returns>
+		PathAttachment NewPathAttachment (Skin skin, String name);
 	}
 }

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

@@ -31,6 +31,6 @@
 
 namespace Spine {
 	public enum AttachmentType {
-		region, boundingbox, mesh, weightedmesh, linkedmesh, weightedlinkedmesh
+		Region, Boundingbox, Mesh, Linkedmesh, Path
 	}
 }

+ 1 - 21
spine-csharp/src/Attachments/BoundingBoxAttachment.cs

@@ -33,29 +33,9 @@ using System;
 
 namespace Spine {
 	/// <summary>Attachment that has a polygon for bounds checking.</summary>
-	public class BoundingBoxAttachment : Attachment {
-		internal float[] vertices;
-
-		public float[] Vertices { get { return vertices; } set { vertices = value; } }
-
+	public class BoundingBoxAttachment : VertexAttachment {
 		public BoundingBoxAttachment (string name)
 			: base(name) {
 		}
-
-		/// <param name="worldVertices">Must have at least the same length as this attachment's vertices.</param>
-		public void ComputeWorldVertices (Bone bone, float[] worldVertices) {
-			float x = bone.skeleton.x + bone.worldX, y = bone.skeleton.y + bone.worldY;
-			float m00 = bone.a;
-			float m01 = bone.b;
-			float m10 = bone.c;
-			float m11 = bone.d;
-			float[] vertices = this.vertices;
-			for (int i = 0, n = vertices.Length; i < n; i += 2) {
-				float px = vertices[i];
-				float py = vertices[i + 1];
-				worldVertices[i] = px * m00 + py * m01 + x;
-				worldVertices[i + 1] = px * m10 + py * m11 + y;
-			}
-		}
 	}
 }

+ 10 - 24
spine-csharp/src/Attachments/MeshAttachment.cs

@@ -33,16 +33,16 @@ using System;
 
 namespace Spine {
 	/// <summary>Attachment that displays a texture region using a mesh.</summary>
-	public class MeshAttachment : Attachment, IFfdAttachment {
-		internal float[] vertices, uvs, regionUVs;
-		internal int[] triangles;
+	public class MeshAttachment : VertexAttachment {
 		internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight;
+		internal float[] uvs, regionUVs;
+		internal int[] triangles;
 		internal float r = 1, g = 1, b = 1, a = 1;
+		internal int hulllength;
 		internal MeshAttachment parentMesh;
-		internal bool inheritFFD;
+		internal bool inheritDeform;
 
-		public int HullLength { get; set; }
-		public float[] Vertices { get { return vertices; } set { vertices = value; } }
+		public int HullLength { get { return hulllength; } set { hulllength = value; } }
 		public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } }
 		public float[] UVs { get { return uvs; } set { uvs = value; } }
 		public int[] Triangles { get { return triangles; } set { triangles = value; } }
@@ -66,7 +66,7 @@ namespace Spine {
 		public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } }
 		public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size.
 
-		public bool InheritFFD { get { return inheritFFD; } set { inheritFFD = value; } }
+		public bool InheritDeform { get { return inheritDeform; } set { inheritDeform = value; } }
 
 		public MeshAttachment ParentMesh {
 			get { return parentMesh; }
@@ -74,6 +74,7 @@ namespace Spine {
 				parentMesh = value;
 				if (value != null) {
 					vertices = value.vertices;
+					worldVerticesLength = value.worldVerticesLength;
 					regionUVs = value.regionUVs;
 					triangles = value.triangles;
 					HullLength = value.HullLength;
@@ -111,23 +112,8 @@ namespace Spine {
 			}
 		}
 
-		public void ComputeWorldVertices (Slot slot, float[] worldVertices) {
-			Bone bone = slot.bone;
-			float x = bone.skeleton.x + bone.worldX, y = bone.skeleton.y + bone.worldY;
-			float m00 = bone.a, m01 = bone.b, m10 = bone.c, m11 = bone.d;
-			float[] vertices = this.vertices;
-			int verticesCount = vertices.Length;
-			if (slot.attachmentVerticesCount == verticesCount) vertices = slot.AttachmentVertices;
-			for (int i = 0; i < verticesCount; i += 2) {
-				float vx = vertices[i];
-				float vy = vertices[i + 1];
-				worldVertices[i] = vx * m00 + vy * m01 + x;
-				worldVertices[i + 1] = vx * m10 + vy * m11 + y;
-			}
-		}
-
-		public bool ApplyFFD (Attachment sourceAttachment) {
-			return this == sourceAttachment || (inheritFFD && parentMesh == sourceAttachment);
+		override public bool ApplyDeform (VertexAttachment sourceAttachment) {
+			return this == sourceAttachment || (inheritDeform && parentMesh == sourceAttachment);
 		}
 	}
 }

+ 16 - 3
spine-csharp/src/Attachments/IFfdAttachment.cs → spine-csharp/src/Attachments/PathAttachment.cs

@@ -29,8 +29,21 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
 
+using System;
+using System.Collections.Generic;
+
 namespace Spine {
-	public interface IFfdAttachment {
-		bool ApplyFFD (Attachment sourceAttachment);
+	public class PathAttachment : VertexAttachment {
+		internal float[] lengths;
+		internal bool closed, constantSpeed;
+
+		/// <summary>The length in the setup pose from the start of the path to the end of each curve.</summary>
+		public float[] Lengths { get { return lengths; } set { lengths = value; } }
+		public bool Closed { get { return closed; } set { closed = value; } }
+		public bool ConstantSpeed { get { return constantSpeed; } set { constantSpeed = value; } }
+
+		public PathAttachment (String name)
+			: base(name) {
+		}			
 	}
-}
+}

+ 14 - 13
spine-csharp/src/Attachments/RegionAttachment.cs

@@ -111,9 +111,9 @@ namespace Spine {
 			float localY = -height / 2 * scaleY + regionOffsetY * regionScaleY;
 			float localX2 = localX + regionWidth * regionScaleX;
 			float localY2 = localY + regionHeight * regionScaleY;
-			float radians = rotation * (float)Math.PI / 180;
-			float cos = (float)Math.Cos(radians);
-			float sin = (float)Math.Sin(radians);
+			float rotation = this.rotation;
+			float cos = MathUtils.CosDeg(rotation);
+			float sin = MathUtils.SinDeg(rotation);
 			float x = this.x;
 			float y = this.y;
 			float localXCos = localX * cos + x;
@@ -136,17 +136,18 @@ namespace Spine {
 		}
 
 		public void ComputeWorldVertices (Bone bone, float[] worldVertices) {
-			float x = bone.skeleton.x + bone.worldX, y = bone.skeleton.y + bone.worldY;
-			float m00 = bone.a, m01 = bone.b, m10 = bone.c, m11 = bone.d;
+			Skeleton skeleton = bone.skeleton;
+			float x = skeleton.x + bone.worldX, y = skeleton.y + bone.worldY;			
+			float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
 			float[] offset = this.offset;
-			worldVertices[X1] = offset[X1] * m00 + offset[Y1] * m01 + x;
-			worldVertices[Y1] = offset[X1] * m10 + offset[Y1] * m11 + y;
-			worldVertices[X2] = offset[X2] * m00 + offset[Y2] * m01 + x;
-			worldVertices[Y2] = offset[X2] * m10 + offset[Y2] * m11 + y;
-			worldVertices[X3] = offset[X3] * m00 + offset[Y3] * m01 + x;
-			worldVertices[Y3] = offset[X3] * m10 + offset[Y3] * m11 + y;
-			worldVertices[X4] = offset[X4] * m00 + offset[Y4] * m01 + x;
-			worldVertices[Y4] = offset[X4] * m10 + offset[Y4] * m11 + y;
+			worldVertices[X1] = offset[X1] * a + offset[Y1] * b + x;
+			worldVertices[Y1] = offset[X1] * c + offset[Y1] * d + y;
+			worldVertices[X2] = offset[X2] * a + offset[Y2] * b + x;
+			worldVertices[Y2] = offset[X2] * c + offset[Y2] * d + y;
+			worldVertices[X3] = offset[X3] * a + offset[Y3] * b + x;
+			worldVertices[Y3] = offset[X3] * c + offset[Y3] * d + y;
+			worldVertices[X4] = offset[X4] * a + offset[Y4] * b + x;
+			worldVertices[Y4] = offset[X4] * c + offset[Y4] * d + y;
 		}
 	}
 }

+ 114 - 0
spine-csharp/src/Attachments/VertexAttachment.cs

@@ -0,0 +1,114 @@
+/******************************************************************************
+ * Spine Runtimes Software License
+ * Version 2.3
+ * 
+ * Copyright (c) 2013-2015, Esoteric Software
+ * All rights reserved.
+ * 
+ * You are granted a perpetual, non-exclusive, non-sublicensable and
+ * non-transferable license to use, install, execute and perform the Spine
+ * Runtimes Software (the "Software") and derivative works solely for personal
+ * or internal use. Without the written permission of Esoteric Software (see
+ * Section 2 of the Spine Software License Agreement), you may not (a) modify,
+ * translate, adapt or otherwise create derivative works, improvements of the
+ * Software or develop new applications using the Software or (b) remove,
+ * delete, alter or obscure any trademarks or any copyright, trademark, patent
+ * or other intellectual property or proprietary rights notices on or in the
+ * Software, including any copy thereof. Redistributions in binary or source
+ * form must include this license and terms.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) 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 THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+using System.Collections.Generic;
+
+namespace Spine {
+	/// <summary>>An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's vertices.</summary> 
+	public class VertexAttachment : Attachment {
+		internal int[] bones;
+		internal float[] vertices;
+		internal int worldVerticesLength;
+
+		public int[] Bones { get { return bones; } set { bones = value; } }
+		public float[] Vertices { get { return vertices; } set { vertices = value; } }
+		public int WorldVerticesLength { get { return worldVerticesLength; } set { worldVerticesLength = value; } }
+
+		public VertexAttachment (String name)
+			: base(name) {
+		}
+
+		public void ComputeWorldVertices (Slot slot, float[] worldVertices) {
+			ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0);
+		}
+
+		public void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset) {
+			count += offset;
+			Skeleton skeleton = slot.Skeleton;
+			float x = skeleton.x, y = skeleton.y;
+			var deformArray = slot.attachmentVertices;
+			float[] vertices = this.vertices;
+			int[] bones = this.bones;
+			if (bones == null) {
+				if (deformArray.Count > 0) vertices = deformArray.Items;
+				Bone bone = slot.bone;
+				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 += 2) {
+					float vx = vertices[vv], vy = vertices[vv + 1];
+					worldVertices[w] = vx * a + vy * b + x;
+					worldVertices[w + 1] = vx * c + vy * d + y;
+				}
+				return;
+			}
+			int v = 0, skip = 0;
+			for (int i = 0; i < start; i += 2) {
+				int n = bones[v];
+				v += n + 1;
+				skip += n;
+			}
+			Bone[] skeletonBones = skeleton.Bones.Items;
+			if (deformArray.Count == 0) {
+				for (int w = offset, b = skip * 3; w < count; w += 2) {
+					float wx = x, wy = y;
+					for (int n = bones[v++] + v; v < n; v++, b += 3) {
+						Bone bone = skeletonBones[bones[v]];
+						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;
+					}
+					worldVertices[w] = wx;
+					worldVertices[w + 1] = wy;
+				}
+			} else {
+				float[] deform = deformArray.Items;
+				for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += 2) {
+					float wx = x, wy = y;
+					for (int n = bones[v++] + v; v < n; v++, b += 3, f += 2) {
+						Bone bone = skeletonBones[bones[v]];
+						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;
+					}
+					worldVertices[w] = wx;
+					worldVertices[w + 1] = wy;
+				}
+			}
+		}
+
+		/// <summary>Returns true if a deform originally applied to the specified attachment should be applied to this attachment.</summary>
+		virtual public bool ApplyDeform (VertexAttachment sourceAttachment) {
+			return this == sourceAttachment;
+		}			
+	}
+}

+ 0 - 158
spine-csharp/src/Attachments/WeightedMeshAttachment.cs

@@ -1,158 +0,0 @@
-/******************************************************************************
- * Spine Runtimes Software License
- * Version 2.3
- * 
- * Copyright (c) 2013-2015, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to use, install, execute and perform the Spine
- * Runtimes Software (the "Software") and derivative works solely for personal
- * or internal use. Without the written permission of Esoteric Software (see
- * Section 2 of the Spine Software License Agreement), you may not (a) modify,
- * translate, adapt or otherwise create derivative works, improvements of the
- * Software or develop new applications using the Software or (b) remove,
- * delete, alter or obscure any trademarks or any copyright, trademark, patent
- * or other intellectual property or proprietary rights notices on or in the
- * Software, including any copy thereof. Redistributions in binary or source
- * form must include this license and terms.
- * 
- * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
- * OR BUSINESS INTERRUPTION) 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 THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-using System;
-using System.Collections.Generic;
-
-namespace Spine {
-	/// <summary>Attachment that displays a texture region using a mesh which can be deformed by bones.</summary>
-	public class WeightedMeshAttachment : Attachment, IFfdAttachment {
-		internal int[] bones;
-		internal float[] weights, uvs, regionUVs;
-		internal int[] triangles;
-		internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight;
-		internal float r = 1, g = 1, b = 1, a = 1;
-		internal WeightedMeshAttachment parentMesh;
-		internal bool inheritFFD;
-
-		public int HullLength { get; set; }
-		public int[] Bones { get { return bones; } set { bones = value; } }
-		public float[] Weights { get { return weights; } set { weights = value; } }
-		public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } }
-		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 String Path { get; set; }
-		public Object RendererObject { get; set; }
-		public float RegionU { get; set; }
-		public float RegionV { get; set; }
-		public float RegionU2 { get; set; }
-		public float RegionV2 { get; set; }
-		public bool RegionRotate { get; set; }
-		public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } }
-		public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated.
-		public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } }
-		public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size.
-		public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } }
-		public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size.
-
-		public bool InheritFFD { get { return inheritFFD; } set { inheritFFD = value; } }
-
-		public WeightedMeshAttachment ParentMesh {
-			get { return parentMesh; }
-			set {
-				parentMesh = value;
-				if (value != null) {
-					bones = value.bones;
-					weights = value.weights;
-					regionUVs = value.regionUVs;
-					triangles = value.triangles;
-					HullLength = value.HullLength;
-					Edges = value.Edges;
-					Width = value.Width;
-					Height = value.Height;
-				}
-			}
-		}
-
-		// Nonessential.
-		public int[] Edges { get; set; }
-		public float Width { get; set; }
-		public float Height { get; set; }
-
-		public WeightedMeshAttachment (string name)
-			: base(name) {
-		}
-
-		public void UpdateUVs () {
-			float u = RegionU, v = RegionV, width = RegionU2 - RegionU, height = RegionV2 - RegionV;
-			float[] regionUVs = this.regionUVs;
-			if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length];
-			float[] uvs = this.uvs;
-			if (RegionRotate) {
-				for (int i = 0, n = uvs.Length; i < n; i += 2) {
-					uvs[i] = u + regionUVs[i + 1] * width;
-					uvs[i + 1] = v + height - regionUVs[i] * height;
-				}
-			} else {
-				for (int i = 0, n = uvs.Length; i < n; i += 2) {
-					uvs[i] = u + regionUVs[i] * width;
-					uvs[i + 1] = v + regionUVs[i + 1] * height;
-				}
-			}
-		}
-
-		public void ComputeWorldVertices (Slot slot, float[] worldVertices) {
-			Skeleton skeleton = slot.bone.skeleton;
-			ExposedList<Bone> skeletonBones = skeleton.bones;
-			float x = skeleton.x, y = skeleton.y;
-			float[] weights = this.weights;
-			int[] bones = this.bones;
-			if (slot.attachmentVerticesCount == 0) {
-				for (int w = 0, v = 0, b = 0, n = bones.Length; v < n; w += 2) {
-					float wx = 0, wy = 0;
-					int nn = bones[v++] + v;
-					for (; v < nn; v++, b += 3) {
-						Bone bone = skeletonBones.Items[bones[v]];
-						float vx = weights[b], vy = weights[b + 1], weight = weights[b + 2];
-						wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight;
-						wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight;
-					}
-					worldVertices[w] = wx + x;
-					worldVertices[w + 1] = wy + y;
-				}
-			} else {
-				float[] ffd = slot.attachmentVertices;
-				for (int w = 0, v = 0, b = 0, f = 0, n = bones.Length; v < n; w += 2) {
-					float wx = 0, wy = 0;
-					int nn = bones[v++] + v;
-					for (; v < nn; v++, b += 3, f += 2) {
-						Bone bone = skeletonBones.Items[bones[v]];
-						float vx = weights[b] + ffd[f], vy = weights[b + 1] + ffd[f + 1], weight = weights[b + 2];
-						wx += (vx * bone.a + vy * bone.b + bone.worldX) * weight;
-						wy += (vx * bone.c + vy * bone.d + bone.worldY) * weight;
-					}
-					worldVertices[w] = wx + x;
-					worldVertices[w + 1] = wy + y;
-				}
-			}
-		}
-
-		public bool ApplyFFD (Attachment sourceAttachment) {
-			return this == sourceAttachment || (inheritFFD && parentMesh == sourceAttachment);
-		}
-	}
-}

+ 96 - 27
spine-csharp/src/Bone.cs

@@ -30,7 +30,6 @@
  *****************************************************************************/
 
 using System;
-using System.Collections.Generic;
 
 namespace Spine {
 	public class Bone : IUpdatable {
@@ -41,12 +40,14 @@ namespace Spine {
 		internal Bone parent;
 		internal ExposedList<Bone> children = new ExposedList<Bone>();
 		internal float x, y, rotation, scaleX, scaleY, shearX, shearY;
-		internal float appliedRotation, appliedScaleX, appliedScaleY;
+		internal float appliedRotation;
 
 		internal float a, b, worldX;
 		internal float c, d, worldY;
 		internal float worldSignX, worldSignY;
 
+		internal bool sorted;
+
 		public BoneData Data { get { return data; } }
 		public Skeleton Skeleton { get { return skeleton; } }
 		public Bone Parent { get { return parent; } }
@@ -56,10 +57,6 @@ namespace Spine {
 		public float Rotation { get { return rotation; } set { rotation = value; } }
 		/// <summary>The rotation, as calculated by any constraints.</summary>
 		public float AppliedRotation { get { return appliedRotation; } set { appliedRotation = value; } }
-		/// <summary>The scale X, as calculated by any constraints.</summary>
-		public float AppliedScaleX { get { return appliedScaleX; } set { appliedScaleX = value; } }
-		/// <summary>The scale Y, as calculated by any constraints.</summary>
-		public float AppliedScaleY { get { return appliedScaleY; } set { appliedScaleY = value; } }
 		public float ScaleX { get { return scaleX; } set { scaleX = value; } }
 		public float ScaleY { get { return scaleY; } set { scaleY = value; } }
 		public float ShearX { get { return shearX; } set { shearX = value; } }
@@ -80,29 +77,27 @@ namespace Spine {
 
 		/// <param name="parent">May be null.</param>
 		public Bone (BoneData data, Skeleton skeleton, Bone parent) {
-			if (data == null) throw new ArgumentNullException("data cannot be null.");
-			if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null.");
+			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>Same as {@link #updateWorldTransform()}. This method exists for Bone to implement {@link Updatable}.</summary>
+		/// <summary>Same as <see cref="UpdateWorldTransform"/>. This method exists for Bone to implement <see cref="Spine.IUpdatable"/>.</summary>
 		public void Update () {
 			UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY);
 		}
 
-		/// <summary>Computes the world SRT using the parent bone and this bone's local SRT.</summary>
+		/// <summary>Computes the world 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 SRT using the parent bone and the specified local SRT.</summary>
+		/// <summary>Computes the world transform using the parent bone and the specified local transform.</summary>
 		public void UpdateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) {
 			appliedRotation = rotation;
-			appliedScaleX = scaleX;
-			appliedScaleY = scaleY;
 
 			float rotationY = rotation + 90 + shearY;
 			float la = MathUtils.CosDeg(rotation + shearX) * scaleX, lb = MathUtils.CosDeg(rotationY) * scaleY;
@@ -152,10 +147,10 @@ namespace Spine {
 					do {
 						float cos = MathUtils.CosDeg(parent.appliedRotation), sin = MathUtils.SinDeg(parent.appliedRotation);
 						float temp = pa * cos + pb * sin;
-						pb = pa * -sin + pb * cos;
+						pb = pb * cos - pa * sin;
 						pa = temp;
 						temp = pc * cos + pd * sin;
-						pd = pc * -sin + pd * cos;
+						pd = pd * cos - pc * sin;
 						pc = temp;
 
 						if (!parent.data.inheritRotation) break;
@@ -171,24 +166,22 @@ namespace Spine {
 					pc = 0;
 					pd = 1;
 					do {
-						float r = parent.appliedRotation, cos = MathUtils.CosDeg(r), sin = MathUtils.SinDeg(r);
-						float psx = parent.appliedScaleX, psy = parent.appliedScaleY;
-						float za = cos * psx, zb = -sin * psy, zc = sin * psx, zd = cos * psy;
+						float cos = MathUtils.CosDeg(parent.appliedRotation), sin = MathUtils.SinDeg(parent.appliedRotation);
+						float psx = parent.scaleX, psy = parent.scaleY;
+						float za = cos * psx, zb = sin * psy, zc = sin * psx, zd = cos * psy;
 						float temp = pa * za + pb * zc;
-						pb = pa * zb + pb * zd;
+						pb = pb * zd - pa * zb;
 						pa = temp;
 						temp = pc * za + pd * zc;
-						pd = pc * zb + pd * zd;
+						pd = pd * zd - pc * zb;
 						pc = temp;
 
-						if (psx < 0) r = -r;
-						cos = MathUtils.CosDeg(-r);
-						sin = MathUtils.SinDeg(-r);
+						if (psx >= 0) sin = -sin;
 						temp = pa * cos + pb * sin;
-						pb = pa * -sin + pb * cos;
+						pb = pb * cos - pa * sin;
 						pa = temp;
 						temp = pc * cos + pd * sin;
-						pd = pc * -sin + pd * cos;
+						pd = pd * cos - pc * sin;
 						pc = temp;
 
 						if (!parent.data.inheritScale) break;
@@ -226,10 +219,86 @@ namespace Spine {
 			shearY = data.shearY;
 		}
 
-		public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) {
-			float x = worldX - this.worldX, y = worldY - this.worldY;
+		public float WorldToLocalRotationX {
+			get {
+				Bone parent = this.parent;
+				if (parent == null) return rotation;
+				float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c;
+				return MathUtils.Atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.radDeg;
+			}
+		}
+
+		public float WorldToLocalRotationY {
+			get {
+				Bone parent = this.parent;
+				if (parent == null) return rotation;
+				float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d;
+				return MathUtils.Atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.radDeg;
+			}
+		}
+
+		public void RotateWorld (float degrees) {
+			float a = this.a, b = this.b, c = this.c, d = this.d;
+			float cos = MathUtils.CosDeg(degrees), sin = MathUtils.SinDeg(degrees);
+			this.a = cos * a - sin * c;
+			this.b = cos * b - sin * d;
+			this.c = sin * a + cos * c;
+			this.d = sin * b + cos * d;
+		}
+
+		/// <summary>
+		/// Computes the local transform from the world transform. This can be useful to perform processing on the local transform
+		/// after the world transform has been modified directly (eg, by a constraint).
+		/// 
+		/// Some redundant information is lost by the world transform, such as -1,-1 scale versus 180 rotation. The computed local
+		/// transform values may differ from the original values but are functionally the same.
+		/// </summary>
+		public void UpdateLocalTransform () {
+			Bone parent = this.parent;
+			if (parent == null) {
+				x = worldX;
+				y = worldY;
+				rotation = MathUtils.Atan2(c, a) * MathUtils.radDeg;
+				scaleX = (float)Math.Sqrt(a * a + c * c);
+				scaleY = (float)Math.Sqrt(b * b + d * d);
+				float det = a * d - b * c;
+				shearX = 0;
+				shearY = MathUtils.Atan2(a * b + c * d, det) * MathUtils.radDeg;
+				return;
+			}
+			float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
+			float pid = 1 / (pa * pd - pb * pc);
+			float dx = worldX - parent.worldX, dy = worldY - parent.worldY;
+			x = (dx * pd * pid - dy * pb * pid);
+			y = (dy * pa * pid - dx * pc * pid);
+			float ia = pid * pd;
+			float id = pid * pa;
+			float ib = pid * pb;
+			float ic = pid * pc;
+			float ra = ia * a - ib * c;
+			float rb = ia * b - ib * d;
+			float rc = id * c - ic * a;
+			float 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.Atan2(ra * rb + rc * rd, det) * MathUtils.radDeg;
+				rotation = MathUtils.Atan2(rc, ra) * MathUtils.radDeg;
+			} else {
+				scaleX = 0;
+				scaleY = (float)Math.Sqrt(rb * rb + rd * rd);
+				shearY = 0;
+				rotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.radDeg;
+			}
+			appliedRotation = rotation;
+		}
+
+		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 invDet = 1 / (a * d - b * c);
+			float x = worldX - this.worldX, y = worldY - this.worldY;
 			localX = (x * d * invDet - y * b * invDet);
 			localY = (y * a * invDet - x * c * invDet);
 		}

+ 12 - 7
spine-csharp/src/BoneData.cs

@@ -33,14 +33,17 @@ using System;
 
 namespace Spine {
 	public class BoneData {
-		internal BoneData parent;
+		internal int index;
 		internal String name;
-		internal float length, x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY;
-		internal bool inheritScale = true, inheritRotation = true;
+		internal BoneData parent;
+		internal float length;
+		internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY;
+		internal bool inheritRotation = true, inheritScale = true;
 
 		/// <summary>May be null.</summary>
-		public BoneData Parent { get { return parent; } }
+		public int Index { get { return index; } }
 		public String Name { get { return name; } }
+		public BoneData Parent { get { return parent; } }
 		public float Length { get { return length; } set { length = value; } }
 		public float X { get { return x; } set { x = value; } }
 		public float Y { get { return y; } set { y = value; } }
@@ -49,12 +52,14 @@ namespace Spine {
 		public float ScaleY { get { return scaleY; } set { scaleY = value; } }
 		public float ShearX { get { return shearX; } set { shearX = value; } }
 		public float ShearY { get { return shearY; } set { shearY = value; } }
-		public bool InheritScale { get { return inheritScale; } set { inheritScale = value; } }
 		public bool InheritRotation { get { return inheritRotation; } set { inheritRotation = value; } }
+		public bool InheritScale { get { return inheritScale; } set { inheritScale = value; } }
 
 		/// <param name="parent">May be null.</param>
-		public BoneData (String name, BoneData parent) {
-			if (name == null) throw new ArgumentNullException("name cannot be null.");
+		public BoneData (int index, String name, BoneData parent) {
+			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;
 		}

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

@@ -40,6 +40,7 @@ namespace Spine {
 		public float Time { get; private set; }
 
 		public Event (float time, EventData data) {
+			if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
 			Time = time;
 			Data = data;
 		}

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

@@ -41,7 +41,7 @@ namespace Spine {
 		public String String { get; set; }
 
 		public EventData (String name) {
-			if (name == null) throw new ArgumentNullException("name cannot be null.");
+			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
 			this.name = name;
 		}
 

+ 7 - 1
spine-csharp/src/ExposedList.cs

@@ -89,6 +89,12 @@ namespace Spine {
 				Capacity = Math.Max(Math.Max(Capacity * 2, DefaultCapacity), minimumSize);
 		}
 
+		public ExposedList<T> Resize (int newSize) {
+			if (newSize > Items.Length) Array.Resize(ref Items, newSize);
+			Count = newSize;
+			return this;
+		}
+
 		private void CheckRange (int idx, int count) {
 			if (idx < 0)
 				throw new ArgumentOutOfRangeException("index");
@@ -580,4 +586,4 @@ namespace Spine {
 			}
 		}
 	}
-}
+}

+ 56 - 39
spine-csharp/src/IkConstraint.cs

@@ -30,15 +30,16 @@
  *****************************************************************************/
 
 using System;
-using System.Collections.Generic;
 
 namespace Spine {
 	public class IkConstraint : IUpdatable {
 		internal IkConstraintData data;
 		internal ExposedList<Bone> bones = new ExposedList<Bone>();
 		internal Bone target;
-		internal int bendDirection;
 		internal float mix;
+		internal int bendDirection;
+
+		internal int level;
 
 		public IkConstraintData Data { get { return data; } }
 		public ExposedList<Bone> Bones { get { return bones; } }
@@ -47,8 +48,8 @@ namespace Spine {
 		public float Mix { get { return mix; } set { mix = value; } }
 
 		public IkConstraint (IkConstraintData data, Skeleton skeleton) {
-			if (data == null) throw new ArgumentNullException("data cannot be null.");
-			if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null.");
+			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;
 			mix = data.mix;
 			bendDirection = data.bendDirection;
@@ -87,21 +88,24 @@ namespace Spine {
 			float id = 1 / (pp.a * pp.d - pp.b * pp.c);
 			float x = targetX - pp.worldX, y = targetY - pp.worldY;
 			float tx = (x * pp.d - y * pp.b) * id - bone.x, ty = (y * pp.a - x * pp.c) * id - bone.y;
-			float rotationIK = MathUtils.Atan2(ty, tx) * MathUtils.radDeg - bone.shearX;
+			float rotationIK = MathUtils.Atan2(ty, tx) * MathUtils.radDeg - bone.shearX - bone.rotation;
 			if (bone.scaleX < 0) rotationIK += 180;
 			if (rotationIK > 180)
 				rotationIK -= 360;
 			else if (rotationIK < -180) rotationIK += 360;
-			bone.UpdateWorldTransform(bone.x, bone.y, bone.rotation + (rotationIK - bone.rotation) * alpha, bone.appliedScaleX,
-				bone.appliedScaleY, bone.shearX, bone.shearY);
+			bone.UpdateWorldTransform(bone.x, bone.y, bone.rotation + rotationIK * alpha, bone.scaleX, bone.scaleY,
+				bone.shearX, bone.shearY);
 		}
 
 		/// <summary>Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as
 		/// possible. 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, float alpha) {
-			if (alpha == 0) return;
-			float px = parent.x, py = parent.y, psx = parent.appliedScaleX, psy = parent.appliedScaleY;
+			if (alpha == 0) {
+				child.UpdateWorldTransform ();
+				return;
+			}
+			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;
@@ -115,44 +119,55 @@ namespace Spine {
 				psy = -psy;
 				s2 = -s2;
 			}
-			float cx = child.x, cy = child.y, csx = child.appliedScaleX;
-			bool u = Math.Abs(psx - psy) <= 0.0001f;
-			if (!u && cy != 0) {
-				child.worldX = parent.a * cx + parent.worldX;
-				child.worldY = parent.c * cx + parent.worldY;
-				cy = 0;
-			}
 			if (csx < 0) {
 				csx = -csx;
 				os2 = 180;
 			} else
 				os2 = 0;
+			float cx = child.x, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d;
+			bool u = Math.Abs(psx - psy) <= 0.0001f;
+			if (!u) {
+				cy = 0;
+				cwx = a * cx + parent.worldX;
+				cwy = c * cx + parent.worldY;
+			} else {
+				cy = child.y;
+				cwx = a * cx + b * cy + parent.worldX;
+				cwy = c * cx + d * cy + parent.worldY;
+			}
 			Bone pp = parent.parent;
-			float ppa = pp.a, ppb = pp.b, ppc = pp.c, ppd = pp.d, id = 1 / (ppa * ppd - ppb * ppc);
-			float x = targetX - pp.worldX, y = targetY - pp.worldY;
-			float tx = (x * ppd - y * ppb) * id - px, ty = (y * ppa - x * ppc) * id - py;
-			x = child.worldX - pp.worldX;
-			y = child.worldY - pp.worldY;
-			float dx = (x * ppd - y * ppb) * id - px, dy = (y * ppa - x * ppc) * id - py;
+			a = pp.a;
+			b = pp.b;
+			c = pp.c;
+			d = pp.d;
+			float id = 1 / (a * d - b * c), x = targetX - pp.worldX, y = targetY - pp.worldY;
+			float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py;
+			x = cwx - pp.worldX;
+			y = cwy - pp.worldY;
+			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;
 			if (u) {
 				l2 *= psx;
 				float cos = (tx * tx + ty * ty - l1 * l1 - l2 * l2) / (2 * l1 * l2);
-				if (cos < -1) cos = -1;
+				if (cos < -1)
+					cos = -1;
 				else if (cos > 1) cos = 1;
 				a2 = (float)Math.Acos(cos) * bendDir;
-				float a = l1 + l2 * cos, o = l2 * MathUtils.Sin(a2);
-				a1 = MathUtils.Atan2(ty * a - tx * o, tx * a + ty * o);
+				a = l1 + l2 * cos;
+				b = l2 * MathUtils.Sin(a2);
+				a1 = MathUtils.Atan2(ty * a - tx * b, tx * a + ty * b);
 			} else {
-				float a = psx * l2, b = psy * l2, ta = MathUtils.Atan2(ty, tx);
-				float aa = a * a, bb = b * b, ll = l1 * l1, dd = tx * tx + ty * ty;
-				float c0 = bb * ll + aa * dd - aa * bb, c1 = -2 * bb * l1, c2 = bb - aa;
-				float d = c1 * c1 - 4 * c2 * c0;
+				a = psx * l2;
+				b = psy * l2;
+				float aa = a * a, bb = b * b, dd = tx * tx + ty * ty, ta = MathUtils.Atan2(ty, tx);
+				c = bb * l1 * l1 + aa * dd - aa * bb;
+				float c1 = -2 * bb * l1, c2 = bb - aa;
+				d = c1 * c1 - 4 * c2 * c;
 				if (d >= 0) {
 					float q = (float)Math.Sqrt(d);
 					if (c1 < 0) q = -q;
 					q = -(c1 + q) / 2;
-					float r0 = q / c2, r1 = c0 / q;
+					float r0 = q / c2, r1 = c / q;
 					float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1;
 					if (r * r <= dd) {
 						y = (float)Math.Sqrt(dd - r * r) * bendDir;
@@ -201,18 +216,20 @@ namespace Spine {
 					a2 = maxAngle * bendDir;
 				}
 			}
-		outer:
+			outer:
 			float os = MathUtils.Atan2(cy, cx) * s2;
-			a1 = (a1 - os) * MathUtils.radDeg + os1;
-			a2 = ((a2 + os) * MathUtils.radDeg - child.shearX) * s2 + os2;
-			if (a1 > 180) a1 -= 360;
-			else if (a1 < -180) a1 += 360;
-			if (a2 > 180) a2 -= 360;
-			else if (a2 < -180) a2 += 360;
 			float rotation = parent.rotation;
-			parent.UpdateWorldTransform(px, py, rotation + (a1 - rotation) * alpha, parent.appliedScaleX, parent.appliedScaleY, 0, 0);
+			a1 = (a1 - os) * MathUtils.radDeg + os1 - rotation;
+			if (a1 > 180)
+				a1 -= 360;
+			else if (a1 < -180) a1 += 360;
+			parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, parent.scaleX, parent.scaleY, 0, 0);
 			rotation = child.rotation;
-			child.UpdateWorldTransform(cx, cy, rotation + (a2 - rotation) * alpha, child.appliedScaleX, child.appliedScaleY, child.shearX, child.shearY);
+			a2 = ((a2 + os) * MathUtils.radDeg - child.shearX) * s2 + os2 - rotation;
+			if (a2 > 180)
+				a2 -= 360;
+			else if (a2 < -180) a2 += 360;
+			child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.scaleX, child.scaleY, child.shearX, child.shearY);
 		}
 	}
 }

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

@@ -47,7 +47,7 @@ namespace Spine {
 		public float Mix { get { return mix; } set { mix = value; } }
 
 		public IkConstraintData (String name) {
-			if (name == null) throw new ArgumentNullException("name cannot be null.");
+			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
 			this.name = name;
 		}
 

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

@@ -54,22 +54,22 @@ namespace Spine {
 				sin[(int)(i * degToIndex) & SIN_MASK] = (float)Math.Sin(i * degRad);
 		}
 
-		/** Returns the sine in radians from a lookup table. */
+		/// <summary>Returns the sine in radians from a lookup table.</summary>
 		static public float Sin (float radians) {
 			return sin[(int)(radians * radToIndex) & SIN_MASK];
 		}
 
-		/** Returns the cosine in radians from a lookup table. */
+		/// <summary>Returns the cosine in radians from a lookup table.</summary>
 		static public float Cos (float radians) {
 			return sin[(int)((radians + PI / 2) * radToIndex) & SIN_MASK];
 		}
-
-		/** Returns the sine in radians from a lookup table. */
+			
+		/// <summary>Returns the sine in radians from a lookup table.</summary>
 		static public float SinDeg (float degrees) {
 			return sin[(int)(degrees * degToIndex) & SIN_MASK];
 		}
-
-		/** Returns the cosine in radians from a lookup table. */
+			
+		/// <summary>Returns the cosine in radians from a lookup table.</summary>
 		static public float CosDeg (float degrees) {
 			return sin[(int)((degrees + 90) * degToIndex) & SIN_MASK];
 		}
@@ -91,5 +91,11 @@ namespace Spine {
 			atan = PI / 2 - z / (z * z + 0.28f);
 			return y < 0f ? atan - PI : atan;
 		}
+
+		static public float Clamp (float value, float min, float max) {
+			if (value < min) return min;
+			if (value > max) return max;
+			return value;
+		}
 	}
 }

+ 400 - 0
spine-csharp/src/PathConstraint.cs

@@ -0,0 +1,400 @@
+/******************************************************************************
+ * Spine Runtimes Software License
+ * Version 2.3
+ * 
+ * Copyright (c) 2013-2015, Esoteric Software
+ * All rights reserved.
+ * 
+ * You are granted a perpetual, non-exclusive, non-sublicensable and
+ * non-transferable license to use, install, execute and perform the Spine
+ * Runtimes Software (the "Software") and derivative works solely for personal
+ * or internal use. Without the written permission of Esoteric Software (see
+ * Section 2 of the Spine Software License Agreement), you may not (a) modify,
+ * translate, adapt or otherwise create derivative works, improvements of the
+ * Software or develop new applications using the Software or (b) remove,
+ * delete, alter or obscure any trademarks or any copyright, trademark, patent
+ * or other intellectual property or proprietary rights notices on or in the
+ * Software, including any copy thereof. Redistributions in binary or source
+ * form must include this license and terms.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) 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 THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+	public class PathConstraint : IUpdatable {
+		private const int NONE = -1, BEFORE = -2, AFTER = -3;
+
+		internal PathConstraintData data;
+		internal ExposedList<Bone> bones;
+		internal Slot target;
+		internal float position, spacing, rotateMix, translateMix;
+
+		internal ExposedList<float> spaces = new ExposedList<float>(), positions = new ExposedList<float>();
+		internal ExposedList<float> world = new ExposedList<float>(), curves = new ExposedList<float>(), lengths = new ExposedList<float>();
+		internal float[] segments = new float[10];
+
+		public float Position { get { return position; } set { position = value; } }
+		public float Spacing { get { return spacing; } set { spacing = value; } }
+		public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
+		public float TranslateMix { get { return translateMix; } set { translateMix = value; } }
+		public ExposedList<Bone> Bones { get { return bones; } }
+		public Slot Target { get { return target; } set { target = value; } }
+		public PathConstraintData Data { get { return data; } }
+
+		public PathConstraint (PathConstraintData data, Skeleton skeleton) {
+			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;
+			bones = new ExposedList<Bone>(data.Bones.Count);
+			foreach (BoneData boneData in data.bones)
+				bones.Add(skeleton.FindBone(boneData.name));
+			target = skeleton.FindSlot(data.target.name);
+			position = data.position;
+			spacing = data.spacing;
+			rotateMix = data.rotateMix;
+			translateMix = data.translateMix;
+		}
+
+		public void Apply () {
+			Update();
+		}
+			
+		public void Update () {
+			PathAttachment attachment = target.Attachment as PathAttachment;
+			if (attachment == null) return;
+
+			float rotateMix = this.rotateMix, translateMix = this.translateMix;
+			bool translate = translateMix > 0, rotate = rotateMix > 0;
+			if (!translate && !rotate) return;
+
+			PathConstraintData data = this.data;
+			SpacingMode spacingMode = data.spacingMode;
+			bool lengthSpacing = spacingMode == SpacingMode.Length;
+			RotateMode rotateMode = data.rotateMode;
+			bool tangents = rotateMode == RotateMode.Tangent, scale = rotateMode == RotateMode.ChainScale;
+			int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1;
+			Bone[] bones = this.bones.Items;
+			ExposedList<float> spaces = this.spaces.Resize(spacesCount), lengths = null;
+			float spacing = this.spacing;
+			if (scale || lengthSpacing) {
+				if (scale) lengths = this.lengths.Resize(boneCount);
+				for (int i = 0, n = spacesCount - 1; i < n;) {
+					Bone bone = bones[i];
+					float length = bone.data.length, x = length * bone.a, y = length * bone.c;
+					length = (float)Math.Sqrt(x * x + y * y);
+					if (scale) lengths.Items[i] = length;
+					spaces.Items[++i] = lengthSpacing ? Math.Max(0, length + spacing) : spacing;
+				}
+			} else {
+				for (int i = 1; i < spacesCount; i++)
+					spaces.Items[i] = spacing;
+			}
+
+			float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents,
+				data.positionMode == PositionMode.Percent, spacingMode == SpacingMode.Percent);
+			Skeleton skeleton = target.Skeleton;
+			float skeletonX = skeleton.x, skeletonY = skeleton.y;
+			float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation;
+			bool tip = rotateMode == RotateMode.Chain && offsetRotation == 0;
+			for (int i = 0, p = 3; i < boneCount; i++, p += 3) {
+				Bone bone = (Bone)bones[i];
+				bone.worldX += (boneX - skeletonX - bone.worldX) * translateMix;
+				bone.worldY += (boneY - skeletonY - bone.worldY) * translateMix;
+				float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY;
+				if (scale) {
+					float length = lengths.Items[i];
+					if (length != 0) {
+						float s = ((float)Math.Sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1;
+						bone.a *= s;
+						bone.c *= s;
+					}
+				}
+				boneX = x;
+				boneY = y;
+				if (rotate) {
+					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.Items[i + 1] == 0)
+						r = positions[p + 2];
+					else
+						r = MathUtils.Atan2(dy, dx);
+					r -= MathUtils.Atan2(c, a) - offsetRotation * MathUtils.degRad;
+					if (tip) {
+						cos = MathUtils.Cos(r);
+						sin = MathUtils.Sin(r);
+						float length = bone.data.length;
+						boneX += (length * (cos * a - sin * c) - dx) * rotateMix;
+						boneY += (length * (sin * a + cos * c) - dy) * rotateMix;
+					}
+					if (r > MathUtils.PI)
+						r -= MathUtils.PI2;
+					else if (r < -MathUtils.PI) //
+						r += MathUtils.PI2;
+					r *= rotateMix;
+					cos = MathUtils.Cos(r);
+					sin = MathUtils.Sin(r);
+					bone.a = cos * a - sin * c;
+					bone.b = cos * b - sin * d;
+					bone.c = sin * a + cos * c;
+					bone.d = sin * b + cos * d;
+				}
+			}
+		}
+
+		float[] ComputeWorldPositions (PathAttachment path, int spacesCount, bool tangents, bool percentPosition,
+			bool percentSpacing) {
+
+			Slot target = this.target;
+			float position = this.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;
+
+			float pathLength;
+			if (!path.ConstantSpeed) {
+				float[] lengths = path.Lengths;
+				curveCount -= closed ? 1 : 2;
+				pathLength = lengths[curveCount];
+				if (percentPosition) position *= pathLength;
+				if (percentSpacing) {
+					for (int i = 0; i < spacesCount; i++)
+						spaces[i] *= pathLength;
+				}
+				world = this.world.Resize(8).Items;
+				for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) {
+					float space = spaces[i];
+					position += space;
+					float p = position;
+
+					if (closed) {
+						p %= pathLength;
+						if (p < 0) p += pathLength;
+						curve = 0;
+					} else if (p < 0) {
+						if (prevCurve != BEFORE) {
+							prevCurve = BEFORE;
+							path.ComputeWorldVertices(target, 2, 4, world, 0);
+						}
+						AddBeforePosition(p, world, 0, output, o);
+						continue;
+					} else if (p > pathLength) {
+						if (prevCurve != AFTER) {
+							prevCurve = AFTER;
+							path.ComputeWorldVertices(target, verticesLength - 6, 4, world, 0);
+						}
+						AddAfterPosition(p - pathLength, world, 0, output, o);
+						continue;
+					}
+
+					// Determine curve containing position.
+					for (;; curve++) {
+						float length = lengths[curve];
+						if (p > length) continue;
+						if (curve == 0)
+							p /= length;
+						else {
+							float prev = lengths[curve - 1];
+							p = (p - prev) / (length - prev);
+						}
+						break;
+					}
+					if (curve != prevCurve) {
+						prevCurve = curve;
+						if (closed && curve == curveCount) {
+							path.ComputeWorldVertices(target, verticesLength - 4, 4, world, 0);
+							path.ComputeWorldVertices(target, 0, 4, world, 4);
+						} else
+							path.ComputeWorldVertices(target, curve * 6 + 2, 8, world, 0);
+					}
+					AddCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], output, o,
+						tangents || (i > 0 && space == 0));
+				}
+				return output;
+			}
+
+			// World vertices.
+			if (closed) {
+				verticesLength += 2;
+				world = this.world.Resize(verticesLength).Items;
+				path.ComputeWorldVertices(target, 2, verticesLength - 4, world, 0);
+				path.ComputeWorldVertices(target, 0, 2, world, verticesLength - 4);
+				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);
+			}
+
+			// Curve lengths.
+			float[] curves = this.curves.Resize(curveCount).Items;
+			pathLength = 0;
+			float x1 = world[0], y1 = world[1], cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0, x2 = 0, y2 = 0;
+			float tmpx, tmpy, dddfx, dddfy, ddfx, ddfy, dfx, dfy;
+			for (int i = 0, w = 2; i < curveCount; i++, w += 6) {
+				cx1 = world[w];
+				cy1 = world[w + 1];
+				cx2 = world[w + 2];
+				cy2 = world[w + 3];
+				x2 = world[w + 4];
+				y2 = world[w + 5];
+				tmpx = (x1 - cx1 * 2 + cx2) * 0.1875f;
+				tmpy = (y1 - cy1 * 2 + cy2) * 0.1875f;
+				dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375f;
+				dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375f;
+				ddfx = tmpx * 2 + dddfx;
+				ddfy = tmpy * 2 + dddfy;
+				dfx = (cx1 - x1) * 0.75f + tmpx + dddfx * 0.16666667f;
+				dfy = (cy1 - y1) * 0.75f + tmpy + dddfy * 0.16666667f;
+				pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy);
+				dfx += ddfx;
+				dfy += ddfy;
+				ddfx += dddfx;
+				ddfy += dddfy;
+				pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy);
+				dfx += ddfx;
+				dfy += ddfy;
+				pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy);
+				dfx += ddfx + dddfx;
+				dfy += ddfy + dddfy;
+				pathLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy);
+				curves[i] = pathLength;
+				x1 = x2;
+				y1 = y2;
+			}
+			if (percentPosition) position *= pathLength;
+			if (percentSpacing) {
+				for (int i = 0; i < spacesCount; i++)
+					spaces[i] *= pathLength;
+			}
+
+			float[] segments = this.segments;
+			float curveLength = 0;
+			for (int i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) {
+				float space = spaces[i];
+				position += space;
+				float p = position;
+
+				if (closed) {
+					p %= pathLength;
+					if (p < 0) p += pathLength;
+					curve = 0;
+				} else if (p < 0) {
+					AddBeforePosition(p, world, 0, output, o);
+					continue;
+				} else if (p > pathLength) {
+					AddAfterPosition(p - pathLength, world, verticesLength - 4, output, o);
+					continue;
+				}
+
+				// Determine curve containing position.
+				for (;; curve++) {
+					float length = curves[curve];
+					if (p > length) continue;
+					if (curve == 0)
+						p /= length;
+					else {
+						float prev = curves[curve - 1];
+						p = (p - prev) / (length - prev);
+					}
+					break;
+				}
+
+				// Curve segment lengths.
+				if (curve != prevCurve) {
+					prevCurve = curve;
+					int ii = curve * 6;
+					x1 = world[ii];
+					y1 = world[ii + 1];
+					cx1 = world[ii + 2];
+					cy1 = world[ii + 3];
+					cx2 = world[ii + 4];
+					cy2 = world[ii + 5];
+					x2 = world[ii + 6];
+					y2 = world[ii + 7];
+					tmpx = (x1 - cx1 * 2 + cx2) * 0.03f;
+					tmpy = (y1 - cy1 * 2 + cy2) * 0.03f;
+					dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006f;
+					dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.006f;
+					ddfx = tmpx * 2 + dddfx;
+					ddfy = tmpy * 2 + dddfy;
+					dfx = (cx1 - x1) * 0.3f + tmpx + dddfx * 0.16666667f;
+					dfy = (cy1 - y1) * 0.3f + tmpy + dddfy * 0.16666667f;
+					curveLength = (float)Math.Sqrt(dfx * dfx + dfy * dfy);
+					segments[0] = curveLength;
+					for (ii = 1; ii < 8; ii++) {
+						dfx += ddfx;
+						dfy += ddfy;
+						ddfx += dddfx;
+						ddfy += dddfy;
+						curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy);
+						segments[ii] = curveLength;
+					}
+					dfx += ddfx;
+					dfy += ddfy;
+					curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy);
+					segments[8] = curveLength;
+					dfx += ddfx + dddfx;
+					dfy += ddfy + dddfy;
+					curveLength += (float)Math.Sqrt(dfx * dfx + dfy * dfy);
+					segments[9] = curveLength;
+					segment = 0;
+				}
+
+				// Weight by segment length.
+				p *= curveLength;
+				for (;; segment++) {
+					float length = segments[segment];
+					if (p > length) continue;
+					if (segment == 0)
+						p /= length;
+					else {
+						float prev = segments[segment - 1];
+						p = segment + (p - prev) / (length - prev);
+					}
+					break;
+				}
+				AddCurvePosition(p * 0.1f, x1, y1, cx1, cy1, cx2, cy2, x2, y2, output, o, tangents || (i > 0 && space == 0));
+			}
+			return output;
+		}
+
+		private void AddBeforePosition (float p, float[] temp, int i, float[] output, int o) {
+			float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = MathUtils.Atan2(dy, dx);
+			output[o] = x1 + p * MathUtils.Cos(r);
+			output[o + 1] = y1 + p * MathUtils.Sin(r);
+			output[o + 2] = r;
+		}
+
+		private void AddAfterPosition (float p, float[] temp, int i, float[] output, int o) {
+			float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = MathUtils.Atan2(dy, dx);
+			output[o] = x1 + p * MathUtils.Cos(r);
+			output[o + 1] = y1 + p * MathUtils.Sin(r);
+			output[o + 2] = r;
+		}
+
+		private void AddCurvePosition (float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2,
+			float[] output, int o, bool tangents) {
+			if (p == 0) p = 0.0001f;
+			float tt = p * p, ttt = tt * p, u = 1 - p, uu = u * u, uuu = uu * u;
+			float ut = u * p, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * p;
+			float x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt;
+			output[o] = x;
+			output[o + 1] = y;
+			if (tangents) output[o + 2] = (float)Math.Atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt));
+		}
+	}
+}

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

@@ -0,0 +1,74 @@
+/******************************************************************************
+ * Spine Runtimes Software License
+ * Version 2.3
+ * 
+ * Copyright (c) 2013-2015, Esoteric Software
+ * All rights reserved.
+ * 
+ * You are granted a perpetual, non-exclusive, non-sublicensable and
+ * non-transferable license to use, install, execute and perform the Spine
+ * Runtimes Software (the "Software") and derivative works solely for personal
+ * or internal use. Without the written permission of Esoteric Software (see
+ * Section 2 of the Spine Software License Agreement), you may not (a) modify,
+ * translate, adapt or otherwise create derivative works, improvements of the
+ * Software or develop new applications using the Software or (b) remove,
+ * delete, alter or obscure any trademarks or any copyright, trademark, patent
+ * or other intellectual property or proprietary rights notices on or in the
+ * Software, including any copy thereof. Redistributions in binary or source
+ * form must include this license and terms.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) 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 THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+	public class PathConstraintData {
+		internal String name;
+		internal ExposedList<BoneData> bones = new ExposedList<BoneData>();
+		internal SlotData target;
+		internal PositionMode positionMode;
+		internal SpacingMode spacingMode;
+		internal RotateMode rotateMode;
+		internal float offsetRotation;
+		internal float position, spacing, rotateMix, translateMix;
+
+		public ExposedList<BoneData> Bones { get { return bones; } }
+		public SlotData Target { get { return target; } set { target = value; } }			
+		public PositionMode PositionMode { get { return positionMode; } set { positionMode = value; } }
+		public SpacingMode SpacingMode { get { return spacingMode; } set { spacingMode = value; } }
+		public RotateMode RotateMode { get { return rotateMode; } set { rotateMode = value; } }
+		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; } }
+		public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
+		public float TranslateMix { get { return translateMix; } set { translateMix = value; } }
+		public String Name { get { return name; } }
+
+		public PathConstraintData (String name) {
+			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
+			this.name = name;
+		}
+	}
+	
+	public enum PositionMode {
+		Fixed, Percent        
+	}
+
+	public enum SpacingMode {
+		Length, Fixed, Percent
+	}
+
+	public enum RotateMode {
+		Tangent, Chain, ChainScale
+	}
+}

+ 202 - 64
spine-csharp/src/Skeleton.cs

@@ -38,9 +38,10 @@ namespace Spine {
 		internal ExposedList<Bone> bones;
 		internal ExposedList<Slot> slots;
 		internal ExposedList<Slot> drawOrder;
-		internal ExposedList<IkConstraint> ikConstraints;
+		internal ExposedList<IkConstraint> ikConstraints, ikConstraintsSorted;
 		internal ExposedList<TransformConstraint> transformConstraints;
-		private ExposedList<IUpdatable> updateCache = new ExposedList<IUpdatable>();
+		internal ExposedList<PathConstraint> pathConstraints;
+		internal ExposedList<IUpdatable> updateCache = new ExposedList<IUpdatable>();
 		internal Skin skin;
 		internal float r = 1, g = 1, b = 1, a = 1;
 		internal float time;
@@ -49,9 +50,12 @@ namespace Spine {
 
 		public SkeletonData Data { get { return data; } }
 		public ExposedList<Bone> Bones { get { return bones; } }
+		public ExposedList<IUpdatable> UpdateCacheList { get { return updateCache; } }
 		public ExposedList<Slot> Slots { get { return slots; } }
 		public ExposedList<Slot> DrawOrder { get { return drawOrder; } }
-		public ExposedList<IkConstraint> IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } }
+		public ExposedList<IkConstraint> IkConstraints { get { return ikConstraints; } }
+		public ExposedList<PathConstraint> PathConstraints { get { return pathConstraints; } }
+		public ExposedList<TransformConstraint> TransformConstraints { get { return transformConstraints; } }
 		public Skin Skin { get { return skin; } set { skin = value; } }
 		public float R { get { return r; } set { r = value; } }
 		public float G { get { return g; } set { g = value; } }
@@ -64,33 +68,37 @@ namespace Spine {
 		public bool FlipY { get { return flipY; } set { flipY = value; } }
 
 		public Bone RootBone {
-			get {
-				return bones.Count == 0 ? null : bones.Items[0];
-			}
+			get { return bones.Count == 0 ? null : bones.Items[0]; }
 		}
 
 		public Skeleton (SkeletonData data) {
-			if (data == null) throw new ArgumentNullException("data cannot be null.");
+			if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
 			this.data = data;
 
 			bones = new ExposedList<Bone>(data.bones.Count);
 			foreach (BoneData boneData in data.bones) {
-				Bone parent = boneData.parent == null ? null : bones.Items[data.bones.IndexOf(boneData.parent)];
-				Bone bone = new Bone(boneData, this, parent);
-				if (parent != null) parent.children.Add(bone);
+				Bone bone;
+				if (boneData.parent == null) {
+					bone = new Bone (boneData, this, null);				
+				} else {
+					Bone parent = bones.Items[boneData.parent.index];
+					bone = new Bone (boneData, this, parent);
+					parent.children.Add (bone);
+				}
 				bones.Add(bone);
 			}
 
 			slots = new ExposedList<Slot>(data.slots.Count);
 			drawOrder = new ExposedList<Slot>(data.slots.Count);
 			foreach (SlotData slotData in data.slots) {
-				Bone bone = bones.Items[data.bones.IndexOf(slotData.boneData)];
+				Bone bone = bones.Items[slotData.boneData.index];
 				Slot slot = new Slot(slotData, bone);
 				slots.Add(slot);
 				drawOrder.Add(slot);
 			}
 
 			ikConstraints = new ExposedList<IkConstraint>(data.ikConstraints.Count);
+			ikConstraintsSorted = new ExposedList<IkConstraint>(data.ikConstraints.Count);
 			foreach (IkConstraintData ikConstraintData in data.ikConstraints)
 				ikConstraints.Add(new IkConstraint(ikConstraintData, this));
 
@@ -98,46 +106,150 @@ namespace Spine {
 			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));
+
 			UpdateCache();
 			UpdateWorldTransform();
 		}
 
-		/// <summary>Caches information about bones and constraints. Must be called if bones or constraints are added
+		/// <summary>Caches information about bones and constraints. Must be called if bones, constraints or weighted path attachments are added
 		/// or removed.</summary>
 		public void UpdateCache () {
-			ExposedList<Bone> bones = this.bones;
 			ExposedList<IUpdatable> updateCache = this.updateCache;
-			ExposedList<IkConstraint> ikConstraints = this.ikConstraints;
-			ExposedList<TransformConstraint> transformConstraints = this.transformConstraints;
-			int ikConstraintsCount = ikConstraints.Count;
-			int transformConstraintsCount = transformConstraints.Count;
 			updateCache.Clear();
-			for (int i = 0, n = bones.Count; i < n; i++) {
-				Bone bone = bones.Items[i];
-				updateCache.Add(bone);
-				for (int ii = 0; ii < ikConstraintsCount; ii++) {
-					IkConstraint ikConstraint = ikConstraints.Items[ii];
-					if (bone == ikConstraint.bones.Items[ikConstraint.bones.Count - 1]) {
-						updateCache.Add(ikConstraint);
-						break;
-					}
+
+			ExposedList<Bone> bones = this.bones;
+			for (int i = 0, n = bones.Count; i < n; i++)
+				bones.Items[i].sorted = false;
+
+			ExposedList<IkConstraint> ikConstraints = this.ikConstraintsSorted;
+			ikConstraints.Clear();
+			ikConstraints.AddRange(this.ikConstraints);
+			int ikCount = ikConstraints.Count;
+			for (int i = 0, level, n = ikCount; i < n; i++) {
+				IkConstraint ik = ikConstraints.Items[i];
+				Bone bone = ik.bones.Items[0].Parent;
+				for (level = 0; bone != null; level++)
+					bone = bone.Parent;
+				ik.level = level;
+			}
+			for (int i = 1, ii; i < ikCount; i++) {
+				IkConstraint ik = ikConstraints.Items [i];
+				int level = ik.level;
+				for (ii = i - 1; ii >= 0; ii--) {
+					IkConstraint other = ikConstraints.Items[ii];
+					if (other.level < level) break;
+					ikConstraints.Items[ii + 1] = other;
 				}
+				ikConstraints.Items[ii + 1] = ik;
 			}
+			for (int i = 0, n = ikConstraints.Count; i < n; i++) {
+				IkConstraint constraint = ikConstraints.Items[i];
+				Bone target = constraint.target;
+				SortBone(target);
 
-			for (int i = 0; i < transformConstraintsCount; i++) {
-				TransformConstraint transformConstraint = transformConstraints.Items[i];
-				for (int ii = updateCache.Count - 1; i >= 0; ii--) {
-					if (updateCache.Items[ii] == transformConstraint.bone) {
-						updateCache.Insert(ii + 1, transformConstraint);
-						break;
-					}
-				}
+				ExposedList<Bone> constrained = constraint.bones;
+				Bone parent = constrained.Items [0];
+				SortBone(parent);
+
+				updateCache.Add(constraint);
+
+				SortReset(parent.children);
+				constrained.Items[constrained.Count - 1].sorted = true;
+			}
+
+			ExposedList<PathConstraint> pathConstraints = this.pathConstraints;
+			for (int i = 0, n = pathConstraints.Count; i < n; i++) {
+				PathConstraint constraint = pathConstraints.Items[i];
+
+				Slot slot = constraint.Target;
+				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);
+				for (int ii = 0, nn = data.skins.Count; ii < nn; ii++)
+					SortPathConstraintAttachment(data.skins.Items[ii], slotIndex, slotBone);
+
+				PathAttachment attachment = slot.Attachment as PathAttachment;
+				if (attachment != null) SortPathConstraintAttachment(attachment, slotBone);
+
+				ExposedList<Bone> constrained = constraint.bones;
+				int boneCount = constrained.Count;
+				for (int ii = 0; ii < boneCount; ii++)
+					SortBone(constrained.Items[ii]);
+
+				updateCache.Add(constraint);
+
+				for (int ii = 0; ii < boneCount; ii++)
+					SortReset(constrained.Items[ii].children);
+				for (int ii = 0; ii < boneCount; ii++)
+					constrained.Items[ii].sorted = true;
+			}
+
+			ExposedList<TransformConstraint> transformConstraints = this.transformConstraints;
+			for (int i = 0, n = transformConstraints.Count; i < n; i++) {
+				TransformConstraint constraint = transformConstraints.Items[i];
+
+				SortBone(constraint.target);
+
+				ExposedList<Bone> constrained = constraint.bones;
+				int boneCount = constrained.Count;
+				for (int ii = 0; ii < boneCount; ii++)
+					SortBone(constrained.Items[ii]);
+
+				updateCache.Add(constraint);
+
+				for (int ii = 0; ii < boneCount; ii++)
+					SortReset(constrained.Items[ii].children);
+				for (int ii = 0; ii < boneCount; ii++)
+					constrained.Items[ii].sorted = true;
+			}
+
+			for (int i = 0, n = bones.Count; i < n; i++)
+				SortBone(bones.Items[i]);
+		}
+
+		private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) {
+			foreach (var entry in skin.Attachments)
+				if (entry.Key.slotIndex == slotIndex) SortPathConstraintAttachment(entry.Value, slotBone);
+		}
+
+		private void SortPathConstraintAttachment (Attachment attachment, Bone slotBone) {
+			var pathAttachment = attachment as PathAttachment;
+			if (pathAttachment == null) return;
+			int[] pathBones = pathAttachment.bones;
+			if (pathBones == null)
+				SortBone(slotBone);
+			else {
+				var bones = this.bones;
+				for (int i = 0, n = pathBones.Length; i < n; i++)
+					SortBone(bones.Items[pathBones[i]]);
+			}
+		}
+
+		private void SortBone (Bone bone) {
+			if (bone.sorted) return;
+			Bone parent = bone.parent;
+			if (parent != null) SortBone(parent);
+			bone.sorted = true;
+			updateCache.Add(bone);
+		}
+
+		private void SortReset (ExposedList<Bone> bones) {
+			var bonesItems = bones.Items;
+			for (int i = 0, n = bones.Count; i < n; i++) {
+				Bone bone = bonesItems[i];
+				if (bone.sorted) SortReset(bone.children);
+				bone.sorted = false;
 			}
 		}
 
 		/// <summary>Updates the world transform for each bone and applies constraints.</summary>
 		public void UpdateWorldTransform () {
-			ExposedList<IUpdatable> updateCache = this.updateCache;
+			var updateCache = this.updateCache;
 			for (int i = 0, n = updateCache.Count; i < n; i++)
 				updateCache.Items[i].Update();
 		}
@@ -150,44 +262,56 @@ namespace Spine {
 
 		/// <summary>Sets the bones and constraints to their setup pose values.</summary>
 		public void SetBonesToSetupPose () {
-			ExposedList<Bone> bones = this.bones;
+			var bonesItems = this.bones.Items;
 			for (int i = 0, n = bones.Count; i < n; i++)
-				bones.Items[i].SetToSetupPose();
+				bonesItems[i].SetToSetupPose();
 
-			ExposedList<IkConstraint> ikConstraints = this.ikConstraints;
+			var ikConstraintsItems = this.ikConstraints.Items;
 			for (int i = 0, n = ikConstraints.Count; i < n; i++) {
-				IkConstraint constraint = ikConstraints.Items[i];
+				IkConstraint constraint = ikConstraintsItems[i];
 				constraint.bendDirection = constraint.data.bendDirection;
 				constraint.mix = constraint.data.mix;
 			}
 
-			ExposedList<TransformConstraint> transformConstraints = this.transformConstraints;
+			var transformConstraintsItems = this.transformConstraints.Items;
 			for (int i = 0, n = transformConstraints.Count; i < n; i++) {
-				TransformConstraint constraint = transformConstraints.Items[i];
+				TransformConstraint constraint = transformConstraintsItems[i];
 				TransformConstraintData data = constraint.data;
 				constraint.rotateMix = data.rotateMix;
 				constraint.translateMix = data.translateMix;
 				constraint.scaleMix = data.scaleMix;
 				constraint.shearMix = data.shearMix;
 			}
+
+			var pathConstraints = this.pathConstraints;
+			for (int i = 0, n = pathConstraints.Count; i < n; i++) {
+				PathConstraint constraint = pathConstraints.Items[i];
+				PathConstraintData data = constraint.data;
+				constraint.position = data.position;
+				constraint.spacing = data.spacing;
+				constraint.rotateMix = data.rotateMix;
+				constraint.translateMix = data.translateMix;
+			}
 		}
 
 		public void SetSlotsToSetupPose () {
-			ExposedList<Slot> slots = this.slots;
+			var slots = this.slots;
+			var slotsItems = slots.Items;
 			drawOrder.Clear();
 			for (int i = 0, n = slots.Count; i < n; i++)
-				drawOrder.Add(slots.Items[i]);
+				drawOrder.Add(slotsItems[i]);
 
 			for (int i = 0, n = slots.Count; i < n; i++)
-				slots.Items[i].SetToSetupPose(i);
+				slotsItems[i].SetToSetupPose();
 		}
 
 		/// <returns>May be null.</returns>
 		public Bone FindBone (String boneName) {
-			if (boneName == null) throw new ArgumentNullException("boneName cannot be null.");
-			ExposedList<Bone> bones = this.bones;
+			if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
+			var bones = this.bones;
+			var bonesItems = bones.Items;
 			for (int i = 0, n = bones.Count; i < n; i++) {
-				Bone bone = bones.Items[i];
+				Bone bone = bonesItems[i];
 				if (bone.data.name == boneName) return bone;
 			}
 			return null;
@@ -195,19 +319,21 @@ namespace Spine {
 
 		/// <returns>-1 if the bone was not found.</returns>
 		public int FindBoneIndex (String boneName) {
-			if (boneName == null) throw new ArgumentNullException("boneName cannot be null.");
-			ExposedList<Bone> bones = this.bones;
+			if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
+			var bones = this.bones;
+			var bonesItems = bones.Items;
 			for (int i = 0, n = bones.Count; i < n; i++)
-				if (bones.Items[i].data.name == boneName) return i;
+				if (bonesItems[i].data.name == boneName) return i;
 			return -1;
 		}
 
 		/// <returns>May be null.</returns>
 		public Slot FindSlot (String slotName) {
-			if (slotName == null) throw new ArgumentNullException("slotName cannot be null.");
-			ExposedList<Slot> slots = this.slots;
+			if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
+			var slots = this.slots;
+			var slotsItems = slots.Items;
 			for (int i = 0, n = slots.Count; i < n; i++) {
-				Slot slot = slots.Items[i];
+				Slot slot = slotsItems[i];
 				if (slot.data.name == slotName) return slot;
 			}
 			return null;
@@ -215,17 +341,18 @@ namespace Spine {
 
 		/// <returns>-1 if the bone was not found.</returns>
 		public int FindSlotIndex (String slotName) {
-			if (slotName == null) throw new ArgumentNullException("slotName cannot be null.");
-			ExposedList<Slot> slots = this.slots;
+			if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
+			var slots = this.slots;
+			var slotsItems = slots.Items;
 			for (int i = 0, n = slots.Count; i < n; i++)
-				if (slots.Items[i].data.name.Equals(slotName)) return i;
+				if (slotsItems[i].data.name.Equals(slotName)) return i;
 			return -1;
 		}
 
 		/// <summary>Sets a skin by name (see SetSkin).</summary>
 		public void SetSkin (String skinName) {
 			Skin skin = data.FindSkin(skinName);
-			if (skin == null) throw new ArgumentException("Skin not found: " + skinName);
+			if (skin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName");
 			SetSkin(skin);
 		}
 
@@ -259,7 +386,7 @@ namespace Spine {
 
 		/// <returns>May be null.</returns>
 		public Attachment GetAttachment (int slotIndex, String attachmentName) {
-			if (attachmentName == null) throw new ArgumentNullException("attachmentName cannot be null.");
+			if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null.");
 			if (skin != null) {
 				Attachment attachment = skin.GetAttachment(slotIndex, attachmentName);
 				if (attachment != null) return attachment;
@@ -270,7 +397,7 @@ namespace Spine {
 
 		/// <param name="attachmentName">May be null.</param>
 		public void SetAttachment (String slotName, String attachmentName) {
-			if (slotName == null) throw new ArgumentNullException("slotName cannot be null.");
+			if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
 			ExposedList<Slot> slots = this.slots;
 			for (int i = 0, n = slots.Count; i < n; i++) {
 				Slot slot = slots.Items[i];
@@ -286,10 +413,10 @@ namespace Spine {
 			}
 			throw new Exception("Slot not found: " + slotName);
 		}
-
-		/** @return May be null. */
+			
+		/// <returns>May be null.</returns>
 		public IkConstraint FindIkConstraint (String constraintName) {
-			if (constraintName == null) throw new ArgumentNullException("constraintName cannot be null.");
+			if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
 			ExposedList<IkConstraint> ikConstraints = this.ikConstraints;
 			for (int i = 0, n = ikConstraints.Count; i < n; i++) {
 				IkConstraint ikConstraint = ikConstraints.Items[i];
@@ -298,9 +425,9 @@ namespace Spine {
 			return null;
 		}
 
-		/** @return May be null. */
+		/// <returns>May be null.</returns>
 		public TransformConstraint FindTransformConstraint (String constraintName) {
-			if (constraintName == null) throw new ArgumentNullException("constraintName cannot be null.");
+			if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
 			ExposedList<TransformConstraint> transformConstraints = this.transformConstraints;
 			for (int i = 0, n = transformConstraints.Count; i < n; i++) {
 				TransformConstraint transformConstraint = transformConstraints.Items[i];
@@ -309,6 +436,17 @@ namespace Spine {
 			return null;
 		}
 
+		/// <returns>May be null.</returns>
+		public PathConstraint FindPathConstraint (String constraintName) {
+			if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
+			ExposedList<PathConstraint> pathConstraints = this.pathConstraints;
+			for (int i = 0, n = pathConstraints.Count; i < n; i++) {
+				PathConstraint constraint = pathConstraints.Items[i];
+				if (constraint.data.name.Equals(constraintName)) return constraint;
+			}
+			return null;
+		}
+
 		public void Update (float delta) {
 			time += delta;
 		}

+ 235 - 198
spine-csharp/src/SkeletonBinary.cs

@@ -44,12 +44,17 @@ using Windows.Storage;
 
 namespace Spine {
 	public class SkeletonBinary {
-		public const int TIMELINE_ROTATE = 0;
-		public const int TIMELINE_TRANSLATE = 1;
-		public const int TIMELINE_SCALE = 2;
-		public const int TIMELINE_SHEAR = 3;
-		public const int TIMELINE_ATTACHMENT = 4;
-		public const int TIMELINE_COLOR = 5;
+		public const int BONE_ROTATE = 0;
+		public const int BONE_TRANSLATE = 1;
+		public const int BONE_SCALE = 2;
+		public const int BONE_SHEAR = 3;
+
+		public const int SLOT_ATTACHMENT = 0;
+		public const int SLOT_COLOR = 1;
+
+		public const int PATH_POSITION = 0;
+		public const int PATH_SPACING = 1;
+		public const int PATH_MIX = 2;
 
 		public const int CURVE_LINEAR = 0;
 		public const int CURVE_STEPPED = 1;
@@ -122,55 +127,26 @@ namespace Spine {
 			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
 				String name = ReadString(input);
 				BoneData parent = i == 0 ? null : skeletonData.bones.Items[ReadVarint(input, true)];
-				BoneData boneData = new BoneData(name, parent);
-				boneData.rotation = ReadFloat(input);		
-				boneData.x = ReadFloat(input) * scale;
-				boneData.y = ReadFloat(input) * scale;
-				boneData.scaleX = ReadFloat(input);
-				boneData.scaleY = ReadFloat(input);
-				boneData.shearX = ReadFloat(input);
-				boneData.shearY = ReadFloat(input);
-				boneData.length = ReadFloat(input) * scale;
-				boneData.inheritRotation = ReadBoolean(input);
-				boneData.inheritScale = ReadBoolean(input);
+				BoneData data = new BoneData(i, name, parent);
+				data.rotation = ReadFloat(input);		
+				data.x = ReadFloat(input) * scale;
+				data.y = ReadFloat(input) * scale;
+				data.scaleX = ReadFloat(input);
+				data.scaleY = ReadFloat(input);
+				data.shearX = ReadFloat(input);
+				data.shearY = ReadFloat(input);
+				data.length = ReadFloat(input) * scale;
+				data.inheritRotation = ReadBoolean(input);
+				data.inheritScale = ReadBoolean(input);
 				if (nonessential) ReadInt(input); // Skip bone color.
-				skeletonData.bones.Add(boneData);
-			}
-
-			// IK constraints.
-			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
-				IkConstraintData ikConstraintData = new IkConstraintData(ReadString(input));
-				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++)
-					ikConstraintData.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]);
-				ikConstraintData.target = skeletonData.bones.Items[ReadVarint(input, true)];
-				ikConstraintData.mix = ReadFloat(input);
-				ikConstraintData.bendDirection = ReadSByte(input);
-				skeletonData.ikConstraints.Add(ikConstraintData);
-			}
-
-			// Transform constraints.
-			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
-				TransformConstraintData transformConstraintData = new TransformConstraintData(ReadString(input));
-				transformConstraintData.bone = skeletonData.bones.Items[ReadVarint(input, true)];
-				transformConstraintData.target = skeletonData.bones.Items[ReadVarint(input, true)];
-				transformConstraintData.offsetRotation = ReadFloat(input);
-				transformConstraintData.offsetX = ReadFloat(input) * scale;
-				transformConstraintData.offsetY = ReadFloat(input) * scale;
-				transformConstraintData.offsetScaleX = ReadFloat(input);
-				transformConstraintData.offsetScaleY = ReadFloat(input);
-				transformConstraintData.offsetShearY = ReadFloat(input);
-				transformConstraintData.rotateMix = ReadFloat(input);
-				transformConstraintData.translateMix = ReadFloat(input);
-				transformConstraintData.scaleMix = ReadFloat(input);
-				transformConstraintData.shearMix = ReadFloat(input);
-				skeletonData.transformConstraints.Add(transformConstraintData);
+				skeletonData.bones.Add(data);
 			}
 
 			// Slots.
 			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
 				String slotName = ReadString(input);
 				BoneData boneData = skeletonData.bones.Items[ReadVarint(input, true)];
-				SlotData slotData = new SlotData(slotName, boneData);
+				SlotData slotData = new SlotData(i, slotName, boneData);
 				int color = ReadInt(input);
 				slotData.r = ((color & 0xff000000) >> 24) / 255f;
 				slotData.g = ((color & 0x00ff0000) >> 16) / 255f;
@@ -181,6 +157,55 @@ namespace Spine {
 				skeletonData.slots.Add(slotData);
 			}
 
+			// IK constraints.
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
+				IkConstraintData data = new IkConstraintData(ReadString(input));
+				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++)
+					data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]);
+				data.target = skeletonData.bones.Items[ReadVarint(input, true)];
+				data.mix = ReadFloat(input);
+				data.bendDirection = ReadSByte(input);
+				skeletonData.ikConstraints.Add(data);
+			}
+
+			// Transform constraints.
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
+				TransformConstraintData data = new TransformConstraintData(ReadString(input));
+				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++)
+				    data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]);
+				data.target = skeletonData.bones.Items[ReadVarint(input, true)];
+				data.offsetRotation = ReadFloat(input);
+				data.offsetX = ReadFloat(input) * scale;
+				data.offsetY = ReadFloat(input) * scale;
+				data.offsetScaleX = ReadFloat(input);
+				data.offsetScaleY = ReadFloat(input);
+				data.offsetShearY = ReadFloat(input);
+				data.rotateMix = ReadFloat(input);
+				data.translateMix = ReadFloat(input);
+				data.scaleMix = ReadFloat(input);
+				data.shearMix = ReadFloat(input);
+				skeletonData.transformConstraints.Add(data);
+			}
+
+			// Path constraints
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
+				PathConstraintData data = new PathConstraintData(ReadString(input));
+				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++)
+					data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]);
+				data.target = skeletonData.slots.Items[ReadVarint(input, true)];
+				data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(ReadVarint(input, true));
+				data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue(ReadVarint(input, true));
+				data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(ReadVarint(input, true));
+				data.offsetRotation = ReadFloat(input);
+				data.position = ReadFloat(input);
+				if (data.positionMode == PositionMode.Fixed) data.position *= scale;
+				data.spacing = ReadFloat(input);
+				if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale;
+				data.rotateMix = ReadFloat(input);
+				data.translateMix = ReadFloat(input);
+				skeletonData.pathConstraints.Add(data);
+			}
+
 			// Default skin.
 			Skin defaultSkin = ReadSkin(input, "default", nonessential);
 			if (defaultSkin != null) {
@@ -199,25 +224,18 @@ namespace Spine {
 				if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin);
 				Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent);
 				if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent);
-				if (linkedMesh.mesh is MeshAttachment) {
-					MeshAttachment mesh = (MeshAttachment)linkedMesh.mesh;
-					mesh.ParentMesh = (MeshAttachment)parent;
-					mesh.UpdateUVs();
-				} else {
-					WeightedMeshAttachment mesh = (WeightedMeshAttachment)linkedMesh.mesh;
-					mesh.ParentMesh = (WeightedMeshAttachment)parent;
-					mesh.UpdateUVs();
-				}
+				linkedMesh.mesh.ParentMesh = (MeshAttachment)parent;
+				linkedMesh.mesh.UpdateUVs();
 			}
 			linkedMeshes.Clear();
 
 			// Events.
 			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
-				EventData eventData = new EventData(ReadString(input));
-				eventData.Int = ReadVarint(input, false);
-				eventData.Float = ReadFloat(input);
-				eventData.String = ReadString(input);
-				skeletonData.events.Add(eventData);
+				EventData data = new EventData(ReadString(input));
+				data.Int = ReadVarint(input, false);
+				data.Float = ReadFloat(input);
+				data.String = ReadString(input);
+				skeletonData.events.Add(data);
 			}
 
 			// Animations.
@@ -230,10 +248,12 @@ namespace Spine {
 			skeletonData.events.TrimExcess();
 			skeletonData.animations.TrimExcess();
 			skeletonData.ikConstraints.TrimExcess();
+			skeletonData.pathConstraints.TrimExcess();
 			return skeletonData;
 		}
 
-		/** @return May be null. */
+
+		/// <returns>May be null.</returns>
 		private Skin ReadSkin (Stream input, String skinName, bool nonessential) {
 			int slotCount = ReadVarint(input, true);
 			if (slotCount == 0) return null;
@@ -256,7 +276,7 @@ namespace Spine {
 
 			AttachmentType type = (AttachmentType)input.ReadByte();
 			switch (type) {
-			case AttachmentType.region: {
+			case AttachmentType.Region: {
 					String path = ReadString(input);
 					float rotation = ReadFloat(input);		
 					float x = ReadFloat(input);
@@ -285,22 +305,26 @@ namespace Spine {
 					region.UpdateOffset();
 					return region;
 				}
-			case AttachmentType.boundingbox: {
-					float[] vertices = ReadFloatArray(input, ReadVarint(input, true) * 2, scale);
+			case AttachmentType.Boundingbox: {
+					int vertexCount = ReadVarint(input, true);
+					Vertices vertices = ReadVertices(input, vertexCount);
+					if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; // Avoid unused local warning.
+					
 					BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name);
 					if (box == null) return null;
-					box.vertices = vertices;
+					box.worldVerticesLength = vertexCount << 1;
+					box.vertices = vertices.vertices;
+					box.bones = vertices.bones;                    
 					return box;
 				}
-			case AttachmentType.mesh: {
+			case AttachmentType.Mesh: {
 					String path = ReadString(input);
 					int color = ReadInt(input);
-					int hullLength = 0;
-					int verticesLength = ReadVarint(input, true) * 2;
-					float[] uvs = ReadFloatArray(input, verticesLength, 1);
+					int vertexCount = ReadVarint(input, true);					
+					float[] uvs = ReadFloatArray(input, vertexCount << 1, 1);
 					int[] triangles = ReadShortArray(input);
-					float[] vertices = ReadFloatArray(input, verticesLength, scale);
-					hullLength = ReadVarint(input, true);
+					Vertices vertices = ReadVertices(input, vertexCount);
+					int hullLength = ReadVarint(input, true);
 					int[] edges = null;
 					float width = 0, height = 0;
 					if (nonessential) {
@@ -317,11 +341,13 @@ namespace Spine {
 					mesh.g = ((color & 0x00ff0000) >> 16) / 255f;
 					mesh.b = ((color & 0x0000ff00) >> 8) / 255f;
 					mesh.a = ((color & 0x000000ff)) / 255f;
-					mesh.vertices = vertices;
+					mesh.bones = vertices.bones;
+					mesh.vertices = vertices.vertices;
+					mesh.WorldVerticesLength = vertexCount << 1;
 					mesh.triangles = triangles;
 					mesh.regionUVs = uvs;
 					mesh.UpdateUVs();
-					mesh.HullLength = hullLength;
+					mesh.HullLength = hullLength << 1;
 					if (nonessential) {
 						mesh.Edges = edges;
 						mesh.Width = width * scale;
@@ -329,12 +355,12 @@ namespace Spine {
 					}
 					return mesh;
 				}
-			case AttachmentType.linkedmesh: {
+			case AttachmentType.Linkedmesh: {
 					String path = ReadString(input);
 					int color = ReadInt(input);
 					String skinName = ReadString(input);
 					String parent = ReadString(input);
-					bool inheritFFD = ReadBoolean(input);
+					bool inheritDeform = ReadBoolean(input);
 					float width = 0, height = 0;
 					if (nonessential) {
 						width = ReadFloat(input);
@@ -349,7 +375,7 @@ namespace Spine {
 					mesh.g = ((color & 0x00ff0000) >> 16) / 255f;
 					mesh.b = ((color & 0x0000ff00) >> 8) / 255f;
 					mesh.a = ((color & 0x000000ff)) / 255f;
-					mesh.inheritFFD = inheritFFD;
+					mesh.inheritDeform = inheritDeform;
 					if (nonessential) {
 						mesh.Width = width * scale;
 						mesh.Height = height * scale;
@@ -357,85 +383,54 @@ namespace Spine {
 					linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent));
 					return mesh;
 				}
-			case AttachmentType.weightedmesh: {
-					String path = ReadString(input);
-					int color = ReadInt(input);
-					int vertexCount = ReadVarint(input, true);		
-					float[] uvs = ReadFloatArray(input, vertexCount * 2, 1);
-					int[] triangles = ReadShortArray(input);
-					var weights = new List<float>(uvs.Length * 3 * 3);
-					var bones = new List<int>(uvs.Length * 3);
-					for (int i = 0; i < vertexCount; i++) {
-						int boneCount = (int)ReadFloat(input);
-						bones.Add(boneCount);
-						for (int ii = 0; ii < boneCount; ii++) {
-							bones.Add((int)ReadFloat(input));
-							weights.Add(ReadFloat(input) * scale);
-							weights.Add(ReadFloat(input) * scale);
-							weights.Add(ReadFloat(input));
-						}
-					}
-					int hullLength = ReadVarint(input, true);
-					int[] edges = null;
-					float width = 0, height = 0;
-					if (nonessential) {
-						edges = ReadShortArray(input);
-						width = ReadFloat(input);
-						height = ReadFloat(input);
-					}
-
-					if (path == null) path = name;
-					WeightedMeshAttachment mesh = attachmentLoader.NewWeightedMeshAttachment(skin, name, path);
-					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.bones = bones.ToArray();
-					mesh.weights = weights.ToArray();
-					mesh.triangles = triangles;
-					mesh.regionUVs = uvs;
-					mesh.UpdateUVs();
-					mesh.HullLength = hullLength * 2;
-					if (nonessential) {
-						mesh.Edges = edges;
-						mesh.Width = width * scale;
-						mesh.Height = height * scale;
-					}
-					//
-					return mesh;
-				}
-			case AttachmentType.weightedlinkedmesh: {
-					String path = ReadString(input);
-					int color = ReadInt(input);
-					String skinName = ReadString(input);
-					String parent = ReadString(input);
-					bool inheritFFD = ReadBoolean(input);
-					float width = 0, height = 0;
-					if (nonessential) {
-						width = ReadFloat(input);
-						height = ReadFloat(input);
-					}
+			case AttachmentType.Path: {
+					bool closed = ReadBoolean(input);
+					bool constantSpeed = ReadBoolean(input);
+					int vertexCount = ReadVarint(input, true);
+					Vertices vertices = ReadVertices(input, vertexCount);
+					float[] lengths = new float[vertexCount / 3];
+					for (int i = 0, n = lengths.Length; i < n; i++)
+						lengths[i] = ReadFloat(input) * scale;
+					if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; // Avoid unused local warning.
+
+					PathAttachment path = attachmentLoader.NewPathAttachment(skin, name);
+					if (path == null) return null;
+					path.closed = closed;
+					path.constantSpeed = constantSpeed;
+					path.worldVerticesLength = vertexCount << 1;
+					path.vertices = vertices.vertices;
+					path.bones = vertices.bones;
+					path.lengths = lengths;
+					return path;                    
+				}			
+			}
+			return null;
+		}
 
-					if (path == null) path = name;
-					WeightedMeshAttachment mesh = attachmentLoader.NewWeightedMeshAttachment(skin, name, path);
-					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.inheritFFD = inheritFFD;
-					if (nonessential) {
-						mesh.Width = width * scale;
-						mesh.Height = height * scale;
-					}
-					linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent));
-					return mesh;
+		private Vertices ReadVertices (Stream input, int vertexCount) {
+			float scale = Scale;
+			int verticesLength = vertexCount << 1;
+			Vertices vertices = new Vertices();
+			if(!ReadBoolean(input)) {
+				vertices.vertices = ReadFloatArray(input, verticesLength, scale);
+				return vertices;
+			}
+			var weights = new ExposedList<float>(verticesLength * 3 * 3);
+			var bonesArray = new ExposedList<int>(verticesLength * 3);
+			for (int i = 0; i < vertexCount; i++) {
+				int boneCount = ReadVarint(input, true);
+				bonesArray.Add(boneCount);
+				for (int ii = 0; ii < boneCount; ii++) {
+					bonesArray.Add(ReadVarint(input, true));
+					weights.Add(ReadFloat(input) * scale);
+					weights.Add(ReadFloat(input) * scale);
+					weights.Add(ReadFloat(input));
 				}
 			}
-			return null;
+
+			vertices.vertices = weights.ToArray();
+			vertices.bones = bonesArray.ToArray();
+			return vertices;
 		}
 
 		private float[] ReadFloatArray (Stream input, int n, float scale) {
@@ -470,7 +465,7 @@ namespace Spine {
 					int timelineType = input.ReadByte();
 					int frameCount = ReadVarint(input, true);
 					switch (timelineType) {
-					case TIMELINE_COLOR: {
+					case SLOT_COLOR: {
 							ColorTimeline timeline = new ColorTimeline(frameCount);
 							timeline.slotIndex = slotIndex;
 							for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
@@ -484,10 +479,10 @@ namespace Spine {
 								if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
 							}
 							timelines.Add(timeline);
-							duration = Math.Max(duration, timeline.frames[frameCount * 5 - 5]);
+							duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]);
 							break;
 						}
-					case TIMELINE_ATTACHMENT: {
+					case SLOT_ATTACHMENT: {
 							AttachmentTimeline timeline = new AttachmentTimeline(frameCount);
 							timeline.slotIndex = slotIndex;
 							for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
@@ -507,7 +502,7 @@ namespace Spine {
 					int timelineType = input.ReadByte();
 					int frameCount = ReadVarint(input, true);
 					switch (timelineType) {
-					case TIMELINE_ROTATE: {
+					case BONE_ROTATE: {
 							RotateTimeline timeline = new RotateTimeline(frameCount);
 							timeline.boneIndex = boneIndex;
 							for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
@@ -515,17 +510,17 @@ namespace Spine {
 								if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
 							}
 							timelines.Add(timeline);
-							duration = Math.Max(duration, timeline.frames[frameCount * 2 - 2]);
+							duration = Math.Max(duration, timeline.frames[(frameCount - 1) * RotateTimeline.ENTRIES]);
 							break;
 						}
-					case TIMELINE_TRANSLATE:
-					case TIMELINE_SCALE:
-					case TIMELINE_SHEAR: {
+					case BONE_TRANSLATE:
+					case BONE_SCALE:
+					case BONE_SHEAR: {
 							TranslateTimeline timeline;
 							float timelineScale = 1;
-							if (timelineType == TIMELINE_SCALE)
+							if (timelineType == BONE_SCALE)
 								timeline = new ScaleTimeline(frameCount);
-							else if (timelineType == TIMELINE_SHEAR)
+							else if (timelineType == BONE_SHEAR)
 								timeline = new ShearTimeline(frameCount);
 							else {
 								timeline = new TranslateTimeline(frameCount);
@@ -538,7 +533,7 @@ namespace Spine {
 								if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
 							}
 							timelines.Add(timeline);
-							duration = Math.Max(duration, timeline.frames[frameCount * 3 - 3]);
+							duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TranslateTimeline.ENTRIES]);
 							break;
 						}
 					}
@@ -546,81 +541,118 @@ namespace Spine {
 			}
 
 			// IK timelines.
-			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
-				IkConstraintData constraint = skeletonData.ikConstraints.Items[ReadVarint(input, true)];
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {				
+				int index = ReadVarint(input, true);
 				int frameCount = ReadVarint(input, true);
 				IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount);
-				timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint);
+				timeline.ikConstraintIndex = index;
 				for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
 					timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input));
 					if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
 				}
 				timelines.Add(timeline);
-				duration = Math.Max(duration, timeline.frames[frameCount * 3 - 3]);
+				duration = Math.Max(duration, timeline.frames[(frameCount - 1) * IkConstraintTimeline.ENTRIES]);
 			}
 
 			// Transform constraint timelines.
 			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
-				TransformConstraintData constraint = skeletonData.transformConstraints.Items[ReadVarint(input, true)];
+				int index = ReadVarint(input, true);
 				int frameCount = ReadVarint(input, true);
 				TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount);
-				timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint);
+				timeline.transformConstraintIndex = index;
 				for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
 					timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input));
 					if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
 				}
 				timelines.Add(timeline);
-				duration = Math.Max(duration, timeline.frames[frameCount * 5 - 5]);
+				duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TransformConstraintTimeline.ENTRIES]);
+			}
+
+			// Path constraint timelines.
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
+				int index = ReadVarint(input, true);
+				PathConstraintData data = skeletonData.pathConstraints.Items[index];
+				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) {
+					int timelineType = ReadSByte(input);
+					int frameCount = ReadVarint(input, true);
+					switch(timelineType) {
+						case PATH_POSITION:
+						case PATH_SPACING: {
+								PathConstraintPositionTimeline timeline;
+								float timelineScale = 1;
+								if (timelineType == PATH_SPACING) {
+									timeline = new PathConstraintSpacingTimeline(frameCount);
+									if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; 
+								} else {
+									timeline = new PathConstraintPositionTimeline(frameCount);
+									if (data.positionMode == PositionMode.Fixed) timelineScale = scale;
+								}
+								timeline.pathConstraintIndex = index;
+								for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {                                    
+									timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale);
+									if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
+								}
+								timelines.Add(timeline);
+								duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintPositionTimeline.ENTRIES]);
+								break;
+							}
+						case PATH_MIX: {
+								PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount);
+								timeline.pathConstraintIndex = index;
+								for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
+									timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input));
+									if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
+								}
+								timelines.Add(timeline);
+								duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintMixTimeline.ENTRIES]);
+								break;
+							}
+					}
+				}
 			}
 
-			// FFD timelines.
+			// Deform timelines.
 			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
 				Skin skin = skeletonData.skins.Items[ReadVarint(input, true)];
 				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) {
 					int slotIndex = ReadVarint(input, true);
 					for (int iii = 0, nnn = ReadVarint(input, true); iii < nnn; iii++) {
-						Attachment attachment = skin.GetAttachment(slotIndex, ReadString(input));
+						VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, ReadString(input));
+						bool weighted = attachment.bones != null;
+						float[] vertices = attachment.vertices;
+						int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length;
+
 						int frameCount = ReadVarint(input, true);
-						FfdTimeline timeline = new FfdTimeline(frameCount);
+						DeformTimeline timeline = new DeformTimeline(frameCount);
 						timeline.slotIndex = slotIndex;
 						timeline.attachment = attachment;
+
 						for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
 							float time = ReadFloat(input);
-
-							float[] vertices;
-							int vertexCount;
-							if (attachment is MeshAttachment)
-								vertexCount = ((MeshAttachment)attachment).vertices.Length;
-							else
-								vertexCount = ((WeightedMeshAttachment)attachment).weights.Length / 3 * 2;
-
+							float[] deform;
 							int end = ReadVarint(input, true);
-							if (end == 0) {
-								if (attachment is MeshAttachment)
-									vertices = ((MeshAttachment)attachment).vertices;
-								else
-									vertices = new float[vertexCount];
-							} else {
-								vertices = new float[vertexCount];
+							if (end == 0)
+								deform = weighted ? new float[deformLength] : vertices;
+							else {
+								deform = new float[deformLength];
 								int start = ReadVarint(input, true);
 								end += start;
 								if (scale == 1) {
 									for (int v = start; v < end; v++)
-										vertices[v] = ReadFloat(input);
+										deform[v] = ReadFloat(input);
 								} else {
 									for (int v = start; v < end; v++)
-										vertices[v] = ReadFloat(input) * scale;
+										deform[v] = ReadFloat(input) * scale;
 								}
-								if (attachment is MeshAttachment) {
-									float[] meshVertices = ((MeshAttachment)attachment).vertices;
-									for (int v = 0, vn = vertices.Length; v < vn; v++)
-										vertices[v] += meshVertices[v];
+								if (!weighted) {
+									for (int v = 0, vn = deform.Length; v < vn; v++)
+										deform[v] += vertices[v];
 								}
 							}
 
-							timeline.SetFrame(frameIndex, time, vertices);
+							timeline.SetFrame(frameIndex, time, deform);
 							if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
-						}
+						}							
 						timelines.Add(timeline);
 						duration = Math.Max(duration, timeline.frames[frameCount - 1]);
 					}
@@ -756,5 +788,10 @@ namespace Spine {
 				length -= count;
 			}
 		}
+
+		internal class Vertices {
+			public int[] bones;
+			public float[] vertices;
+		}
 	}
 }

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

@@ -30,7 +30,6 @@
  *****************************************************************************/
 
 using System;
-using System.Collections.Generic;
 
 namespace Spine {
 	public class SkeletonBounds {
@@ -80,7 +79,7 @@ namespace Spine {
 				int count = boundingBox.Vertices.Length;
 				polygon.Count = count;
 				if (polygon.Vertices.Length < count) polygon.Vertices = new float[count];
-				boundingBox.ComputeWorldVertices(slot.bone, polygon.Vertices);
+				boundingBox.ComputeWorldVertices(slot, polygon.Vertices);
 			}
 
 			if (updateAabb) aabbCompute();

+ 35 - 11
spine-csharp/src/SkeletonData.cs

@@ -30,7 +30,6 @@
  *****************************************************************************/
 
 using System;
-using System.Collections.Generic;
 
 namespace Spine {
 	public class SkeletonData {
@@ -43,6 +42,7 @@ namespace Spine {
 		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 float width, height;
 		internal String version, hash, imagesPath;
 
@@ -55,6 +55,8 @@ namespace Spine {
 		public ExposedList<EventData> Events { get { return events; } set { events = value; } }
 		public ExposedList<Animation> Animations { get { return animations; } set { animations = value; } }
 		public ExposedList<IkConstraintData> IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } }
+		public ExposedList<TransformConstraintData> TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } }
+		public ExposedList<PathConstraintData> PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } }
 		public float Width { get { return width; } set { width = value; } }
 		public float Height { get { return height; } set { height = value; } }
 		/// <summary>The Spine version used to export this data.</summary>
@@ -65,7 +67,7 @@ namespace Spine {
 
 		/// <returns>May be null.</returns>
 		public BoneData FindBone (String boneName) {
-			if (boneName == null) throw new ArgumentNullException("boneName cannot be null.");
+			if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
 			ExposedList<BoneData> bones = this.bones;
 			for (int i = 0, n = bones.Count; i < n; i++) {
 				BoneData bone = bones.Items[i];
@@ -76,7 +78,7 @@ namespace Spine {
 
 		/// <returns>-1 if the bone was not found.</returns>
 		public int FindBoneIndex (String boneName) {
-			if (boneName == null) throw new ArgumentNullException("boneName cannot be null.");
+			if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
 			ExposedList<BoneData> bones = this.bones;
 			for (int i = 0, n = bones.Count; i < n; i++)
 				if (bones.Items[i].name == boneName) return i;
@@ -87,7 +89,7 @@ namespace Spine {
 
 		/// <returns>May be null.</returns>
 		public SlotData FindSlot (String slotName) {
-			if (slotName == null) throw new ArgumentNullException("slotName cannot be null.");
+			if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
 			ExposedList<SlotData> slots = this.slots;
 			for (int i = 0, n = slots.Count; i < n; i++) {
 				SlotData slot = slots.Items[i];
@@ -96,9 +98,9 @@ namespace Spine {
 			return null;
 		}
 
-		/// <returns>-1 if the bone was not found.</returns>
+		/// <returns>-1 if the slot was not found.</returns>
 		public int FindSlotIndex (String slotName) {
-			if (slotName == null) throw new ArgumentNullException("slotName cannot be null.");
+			if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
 			ExposedList<SlotData> slots = this.slots;
 			for (int i = 0, n = slots.Count; i < n; i++)
 				if (slots.Items[i].name == slotName) return i;
@@ -109,7 +111,7 @@ namespace Spine {
 
 		/// <returns>May be null.</returns>
 		public Skin FindSkin (String skinName) {
-			if (skinName == null) throw new ArgumentNullException("skinName cannot be null.");
+			if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null.");
 			foreach (Skin skin in skins)
 				if (skin.name == skinName) return skin;
 			return null;
@@ -119,7 +121,7 @@ namespace Spine {
 
 		/// <returns>May be null.</returns>
 		public EventData FindEvent (String eventDataName) {
-			if (eventDataName == null) throw new ArgumentNullException("eventDataName cannot be null.");
+			if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null.");
 			foreach (EventData eventData in events)
 				if (eventData.name == eventDataName) return eventData;
 			return null;
@@ -129,7 +131,7 @@ namespace Spine {
 
 		/// <returns>May be null.</returns>
 		public Animation FindAnimation (String animationName) {
-			if (animationName == null) throw new ArgumentNullException("animationName cannot be null.");
+			if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null.");
 			ExposedList<Animation> animations = this.animations;
 			for (int i = 0, n = animations.Count; i < n; i++) {
 				Animation animation = animations.Items[i];
@@ -142,7 +144,7 @@ namespace Spine {
 
 		/// <returns>May be null.</returns>
 		public IkConstraintData FindIkConstraint (String constraintName) {
-			if (constraintName == null) throw new ArgumentNullException("constraintName cannot be null.");
+			if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
 			ExposedList<IkConstraintData> ikConstraints = this.ikConstraints;
 			for (int i = 0, n = ikConstraints.Count; i < n; i++) {
 				IkConstraintData ikConstraint = ikConstraints.Items[i];
@@ -155,7 +157,7 @@ namespace Spine {
 
 		/// <returns>May be null.</returns>
 		public TransformConstraintData FindTransformConstraint (String constraintName) {
-			if (constraintName == null) throw new ArgumentNullException("constraintName cannot be null.");
+			if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
 			ExposedList<TransformConstraintData> transformConstraints = this.transformConstraints;
 			for (int i = 0, n = transformConstraints.Count; i < n; i++) {
 				TransformConstraintData transformConstraint = transformConstraints.Items[i];
@@ -164,6 +166,28 @@ namespace Spine {
 			return null;
 		}
 
+		// --- Path constraints.
+
+		/// <returns>May be null.</returns>
+		public PathConstraintData FindPathConstraint (String constraintName) {
+			if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
+			ExposedList<PathConstraintData> pathConstraints = this.pathConstraints;
+			for (int i = 0, n = pathConstraints.Count; i < n; i++) {
+				PathConstraintData constraint = pathConstraints.Items[i];
+				if (constraint.name.Equals(constraintName)) return constraint;
+			}
+			return null;
+		}
+
+		/// <returns>-1 if the path constraint was not found.</returns>
+		public int FindPathConstraintIndex (String pathConstraintName) {
+			if (pathConstraintName == null) throw new ArgumentNullException("pathConstraintName", "pathConstraintName cannot be null.");
+			ExposedList<PathConstraintData> pathConstraints = this.pathConstraints;
+			for (int i = 0, n = pathConstraints.Count; i < n; i++)
+				if (pathConstraints.Items[i].name.Equals(pathConstraintName)) return i;
+			return -1;
+		}
+
 		// ---
 
 		override public String ToString () {

+ 353 - 291
spine-csharp/src/SkeletonJson.cs

@@ -54,7 +54,7 @@ namespace Spine {
 		}
 
 		public SkeletonJson (AttachmentLoader attachmentLoader) {
-			if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader cannot be null.");
+			if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null.");
 			this.attachmentLoader = attachmentLoader;
 			Scale = 1;
 		}
@@ -79,7 +79,7 @@ namespace Spine {
 			Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path);
 			using (StreamReader reader = new StreamReader(stream)) {
 		#else
-			using (StreamReader reader = new StreamReader(path)) {
+			using (var reader = new StreamReader(path)) {
 		#endif // WINDOWS_PHONE
 				SkeletonData skeletonData = ReadSkeletonData(reader);
 				skeletonData.name = Path.GetFileNameWithoutExtension(path);
@@ -89,7 +89,7 @@ namespace Spine {
 		#endif // WINDOWS_STOREAPP
 
 		public SkeletonData ReadSkeletonData (TextReader reader) {
-			if (reader == null) throw new ArgumentNullException("reader cannot be null.");
+			if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null.");
 
 			var scale = this.Scale;
 			var skeletonData = new SkeletonData();
@@ -114,115 +114,147 @@ namespace Spine {
 					if (parent == null)
 						throw new Exception("Parent bone not found: " + boneMap["parent"]);
 				}
-				var boneData = new BoneData((String)boneMap["name"], parent);
-				boneData.length = GetFloat(boneMap, "length", 0) * scale;
-				boneData.x = GetFloat(boneMap, "x", 0) * scale;
-				boneData.y = GetFloat(boneMap, "y", 0) * scale;
-				boneData.rotation = GetFloat(boneMap, "rotation", 0);
-				boneData.scaleX = GetFloat(boneMap, "scaleX", 1);
-				boneData.scaleY = GetFloat(boneMap, "scaleY", 1);
-				boneData.shearX = GetFloat(boneMap, "shearX", 0);
-				boneData.shearY = GetFloat(boneMap, "shearY", 0);
-				boneData.inheritScale = GetBoolean(boneMap, "inheritScale", true);
-				boneData.inheritRotation = GetBoolean(boneMap, "inheritRotation", true);
-				skeletonData.bones.Add(boneData);
+				var data = new BoneData(skeletonData.Bones.Count, (String)boneMap["name"], parent);
+				data.length = GetFloat(boneMap, "length", 0) * scale;
+				data.x = GetFloat(boneMap, "x", 0) * scale;
+				data.y = GetFloat(boneMap, "y", 0) * scale;
+				data.rotation = GetFloat(boneMap, "rotation", 0);
+				data.scaleX = GetFloat(boneMap, "scaleX", 1);
+				data.scaleY = GetFloat(boneMap, "scaleY", 1);
+				data.shearX = GetFloat(boneMap, "shearX", 0);
+				data.shearY = GetFloat(boneMap, "shearY", 0);
+				data.inheritRotation = GetBoolean(boneMap, "inheritRotation", true);
+				data.inheritScale = GetBoolean(boneMap, "inheritScale", true);
+
+				skeletonData.bones.Add(data);
+			}
+
+			// Slots.
+			if (root.ContainsKey("slots")) {
+				foreach (Dictionary<String, Object> slotMap in (List<Object>)root["slots"]) {
+					var slotName = (String)slotMap["name"];
+					var boneName = (String)slotMap["bone"];
+					BoneData boneData = skeletonData.FindBone(boneName);
+					if (boneData == null) throw new Exception("Slot bone not found: " + boneName);
+					var data = new SlotData(skeletonData.Slots.Count, slotName, boneData);
+
+					if (slotMap.ContainsKey("color")) {
+						var color = (String)slotMap["color"];
+						data.r = ToColor(color, 0);
+						data.g = ToColor(color, 1);
+						data.b = ToColor(color, 2);
+						data.a = ToColor(color, 3);
+					}
+						
+					data.attachmentName = GetString(slotMap, "attachment", null);
+					if (slotMap.ContainsKey("blend"))
+						data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (String)slotMap["blend"], false);
+					else
+						data.blendMode = BlendMode.normal;
+					skeletonData.slots.Add(data);
+				}
 			}
 
 			// IK constraints.
 			if (root.ContainsKey("ik")) {
-				foreach (Dictionary<String, Object> ikMap in (List<Object>)root["ik"]) {
-					IkConstraintData ikConstraintData = new IkConstraintData((String)ikMap["name"]);
+				foreach (Dictionary<String, Object> constraintMap in (List<Object>)root["ik"]) {
+					IkConstraintData data = new IkConstraintData((String)constraintMap["name"]);
 
-					foreach (String boneName in (List<Object>)ikMap["bones"]) {
+					foreach (String boneName in (List<Object>)constraintMap["bones"]) {
 						BoneData bone = skeletonData.FindBone(boneName);
-						if (bone == null) throw new Exception("IK bone not found: " + boneName);
-						ikConstraintData.bones.Add(bone);
+						if (bone == null) throw new Exception("IK constraint bone not found: " + boneName);
+						data.bones.Add(bone);
 					}
+					
+					String targetName = (String)constraintMap["target"];
+					data.target = skeletonData.FindBone(targetName);
+					if (data.target == null) throw new Exception("Target bone not found: " + targetName);
 
-					String targetName = (String)ikMap["target"];
-					ikConstraintData.target = skeletonData.FindBone(targetName);
-					if (ikConstraintData.target == null) throw new Exception("Target bone not found: " + targetName);
+					data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1;
+					data.mix = GetFloat(constraintMap, "mix", 1);
 
-					ikConstraintData.bendDirection = GetBoolean(ikMap, "bendPositive", true) ? 1 : -1;
-					ikConstraintData.mix = GetFloat(ikMap, "mix", 1);
-
-					skeletonData.ikConstraints.Add(ikConstraintData);
+					skeletonData.ikConstraints.Add(data);
 				}
 			}
 
 			// Transform constraints.
 			if (root.ContainsKey("transform")) {
-				foreach (Dictionary<String, Object> transformMap in (List<Object>)root["transform"]) {
-					TransformConstraintData transformConstraintData = new TransformConstraintData((String)transformMap["name"]);
-
-					String boneName = (String)transformMap["bone"];
-					transformConstraintData.bone = skeletonData.FindBone(boneName);
-					if (transformConstraintData.bone == null) throw new Exception("Bone not found: " + boneName);
-
-					String targetName = (String)transformMap["target"];
-					transformConstraintData.target = skeletonData.FindBone(targetName);
-					if (transformConstraintData.target == null) throw new Exception("Target bone not found: " + targetName);
-
-					transformConstraintData.offsetRotation = GetFloat(transformMap, "rotation", 0);
-					transformConstraintData.offsetX = GetFloat(transformMap, "x", 0) * scale;
-					transformConstraintData.offsetY = GetFloat(transformMap, "y", 0) * scale;
-					transformConstraintData.offsetScaleX = GetFloat(transformMap, "scaleX", 0);
-					transformConstraintData.offsetScaleY = GetFloat(transformMap, "scaleY", 0);
-					transformConstraintData.offsetShearY = GetFloat(transformMap, "shearY", 0);
-
-					transformConstraintData.rotateMix = GetFloat(transformMap, "rotateMix", 1);
-					transformConstraintData.translateMix = GetFloat(transformMap, "translateMix", 1);
-					transformConstraintData.scaleMix = GetFloat(transformMap, "scaleMix", 1);
-					transformConstraintData.shearMix = GetFloat(transformMap, "shearMix", 1);
-
-					skeletonData.transformConstraints.Add(transformConstraintData);
+				foreach (Dictionary<String, Object> constraintMap in (List<Object>)root["transform"]) {
+					TransformConstraintData data = new TransformConstraintData((String)constraintMap["name"]);
+
+					foreach (String boneName in (List<Object>)constraintMap["bones"]) {
+						BoneData bone = skeletonData.FindBone(boneName);
+						if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName);
+						data.bones.Add(bone);
+					}
+
+					String targetName = (String)constraintMap["target"];
+					data.target = skeletonData.FindBone(targetName);
+					if (data.target == null) throw new Exception("Target bone not found: " + targetName);
+
+					data.offsetRotation = GetFloat(constraintMap, "rotation", 0);
+					data.offsetX = GetFloat(constraintMap, "x", 0) * scale;
+					data.offsetY = GetFloat(constraintMap, "y", 0) * scale;
+					data.offsetScaleX = GetFloat(constraintMap, "scaleX", 0);
+					data.offsetScaleY = GetFloat(constraintMap, "scaleY", 0);
+					data.offsetShearY = GetFloat(constraintMap, "shearY", 0);
+
+					data.rotateMix = GetFloat(constraintMap, "rotateMix", 1);
+					data.translateMix = GetFloat(constraintMap, "translateMix", 1);
+					data.scaleMix = GetFloat(constraintMap, "scaleMix", 1);
+					data.shearMix = GetFloat(constraintMap, "shearMix", 1);
+
+					skeletonData.transformConstraints.Add(data);
 				}
 			}
 
-			// Slots.
-			if (root.ContainsKey("slots")) {
-				foreach (Dictionary<String, Object> slotMap in (List<Object>)root["slots"]) {
-					var slotName = (String)slotMap["name"];
-					var boneName = (String)slotMap["bone"];
-					BoneData boneData = skeletonData.FindBone(boneName);
-					if (boneData == null)
-						throw new Exception("Slot bone not found: " + boneName);
-					var slotData = new SlotData(slotName, boneData);
+			// Path constraints.
+			if(root.ContainsKey("path")) {
+				foreach (Dictionary<String, Object> constraintMap in (List<Object>)root["path"]) {
+					PathConstraintData data = new PathConstraintData((String)constraintMap["name"]);
 
-					if (slotMap.ContainsKey("color")) {
-						var color = (String)slotMap["color"];
-						slotData.r = ToColor(color, 0);
-						slotData.g = ToColor(color, 1);
-						slotData.b = ToColor(color, 2);
-						slotData.a = ToColor(color, 3);
+					foreach (String boneName in (List<Object>)constraintMap["bones"]) {
+						BoneData bone = skeletonData.FindBone(boneName);
+						if (bone == null) throw new Exception("Path bone not found: " + boneName);
+						data.bones.Add(bone);
 					}
 
-					if (slotMap.ContainsKey("attachment"))
-						slotData.attachmentName = (String)slotMap["attachment"];
-
-					if (slotMap.ContainsKey("blend"))
-						slotData.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (String)slotMap["blend"], false);
-					else
-						slotData.blendMode = BlendMode.normal;
-
-					skeletonData.slots.Add(slotData);
+					String targetName = (String)constraintMap["target"];
+					data.target = skeletonData.FindSlot(targetName);
+					if (data.target == null) throw new Exception("Target slot not found: " + targetName);
+
+					data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true);
+					data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true);
+					data.rotateMode = (RotateMode)Enum.Parse(typeof(RotateMode), GetString(constraintMap, "rotateMode", "tangent"), true);
+					data.offsetRotation = GetFloat(constraintMap, "rotation", 0);
+					data.position = GetFloat(constraintMap, "position", 0);
+					if (data.positionMode == PositionMode.Fixed) data.position *= scale;
+					data.spacing = GetFloat(constraintMap, "spacing", 0);
+					if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale;
+					data.rotateMix = GetFloat(constraintMap, "rotateMix", 1);
+					data.translateMix = GetFloat(constraintMap, "translateMix", 1);
+
+					skeletonData.pathConstraints.Add(data);
 				}
 			}
 
 			// Skins.
 			if (root.ContainsKey("skins")) {
-				foreach (KeyValuePair<String, Object> entry in (Dictionary<String, Object>)root["skins"]) {
-					var skin = new Skin(entry.Key);
-					foreach (KeyValuePair<String, Object> slotEntry in (Dictionary<String, Object>)entry.Value) {
+					foreach (KeyValuePair<String, Object> skinMap in (Dictionary<String, Object>)root["skins"]) {
+					var skin = new Skin(skinMap.Key);
+					foreach (KeyValuePair<String, Object> slotEntry in (Dictionary<String, Object>)skinMap.Value) {
 						int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key);
-						foreach (KeyValuePair<String, Object> attachmentEntry in ((Dictionary<String, Object>)slotEntry.Value)) {
-							Attachment attachment = ReadAttachment(skin, slotIndex, attachmentEntry.Key, (Dictionary<String, Object>)attachmentEntry.Value);
-							if (attachment != null) skin.AddAttachment(slotIndex, attachmentEntry.Key, attachment);
-						}
+						foreach (KeyValuePair<String, Object> entry in ((Dictionary<String, Object>)slotEntry.Value)) {
+							try {
+								Attachment attachment = ReadAttachment((Dictionary<String, Object>)entry.Value, skin, slotIndex, entry.Key);
+								if (attachment != null) skin.AddAttachment(slotIndex, entry.Key, attachment);
+							} catch (Exception e) {
+								throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e);
+							}
+						} 
 					}
 					skeletonData.skins.Add(skin);
-					if (skin.name == "default")
-						skeletonData.defaultSkin = skin;
+					if (skin.name == "default") skeletonData.defaultSkin = skin;
 				}
 			}
 
@@ -233,15 +265,8 @@ namespace Spine {
 				if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin);
 				Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent);
 				if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent);
-				if (linkedMesh.mesh is MeshAttachment) {
-					MeshAttachment mesh = (MeshAttachment)linkedMesh.mesh;
-					mesh.ParentMesh = (MeshAttachment)parent;
-					mesh.UpdateUVs();
-				} else {
-					WeightedMeshAttachment mesh = (WeightedMeshAttachment)linkedMesh.mesh;
-					mesh.ParentMesh = (WeightedMeshAttachment)parent;
-					mesh.UpdateUVs();
-				}
+				linkedMesh.mesh.ParentMesh = (MeshAttachment)parent;
+				linkedMesh.mesh.UpdateUVs();
 			}
 			linkedMeshes.Clear();
 
@@ -249,18 +274,23 @@ namespace Spine {
 			if (root.ContainsKey("events")) {
 				foreach (KeyValuePair<String, Object> entry in (Dictionary<String, Object>)root["events"]) {
 					var entryMap = (Dictionary<String, Object>)entry.Value;
-					var eventData = new EventData(entry.Key);
-					eventData.Int = GetInt(entryMap, "int", 0);
-					eventData.Float = GetFloat(entryMap, "float", 0);
-					eventData.String = GetString(entryMap, "string", null);
-					skeletonData.events.Add(eventData);
+					var data = new EventData(entry.Key);
+					data.Int = GetInt(entryMap, "int", 0);
+					data.Float = GetFloat(entryMap, "float", 0);
+					data.String = GetString(entryMap, "string", null);
+					skeletonData.events.Add(data);
 				}
 			}
 
 			// Animations.
 			if (root.ContainsKey("animations")) {
-				foreach (KeyValuePair<String, Object> entry in (Dictionary<String, Object>)root["animations"])
-					ReadAnimation(entry.Key, (Dictionary<String, Object>)entry.Value, skeletonData);
+				foreach (KeyValuePair<String, Object> entry in (Dictionary<String, Object>)root["animations"]) {
+					try {
+						ReadAnimation((Dictionary<String, Object>)entry.Value, entry.Key, skeletonData);
+					} catch (Exception e) {
+						throw new Exception("Error reading animation: " + entry.Key, e);
+					}
+				}   
 			}
 
 			skeletonData.bones.TrimExcess();
@@ -272,25 +302,20 @@ namespace Spine {
 			return skeletonData;
 		}
 
-		private Attachment ReadAttachment (Skin skin, int slotIndex, String name, Dictionary<String, Object> map) {
-			if (map.ContainsKey("name"))
-				name = (String)map["name"];
-
+		private Attachment ReadAttachment (Dictionary<String, Object> map, Skin skin, int slotIndex, String name) {
 			var scale = this.Scale;
+			name = GetString(map, "name", name);
 
-			var type = AttachmentType.region;
-			if (map.ContainsKey("type")) {
-				var typeName = (String)map["type"];
-				if (typeName == "skinnedmesh") typeName = "weightedmesh";
-				type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName , false);
-			}
+			var typeName = GetString(map, "type", "region");
+			if (typeName == "skinnedmesh") typeName = "weightedmesh";
+			if (typeName == "weightedmesh") typeName = "mesh";
+			if (typeName == "weightedlinkedmesh") typeName = "linkedmesh";
+			var type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, true);
 
-			String path = name;
-			if (map.ContainsKey("path"))
-				path = (String)map["path"];
+			String path = GetString(map, "path", name);
 
 			switch (type) {
-			case AttachmentType.region:
+			case AttachmentType.Region:
 				RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path);
 				if (region == null) return null;
 				region.Path = path;
@@ -311,9 +336,15 @@ namespace Spine {
 					region.a = ToColor(color, 3);
 				}
 
+				region.UpdateOffset();
 				return region;
-			case AttachmentType.mesh:
-			case AttachmentType.linkedmesh: {
+			case AttachmentType.Boundingbox:
+				BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name);
+				if (box == null) return null;
+				ReadVertices(map, box, GetInt(map, "vertexCount", 0) << 1);
+				return box;
+			case AttachmentType.Mesh:
+			case AttachmentType.Linkedmesh: {
 					MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path);
 					if (mesh == null) return null;
 					mesh.Path = path;
@@ -326,145 +357,83 @@ namespace Spine {
 						mesh.a = ToColor(color, 3);
 					}
 
-					mesh.Width = GetInt(map, "width", 0) * scale;
-					mesh.Height = GetInt(map, "height", 0) * scale;
+					mesh.Width = GetFloat(map, "width", 0) * scale;
+					mesh.Height = GetFloat(map, "height", 0) * scale;
 
 					String parent = GetString(map, "parent", null);
-					if (parent == null) {
-						mesh.vertices = GetFloatArray(map, "vertices", scale);
-						mesh.triangles = GetIntArray(map, "triangles");
-						mesh.regionUVs = GetFloatArray(map, "uvs", 1);
-						mesh.UpdateUVs();
-
-						mesh.HullLength = GetInt(map, "hull", 0) * 2;
-						if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges");
-					} else {
-						mesh.InheritFFD = GetBoolean(map, "ffd", true);
+					if (parent != null) {
+						mesh.InheritDeform = GetBoolean(map, "deform", true);
 						linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent));
+						return mesh;
 					}
 
-					return mesh;
-				}
-			case AttachmentType.weightedmesh:
-			case AttachmentType.weightedlinkedmesh: {
-					WeightedMeshAttachment mesh = attachmentLoader.NewWeightedMeshAttachment(skin, name, path);
-					if (mesh == null) return null;
-
-					mesh.Path = path;
-
-					if (map.ContainsKey("color")) {
-						var color = (String)map["color"];
-						mesh.r = ToColor(color, 0);
-						mesh.g = ToColor(color, 1);
-						mesh.b = ToColor(color, 2);
-						mesh.a = ToColor(color, 3);
-					}
-
-					mesh.Width = GetInt(map, "width", 0) * scale;
-					mesh.Height = GetInt(map, "height", 0) * scale;
-
-					String parent = GetString(map, "parent", null);
-					if (parent == null) {
-						float[] uvs = GetFloatArray(map, "uvs", 1);
-						float[] vertices = GetFloatArray(map, "vertices", 1);
-						var weights = new List<float>(uvs.Length * 3 * 3);
-						var bones = new List<int>(uvs.Length * 3);
-						for (int i = 0, n = vertices.Length; i < n;) {
-							int boneCount = (int)vertices[i++];
-							bones.Add(boneCount);
-							for (int nn = i + boneCount * 4; i < nn; i += 4) {
-								bones.Add((int)vertices[i]);
-								weights.Add(vertices[i + 1] * scale);
-								weights.Add(vertices[i + 2] * scale);
-								weights.Add(vertices[i + 3]);
-							}
-						}
-						mesh.bones = bones.ToArray();
-						mesh.weights = weights.ToArray();
-						mesh.triangles = GetIntArray(map, "triangles");
-						mesh.regionUVs = uvs;
-						mesh.UpdateUVs();
-
-						mesh.HullLength = GetInt(map, "hull", 0) * 2;
-						if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges");
-					} else {
-						mesh.InheritFFD = GetBoolean(map, "ffd", true);
-						linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent));
-					}
+					float[] uvs = GetFloatArray(map, "uvs", 1);
+					ReadVertices(map, mesh, uvs.Length);
+					mesh.triangles = GetIntArray(map, "triangles");
+					mesh.regionUVs = uvs;
+					mesh.UpdateUVs();
 
+					if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) * 2;
+					if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges");
 					return mesh;
 				}
-			case AttachmentType.boundingbox:
-				BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name);
-				if (box == null) return null;
-				box.vertices = GetFloatArray(map, "vertices", scale);
-				return box;
+			case AttachmentType.Path: {
+					PathAttachment pathAttachment = attachmentLoader.NewPathAttachment(skin, name);
+					if (pathAttachment == null) return null;
+					pathAttachment.closed = GetBoolean(map, "closed", false);
+					pathAttachment.constantSpeed = GetBoolean(map, "constantSpeed", true);
+
+					int vertexCount = GetInt(map, "vertexCount", 0);
+					ReadVertices(map, pathAttachment, vertexCount << 1);
+
+					// potential BOZO see Java impl
+					pathAttachment.lengths = GetFloatArray(map, "lengths", scale);
+					return pathAttachment;
+				}
 			}
 			return null;
 		}
 
-		private float[] GetFloatArray (Dictionary<String, Object> map, String name, float scale) {
-			var list = (List<Object>)map[name];
-			var values = new float[list.Count];
-			if (scale == 1) {
-				for (int i = 0, n = list.Count; i < n; i++)
-					values[i] = (float)list[i];
-			} else {
-				for (int i = 0, n = list.Count; i < n; i++)
-					values[i] = (float)list[i] * scale;
+		private void ReadVertices (Dictionary<String, Object> map, VertexAttachment attachment, int verticesLength) {
+			attachment.WorldVerticesLength = verticesLength;
+			float[] vertices = GetFloatArray(map, "vertices", 1);
+			float scale = Scale;
+			if (verticesLength == vertices.Length) {
+				if (scale != 1) {
+					for (int i = 0; i < vertices.Length; i++) {
+						vertices[i] *= scale;
+					}
+				}
+				attachment.vertices = vertices;
+				return;
 			}
-			return values;
-		}
-
-		private int[] GetIntArray (Dictionary<String, Object> map, String name) {
-			var list = (List<Object>)map[name];
-			var values = new int[list.Count];
-			for (int i = 0, n = list.Count; i < n; i++)
-				values[i] = (int)(float)list[i];
-			return values;
-		}
-
-		private float GetFloat (Dictionary<String, Object> map, String name, float defaultValue) {
-			if (!map.ContainsKey(name))
-				return defaultValue;
-			return (float)map[name];
-		}
-
-		private int GetInt (Dictionary<String, Object> map, String name, int defaultValue) {
-			if (!map.ContainsKey(name))
-				return defaultValue;
-			return (int)(float)map[name];
-		}
-
-		private bool GetBoolean (Dictionary<String, Object> map, String name, bool defaultValue) {
-			if (!map.ContainsKey(name))
-				return defaultValue;
-			return (bool)map[name];
-		}
-
-		private String GetString (Dictionary<String, Object> map, String name, String defaultValue) {
-			if (!map.ContainsKey(name))
-				return defaultValue;
-			return (String)map[name];
-		}
-
-		private float ToColor (String hexString, int colorIndex) {
-			if (hexString.Length != 8)
-				throw new ArgumentException("Color hexidecimal length must be 8, recieved: " + hexString);
-			return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255;
+			ExposedList<float> weights = new ExposedList<float>(verticesLength * 3 * 3);
+			ExposedList<int> bones = new ExposedList<int>(verticesLength * 3);
+			for (int i = 0, n = vertices.Length; i < n;) {
+				int boneCount = (int)vertices[i++];
+				bones.Add(boneCount);
+				for (int nn = i + boneCount * 4; i < nn; i += 4) {
+					bones.Add((int)vertices[i]);
+					weights.Add(vertices[i + 1] * this.Scale);
+					weights.Add(vertices[i + 2] * this.Scale);
+					weights.Add(vertices[i + 3]);
+				}
+			}
+			attachment.bones = bones.ToArray();
+			attachment.vertices = weights.ToArray();
 		}
 
-		private void ReadAnimation (String name, Dictionary<String, Object> map, SkeletonData skeletonData) {
+		private void ReadAnimation (Dictionary<String, Object> map, String name, SkeletonData skeletonData) {
+			var scale = this.Scale;
 			var timelines = new ExposedList<Timeline>();
 			float duration = 0;
-			var scale = this.Scale;
 
+			// Slot timelines.
 			if (map.ContainsKey("slots")) {
 				foreach (KeyValuePair<String, Object> entry in (Dictionary<String, Object>)map["slots"]) {
 					String slotName = entry.Key;
 					int slotIndex = skeletonData.FindSlotIndex(slotName);
 					var timelineMap = (Dictionary<String, Object>)entry.Value;
-
 					foreach (KeyValuePair<String, Object> timelineEntry in timelineMap) {
 						var values = (List<Object>)timelineEntry.Value;
 						var timelineName = (String)timelineEntry.Key;
@@ -477,11 +446,11 @@ namespace Spine {
 								float time = (float)valueMap["time"];
 								String c = (String)valueMap["color"];
 								timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3));
-								ReadCurve(timeline, frameIndex, valueMap);
+								ReadCurve(valueMap, timeline, frameIndex);
 								frameIndex++;
 							}
 							timelines.Add(timeline);
-							duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 5 - 5]);
+							duration = Math.Max(duration, timeline.frames[timeline.FrameCount -1] * ColorTimeline.ENTRIES);
 
 						} else if (timelineName == "attachment") {
 							var timeline = new AttachmentTimeline(values.Count);
@@ -501,13 +470,12 @@ namespace Spine {
 				}
 			}
 
+			// Bone timelines.
 			if (map.ContainsKey("bones")) {
 				foreach (KeyValuePair<String, Object> entry in (Dictionary<String, Object>)map["bones"]) {
 					String boneName = entry.Key;
 					int boneIndex = skeletonData.FindBoneIndex(boneName);
-					if (boneIndex == -1)
-						throw new Exception("Bone not found: " + boneName);
-
+					if (boneIndex == -1) throw new Exception("Bone not found: " + boneName);
 					var timelineMap = (Dictionary<String, Object>)entry.Value;
 					foreach (KeyValuePair<String, Object> timelineEntry in timelineMap) {
 						var values = (List<Object>)timelineEntry.Value;
@@ -518,13 +486,12 @@ namespace Spine {
 
 							int frameIndex = 0;
 							foreach (Dictionary<String, Object> valueMap in values) {
-								float time = (float)valueMap["time"];
-								timeline.SetFrame(frameIndex, time, (float)valueMap["angle"]);
-								ReadCurve(timeline, frameIndex, valueMap);
+								timeline.SetFrame(frameIndex, (float)valueMap["time"], (float)valueMap["angle"]);
+								ReadCurve(valueMap, timeline, frameIndex);
 								frameIndex++;
 							}
 							timelines.Add(timeline);
-							duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 2 - 2]);
+							duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * RotateTimeline.ENTRIES]);
 
 						} else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") {
 							TranslateTimeline timeline;
@@ -544,12 +511,12 @@ namespace Spine {
 								float time = (float)valueMap["time"];
 								float x = GetFloat(valueMap, "x", 0);
 								float y = GetFloat(valueMap, "y", 0);
-								timeline.SetFrame(frameIndex, time, (float)x * timelineScale, (float)y * timelineScale);
-								ReadCurve(timeline, frameIndex, valueMap);
+								timeline.SetFrame(frameIndex, time, x * timelineScale, y * timelineScale);
+								ReadCurve(valueMap, timeline, frameIndex);
 								frameIndex++;
 							}
 							timelines.Add(timeline);
-							duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 3 - 3]);
+							duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TranslateTimeline.ENTRIES]);
 
 						} else
 							throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")");
@@ -557,7 +524,7 @@ namespace Spine {
 				}
 			}
 
-			// IK timelines.
+			// IK constraint timelines.
 			if (map.ContainsKey("ik")) {
 				foreach (KeyValuePair<String, Object> constraintMap in (Dictionary<String, Object>)map["ik"]) {
 					IkConstraintData constraint = skeletonData.FindIkConstraint(constraintMap.Key);
@@ -570,11 +537,11 @@ namespace Spine {
 						float mix = GetFloat(valueMap, "mix", 1);
 						bool bendPositive = GetBoolean(valueMap, "bendPositive", true);
 						timeline.SetFrame(frameIndex, time, mix, bendPositive ? 1 : -1);
-						ReadCurve(timeline, frameIndex, valueMap);
+						ReadCurve(valueMap, timeline, frameIndex);
 						frameIndex++;
 					}
 					timelines.Add(timeline);
-					duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 3 - 3]);
+					duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * IkConstraintTimeline.ENTRIES]);
 				}
 			}
 
@@ -593,62 +560,103 @@ namespace Spine {
 						float scaleMix = GetFloat(valueMap, "scaleMix", 1);
 						float shearMix = GetFloat(valueMap, "shearMix", 1);
 						timeline.SetFrame(frameIndex, time, rotateMix, translateMix, scaleMix, shearMix);
-						ReadCurve(timeline, frameIndex, valueMap);
+						ReadCurve(valueMap, timeline, frameIndex);
 						frameIndex++;
 					}
 					timelines.Add(timeline);
-					duration = Math.Max(duration, timeline.frames[timeline.FrameCount * 5 - 5]);
+					duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TransformConstraintTimeline.ENTRIES]);
+				}
+			}
+
+			// Path constraint timelines.
+			if (map.ContainsKey("paths")) {
+				foreach (KeyValuePair<String, Object> constraintMap in (Dictionary<String, Object>)map["paths"]) {
+					int index = skeletonData.FindPathConstraintIndex(constraintMap.Key);
+					if (index == -1) throw new Exception("Path constraint not found: " + constraintMap.Key);
+					PathConstraintData data = skeletonData.pathConstraints.Items[index];
+					var timelineMap = (Dictionary<String, Object>)constraintMap.Value;
+					foreach (KeyValuePair<String, Object> timelineEntry in timelineMap) {
+						var values = (List<Object>)timelineEntry.Value;
+						var timelineName = (String)timelineEntry.Key;
+						if (timelineName == "position" || timelineName == "spacing") {
+							PathConstraintPositionTimeline timeline;
+							float timelineScale = 1;
+							if (timelineName == "spacing") {
+								timeline = new PathConstraintSpacingTimeline(values.Count);
+								if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale;
+							}
+							else {
+								timeline = new PathConstraintPositionTimeline(values.Count);
+								if (data.positionMode == PositionMode.Fixed) timelineScale = scale;
+							}
+							timeline.pathConstraintIndex = index;
+							int frameIndex = 0;
+							foreach (Dictionary<String, Object> valueMap in values) {
+								timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, timelineName, 0) * timelineScale);
+								ReadCurve(valueMap, timeline, frameIndex);
+								frameIndex++;
+							}
+							timelines.Add(timeline);
+							duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintPositionTimeline.ENTRIES]);
+						}
+						else if (timelineName == "mix") {
+							PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(values.Count);
+							timeline.pathConstraintIndex = index;
+							int frameIndex = 0;
+							foreach (Dictionary<String, Object> valueMap in values) {
+								timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, "rotateMix", 1), GetFloat(valueMap, "translateMix", 1));
+								ReadCurve(valueMap, timeline, frameIndex);
+								frameIndex++;
+							}
+							timelines.Add(timeline);
+							duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintMixTimeline.ENTRIES]);
+						}
+					}
 				}
 			}
 
-			// FFD timelines.
-			if (map.ContainsKey("ffd")) {
-				foreach (KeyValuePair<String, Object> ffdMap in (Dictionary<String, Object>)map["ffd"]) {
-					Skin skin = skeletonData.FindSkin(ffdMap.Key);
-					foreach (KeyValuePair<String, Object> slotMap in (Dictionary<String, Object>)ffdMap.Value) {
+			// Deform timelines.
+			if (map.ContainsKey("deform")) {
+				foreach (KeyValuePair<String, Object> deformMap in (Dictionary<String, Object>)map["deform"]) {
+					Skin skin = skeletonData.FindSkin(deformMap.Key);
+					foreach (KeyValuePair<String, Object> slotMap in (Dictionary<String, Object>)deformMap.Value) {
 						int slotIndex = skeletonData.FindSlotIndex(slotMap.Key);
-						foreach (KeyValuePair<String, Object> meshMap in (Dictionary<String, Object>)slotMap.Value) {
-							var values = (List<Object>)meshMap.Value;
-							var timeline = new FfdTimeline(values.Count);
-							Attachment attachment = skin.GetAttachment(slotIndex, meshMap.Key);
-							if (attachment == null) throw new Exception("FFD attachment not found: " + meshMap.Key);
+						if (slotIndex == -1) throw new Exception("Slot not found: " + slotMap.Key);
+						foreach (KeyValuePair<String, Object> timelineMap in (Dictionary<String, Object>)slotMap.Value) {
+							var values = (List<Object>)timelineMap.Value;
+							VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, timelineMap.Key);
+							if (attachment == null) throw new Exception("Deform attachment not found: " + timelineMap.Key);
+							bool weighted = attachment.bones != null;
+							float[] vertices = attachment.vertices;
+							int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length;
+
+							var timeline = new DeformTimeline(values.Count);
 							timeline.slotIndex = slotIndex;
 							timeline.attachment = attachment;
-
-							int vertexCount;
-							if (attachment is MeshAttachment)
-								vertexCount = ((MeshAttachment)attachment).vertices.Length;
-							else
-								vertexCount = ((WeightedMeshAttachment)attachment).Weights.Length / 3 * 2;
-
+							
 							int frameIndex = 0;
 							foreach (Dictionary<String, Object> valueMap in values) {
-								float[] vertices;
+								float[] deform;
 								if (!valueMap.ContainsKey("vertices")) {
-									if (attachment is MeshAttachment)
-										vertices = ((MeshAttachment)attachment).vertices;
-									else
-										vertices = new float[vertexCount];
+									deform = weighted ? new float[deformLength] : vertices;
 								} else {
-									var verticesValue = (List<Object>)valueMap["vertices"];
-									vertices = new float[vertexCount];
+									deform = new float[deformLength];
 									int start = GetInt(valueMap, "offset", 0);
-									if (scale == 1) {
-										for (int i = 0, n = verticesValue.Count; i < n; i++)
-											vertices[i + start] = (float)verticesValue[i];
-									} else {
-										for (int i = 0, n = verticesValue.Count; i < n; i++)
-											vertices[i + start] = (float)verticesValue[i] * scale;
+									float[] verticesValue = GetFloatArray(valueMap, "vertices", 1);
+									Array.Copy(verticesValue, 0, deform, start, verticesValue.Length);
+									if (scale != 1) {
+										for (int i = start, n = i + verticesValue.Length; i < n; i++)
+											deform[i] *= scale;
 									}
-									if (attachment is MeshAttachment) {
-										float[] meshVertices = ((MeshAttachment)attachment).vertices;
-										for (int i = 0; i < vertexCount; i++)
-											vertices[i] += meshVertices[i];
+
+									if (!weighted) {
+										for (int i = 0; i < deformLength; i++)
+											deform[i] += vertices[i];
 									}
 								}
 
-								timeline.SetFrame(frameIndex, (float)valueMap["time"], vertices);
-								ReadCurve(timeline, frameIndex, valueMap);
+								timeline.SetFrame(frameIndex, (float)valueMap["time"], deform);
+								ReadCurve(valueMap, timeline, frameIndex);
 								frameIndex++;
 							}
 							timelines.Add(timeline);
@@ -658,6 +666,7 @@ namespace Spine {
 				}
 			}
 
+			// Draw order timeline.
 			if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) {
 				var values = (List<Object>)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"];
 				var timeline = new DrawOrderTimeline(values.Count);
@@ -695,6 +704,7 @@ namespace Spine {
 				duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
 			}
 
+			// Event timeline.
 			if (map.ContainsKey("events")) {
 				var eventsMap = (List<Object>)map["events"];
 				var timeline = new EventTimeline(eventsMap.Count);
@@ -716,29 +726,81 @@ namespace Spine {
 			skeletonData.animations.Add(new Animation(name, timelines, duration));
 		}
 
-		private void ReadCurve (CurveTimeline timeline, int frameIndex, Dictionary<String, Object> valueMap) {
+		static void ReadCurve (Dictionary<String, Object> valueMap, CurveTimeline timeline, int frameIndex) {
 			if (!valueMap.ContainsKey("curve"))
 				return;
 			Object curveObject = valueMap["curve"];
 			if (curveObject.Equals("stepped"))
 				timeline.SetStepped(frameIndex);
-			else if (curveObject is List<Object>) {
-				var curve = (List<Object>)curveObject;
-				timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]);
+			else {
+				var curve = curveObject as List<Object>;
+				if (curve != null)
+					timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]);
 			}
 		}
 
 		internal class LinkedMesh {
 			internal String parent, skin;
 			internal int slotIndex;
-			internal Attachment mesh;
+			internal MeshAttachment mesh;
 
-			public LinkedMesh (Attachment mesh, String skin, int slotIndex, String parent) {
+			public LinkedMesh (MeshAttachment mesh, String skin, int slotIndex, String parent) {
 				this.mesh = mesh;
 				this.skin = skin;
 				this.slotIndex = slotIndex;
 				this.parent = parent;
 			}
 		}
+
+		static float[] GetFloatArray(Dictionary<String, Object> map, String name, float scale) {
+			var list = (List<Object>)map[name];
+			var values = new float[list.Count];
+			if (scale == 1) {
+				for (int i = 0, n = list.Count; i < n; i++)
+					values[i] = (float)list[i];
+			} else {
+				for (int i = 0, n = list.Count; i < n; i++)
+					values[i] = (float)list[i] * scale;
+			}
+			return values;
+		}
+
+		static int[] GetIntArray(Dictionary<String, Object> map, String name) {
+			var list = (List<Object>)map[name];
+			var values = new int[list.Count];
+			for (int i = 0, n = list.Count; i < n; i++)
+				values[i] = (int)(float)list[i];
+			return values;
+		}
+
+		static float GetFloat(Dictionary<String, Object> map, String name, float defaultValue) {
+			if (!map.ContainsKey(name))
+				return defaultValue;
+			return (float)map[name];
+		}
+
+		static int GetInt(Dictionary<String, Object> map, String name, int defaultValue) {
+			if (!map.ContainsKey(name))
+				return defaultValue;
+			return (int)(float)map[name];
+		}
+
+		static bool GetBoolean(Dictionary<String, Object> map, String name, bool defaultValue) {
+			if (!map.ContainsKey(name))
+				return defaultValue;
+			return (bool)map[name];
+		}
+
+		static String GetString(Dictionary<String, Object> map, String name, String defaultValue) {
+			if (!map.ContainsKey(name))
+				return defaultValue;
+			return (String)map[name];
+		}
+
+		static float ToColor(String hexString, int colorIndex) {
+			if (hexString.Length != 8)
+				throw new ArgumentException("Color hexidecimal length must be 8, recieved: " + hexString, "hexString");
+			return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255;
+		}
 	}
 }

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

@@ -40,14 +40,15 @@ namespace Spine {
 			new Dictionary<AttachmentKeyTuple, Attachment>(AttachmentKeyTupleComparer.Instance);
 
 		public String Name { get { return name; } }
+		public Dictionary<AttachmentKeyTuple, Attachment> Attachments { get { return attachments; } }
 
 		public Skin (String name) {
-			if (name == null) throw new ArgumentNullException("name cannot be null.");
+			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
 			this.name = name;
 		}
 
 		public void AddAttachment (int slotIndex, String name, Attachment attachment) {
-			if (attachment == null) throw new ArgumentNullException("attachment cannot be null.");
+			if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null.");
 			attachments[new AttachmentKeyTuple(slotIndex, name)] = attachment;
 		}
 
@@ -59,13 +60,13 @@ namespace Spine {
 		}
 
 		public void FindNamesForSlot (int slotIndex, List<String> names) {
-			if (names == null) throw new ArgumentNullException("names cannot be null.");
+			if (names == null) throw new ArgumentNullException("names", "names cannot be null.");
 			foreach (AttachmentKeyTuple key in attachments.Keys)
 				if (key.slotIndex == slotIndex) names.Add(key.name);
 		}
 
 		public void FindAttachmentsForSlot (int slotIndex, List<Attachment> attachments) {
-			if (attachments == null) throw new ArgumentNullException("attachments cannot be null.");
+			if (attachments == null) throw new ArgumentNullException("attachments", "attachments cannot be null.");
 			foreach (KeyValuePair<AttachmentKeyTuple, Attachment> entry in this.attachments)
 				if (entry.Key.slotIndex == slotIndex) attachments.Add(entry.Value);
 		}
@@ -86,7 +87,7 @@ namespace Spine {
 			}
 		}
 
-		struct AttachmentKeyTuple {
+		public struct AttachmentKeyTuple {
 			public readonly int slotIndex;
 			public readonly string name;
 			internal readonly int nameHashCode;

+ 10 - 22
spine-csharp/src/Slot.cs

@@ -38,8 +38,7 @@ namespace Spine {
 		internal float r, g, b, a;
 		internal Attachment attachment;
 		internal float attachmentTime;
-		internal float[] attachmentVertices = new float[0];
-		internal int attachmentVerticesCount;
+		internal ExposedList<float> attachmentVertices = new ExposedList<float>();
 
 		public SlotData Data { get { return data; } }
 		public Bone Bone { get { return bone; } }
@@ -51,38 +50,31 @@ namespace Spine {
 
 		/// <summary>May be null.</summary>
 		public Attachment Attachment {
-			get {
-				return attachment;
-			}
+			get { return attachment; }
 			set {
 				if (attachment == value) return;
 				attachment = value;
 				attachmentTime = bone.skeleton.time;
-				attachmentVerticesCount = 0;
+				attachmentVertices.Clear(false);
 			}
 		}
 
 		public float AttachmentTime {
-			get {
-				return bone.skeleton.time - attachmentTime;
-			}
-			set {
-				attachmentTime = bone.skeleton.time - value;
-			}
+			get { return bone.skeleton.time - attachmentTime; }
+			set { attachmentTime = bone.skeleton.time - value; }
 		}
 
-		public float[] AttachmentVertices { get { return attachmentVertices; } set { attachmentVertices = value; } }
-		public int AttachmentVerticesCount { get { return attachmentVerticesCount; } set { attachmentVerticesCount = value; } }
+		public ExposedList<float> AttachmentVertices { get { return attachmentVertices; } set { attachmentVertices = value; } }
 
 		public Slot (SlotData data, Bone bone) {
-			if (data == null) throw new ArgumentNullException("data cannot be null.");
-			if (bone == null) throw new ArgumentNullException("bone cannot be null.");
+			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;
 			SetToSetupPose();
 		}
 
-		internal void SetToSetupPose (int slotIndex) {
+		public void SetToSetupPose () {
 			r = data.r;
 			g = data.g;
 			b = data.b;
@@ -91,14 +83,10 @@ namespace Spine {
 				Attachment = null;
 			else {
 				attachment = null;
-				Attachment = bone.skeleton.GetAttachment(slotIndex, data.attachmentName);
+				Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName);
 			}
 		}
 
-		public void SetToSetupPose () {
-			SetToSetupPose(bone.skeleton.data.slots.IndexOf(data));
-		}
-
 		override public String ToString () {
 			return data.name;
 		}

+ 7 - 3
spine-csharp/src/SlotData.cs

@@ -33,12 +33,14 @@ using System;
 
 namespace Spine {
 	public class SlotData {
+		internal int index;
 		internal String name;
 		internal BoneData boneData;
 		internal float r = 1, g = 1, b = 1, a = 1;
 		internal String attachmentName;
 		internal BlendMode blendMode;
 
+		public int Index { get { return index; } }
 		public String Name { get { return name; } }
 		public BoneData BoneData { get { return boneData; } }
 		public float R { get { return r; } set { r = value; } }
@@ -49,9 +51,11 @@ namespace Spine {
 		public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } }
 		public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } }
 
-		public SlotData (String name, BoneData boneData) {
-			if (name == null) throw new ArgumentNullException("name cannot be null.");
-			if (boneData == null) throw new ArgumentNullException("boneData cannot be null.");
+		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;
 		}

+ 58 - 65
spine-csharp/src/TransformConstraint.cs

@@ -30,46 +30,35 @@
  *****************************************************************************/
 
 using System;
-using System.Collections.Generic;
 
 namespace Spine {
 	public class TransformConstraint : IUpdatable {
 		internal TransformConstraintData data;
-		internal Bone bone, target;
+		internal ExposedList<Bone> bones;
+		internal Bone target;
 		internal float rotateMix, translateMix, scaleMix, shearMix;
-		internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY;
 
 		public TransformConstraintData Data { get { return data; } }
-		public Bone Bone { get { return bone; } set { bone = value; } }
+		public ExposedList<Bone> Bones { get { return bones; } }
 		public Bone Target { get { return target; } set { target = value; } }
 		public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
 		public float TranslateMix { get { return translateMix; } set { translateMix = value; } }
 		public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } }
 		public float ShearMix { get { return shearMix; } set { shearMix = value; } }
 
-		public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } }
-		public float OffsetX { get { return offsetX; } set { offsetX = value; } }
-		public float OffsetY { get { return offsetY; } set { offsetY = value; } }
-		public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } }
-		public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } }
-		public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } }
-
 		public TransformConstraint (TransformConstraintData data, Skeleton skeleton) {
-			if (data == null) throw new ArgumentNullException("data cannot be null.");
-			if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null.");
+			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;
-			translateMix = data.translateMix;
 			rotateMix = data.rotateMix;
+			translateMix = data.translateMix;
 			scaleMix = data.scaleMix;
 			shearMix = data.shearMix;
-			offsetRotation = data.offsetRotation;
-			offsetX = data.offsetX;
-			offsetY = data.offsetY;
-			offsetScaleX = data.offsetScaleX;
-			offsetScaleY = data.offsetScaleY;
-			offsetShearY = data.offsetShearY;
 
-			bone = skeleton.FindBone(data.bone.name);
+			bones = new ExposedList<Bone>();
+			foreach (BoneData boneData in data.bones)
+				bones.Add (skeleton.FindBone (boneData.name));
+			
 			target = skeleton.FindBone(data.target.name);
 		}
 
@@ -78,55 +67,59 @@ namespace Spine {
 		}
 
 		public void Update () {
-			Bone bone = this.bone;
+			float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix;
 			Bone target = this.target;
+			float ta = target.a, tb = target.b, tc = target.c, td = target.d;
+			ExposedList<Bone> bones = this.bones;
+			for (int i = 0, n = bones.Count; i < n; i++) {
+				Bone bone = bones.Items[i];
 
-			if (rotateMix > 0) {
-				float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
-				float r = MathUtils.Atan2(target.c, target.a) - MathUtils.Atan2(c, a) + offsetRotation * MathUtils.degRad;
-				if (r > MathUtils.PI)
-					r -= MathUtils.PI2;
-				else if (r < -MathUtils.PI) r += MathUtils.PI2;
-				r *= rotateMix;
-				float cos = MathUtils.Cos(r), sin = MathUtils.Sin(r);
-				bone.a = cos * a - sin * c;
-				bone.b = cos * b - sin * d;
-				bone.c = sin * a + cos * c;
-				bone.d = sin * b + cos * d;
-			}
+				if (rotateMix > 0) {
+					float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
+					float r = (float)Math.Atan2(tc, ta) - (float)Math.Atan2(c, a) + data.offsetRotation * MathUtils.degRad;
+					if (r > MathUtils.PI)
+						r -= MathUtils.PI2;
+					else if (r < -MathUtils.PI) r += MathUtils.PI2;
+					r *= rotateMix;
+					float cos = MathUtils.Cos(r), sin = MathUtils.Sin(r);
+					bone.a = cos * a - sin * c;
+					bone.b = cos * b - sin * d;
+					bone.c = sin * a + cos * c;
+					bone.d = sin * b + cos * d;
+				}
 
-			if (scaleMix > 0) {
-				float bs = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c);
-				float ts = (float)Math.Sqrt(target.a * target.a + target.c * target.c);
-				float s = bs > 0.00001f ? (bs + (ts - bs + offsetScaleX) * scaleMix) / bs : 0;
-				bone.a *= s;
-				bone.c *= s;
-				bs = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d);
-				ts = (float)Math.Sqrt(target.b * target.b + target.d * target.d);
-				s = bs > 0.00001f ? (bs + (ts - bs + offsetScaleY) * scaleMix) / bs : 0;
-				bone.b *= s;
-				bone.d *= s;
-			}
+				if (translateMix > 0) {
+					float tempx, tempy;
+					target.LocalToWorld(data.offsetX, data.offsetY, out tempx, out tempy);
+					bone.worldX += (tempx - bone.worldX) * translateMix;
+					bone.worldY += (tempy - bone.worldY) * translateMix;
+				}
 
-			if (shearMix > 0) {
-				float b = bone.b, d = bone.d;
-				float by = MathUtils.Atan2(d, b);
-				float r = MathUtils.Atan2(target.d, target.b) - MathUtils.Atan2(target.c, target.a) - (by - MathUtils.Atan2(bone.c, bone.a));
-				if (r > MathUtils.PI)
-					r -= MathUtils.PI2;
-				else if (r < -MathUtils.PI) r += MathUtils.PI2;
-				r = by + (r + offsetShearY * MathUtils.degRad) * shearMix;
-				float s = (float)Math.Sqrt(b * b + d * d);
-				bone.b = MathUtils.Cos(r) * s;
-				bone.d = MathUtils.Sin(r) * s;
-			}
+				if (scaleMix > 0) {
+					float bs = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c);
+					float ts = (float)Math.Sqrt(ta * ta + tc * tc);
+					float s = bs > 0.00001f ? (bs + (ts - bs + data.offsetScaleX) * scaleMix) / bs : 0;
+					bone.a *= s;
+					bone.c *= s;
+					bs = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d);
+					ts = (float)Math.Sqrt(tb * tb + td * td);
+					s = bs > 0.00001f ? (bs + (ts - bs + data.offsetScaleY) * scaleMix) / bs : 0;
+					bone.b *= s;
+					bone.d *= s;
+				}
 
-			float translateMix = this.translateMix;
-			if (translateMix > 0) {
-				float tx, ty;
-				target.LocalToWorld(offsetX, offsetY, out tx, out ty);
-				bone.worldX += (tx - bone.worldX) * translateMix;
-				bone.worldY += (ty - bone.worldY) * translateMix;
+				if (shearMix > 0) {
+					float b = bone.b, d = bone.d;
+					float by = MathUtils.Atan2(d, b);
+					float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta) - (by - MathUtils.Atan2(bone.c, bone.a));
+					if (r > MathUtils.PI)
+						r -= MathUtils.PI2;
+					else if (r < -MathUtils.PI) r += MathUtils.PI2;
+					r = by + (r + data.offsetShearY * MathUtils.degRad) * shearMix;
+					float s = (float)Math.Sqrt(b * b + d * d);
+					bone.b = MathUtils.Cos(r) * s;
+					bone.d = MathUtils.Sin(r) * s;
+				}
 			}
 		}
 

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

@@ -30,17 +30,17 @@
  *****************************************************************************/
 
 using System;
-using System.Collections.Generic;
 
 namespace Spine {
 	public class TransformConstraintData {
 		internal String name;
-		internal BoneData bone, target;
+		internal ExposedList<BoneData> bones = new ExposedList<BoneData>();
+		internal BoneData target;
 		internal float rotateMix, translateMix, scaleMix, shearMix;
 		internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY;
 
 		public String Name { get { return name; } }
-		public BoneData Bone { get { return bone; } set { bone = value; } }
+		public ExposedList<BoneData> Bones { get { return bones; } }
 		public BoneData Target { get { return target; } set { target = value; } }
 		public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
 		public float TranslateMix { get { return translateMix; } set { translateMix = value; } }
@@ -55,7 +55,7 @@ namespace Spine {
 		public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } }
 
 		public TransformConstraintData (String name) {
-			if (name == null) throw new ArgumentNullException("name cannot be null.");
+			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
 			this.name = name;
 		}
 

+ 4 - 4
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java

@@ -167,7 +167,7 @@ public class SkeletonJson {
 
 			String targetName = constraintMap.getString("target");
 			data.target = skeletonData.findBone(targetName);
-			if (data.target == null) throw new SerializationException("Target bone not found: " + targetName);
+			if (data.target == null) throw new SerializationException("IK target bone not found: " + targetName);
 
 			data.bendDirection = constraintMap.getBoolean("bendPositive", true) ? 1 : -1;
 			data.mix = constraintMap.getFloat("mix", 1);
@@ -182,13 +182,13 @@ public class SkeletonJson {
 			for (JsonValue boneMap = constraintMap.getChild("bones"); boneMap != null; boneMap = boneMap.next) {
 				String boneName = boneMap.asString();
 				BoneData bone = skeletonData.findBone(boneName);
-				if (bone == null) throw new SerializationException("Path bone not found: " + boneName);
+				if (bone == null) throw new SerializationException("Transform constraint bone not found: " + boneName);
 				data.bones.add(bone);
 			}
 
 			String targetName = constraintMap.getString("target");
 			data.target = skeletonData.findBone(targetName);
-			if (data.target == null) throw new SerializationException("Target bone not found: " + targetName);
+			if (data.target == null) throw new SerializationException("Transform constraint target bone not found: " + targetName);
 
 			data.offsetRotation = constraintMap.getFloat("rotation", 0);
 			data.offsetX = constraintMap.getFloat("x", 0) * scale;
@@ -218,7 +218,7 @@ public class SkeletonJson {
 
 			String targetName = constraintMap.getString("target");
 			data.target = skeletonData.findSlot(targetName);
-			if (data.target == null) throw new SerializationException("Target slot not found: " + targetName);
+			if (data.target == null) throw new SerializationException("Path target slot not found: " + targetName);
 
 			data.positionMode = PositionMode.valueOf(constraintMap.getString("positionMode", "percent"));
 			data.spacingMode = SpacingMode.valueOf(constraintMap.getString("spacingMode", "length"));

+ 1 - 1
spine-monogame/README.md

@@ -10,7 +10,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f
 
 ## Spine version
 
-spine-monogame works with data exported from Spine 3.2.01. Updating spine-monogame to [v3.3](https://github.com/EsotericSoftware/spine-runtimes/issues/613) is in progress.
+spine-monogame works with data exported from Spine 3.3.07.
 
 spine-monogame supports all Spine features.
 

BIN
spine-unity/Assets/Examples/Getting Started/1 The Spine GameObject.unity


BIN
spine-unity/Assets/Examples/Getting Started/2 Controlling Animation.unity


BIN
spine-unity/Assets/Examples/Getting Started/3 Controlling Animation Continued.unity


BIN
spine-unity/Assets/Examples/Getting Started/4 Object Oriented Sample.unity


BIN
spine-unity/Assets/Examples/Getting Started/5 Basic Platformer.unity


BIN
spine-unity/Assets/Examples/Getting Started/6 SkeletonGraphic.unity


+ 20 - 2
spine-unity/Assets/Examples/Getting Started/Scripts/SpineboyBeginnerModel.cs

@@ -50,9 +50,27 @@ public class SpineboyBeginnerModel : MonoBehaviour {
 	IEnumerator JumpRoutine () {
 		if (state == SpineBeginnerBodyState.Jumping) yield break;	// Don't jump when already jumping.
 
-		// Fake jumping.
 		state = SpineBeginnerBodyState.Jumping;
-		yield return new WaitForSeconds(1.2f); 
+
+		// Terribly-coded Fake jumping.
+		{
+			var pos = transform.localPosition;
+			const float jumpTime = 1.2f;
+			const float half = jumpTime * 0.5f;
+			const float jumpPower = 20f;
+			for (float t = 0; t < half; t += Time.deltaTime) {
+				float d = jumpPower * (half - t);
+				transform.Translate((d * Time.deltaTime) * Vector3.up);
+				yield return null;
+			}
+			for (float t = 0; t < half; t += Time.deltaTime) {
+				float d = jumpPower * t;
+				transform.Translate((d * Time.deltaTime) * Vector3.down);
+				yield return null;
+			}
+			transform.localPosition = pos;
+		}
+
 		state = SpineBeginnerBodyState.Idle;
 	}
 

BIN
spine-unity/Assets/Examples/Other Examples/AtlasRegionAttacher.unity


BIN
spine-unity/Assets/Examples/Other Examples/Dragon.unity


BIN
spine-unity/Assets/Examples/Other Examples/Goblins.unity


BIN
spine-unity/Assets/Examples/Other Examples/Mix and Match.unity


BIN
spine-unity/Assets/Examples/Other Examples/SkeletonRenderSeparator.unity


BIN
spine-unity/Assets/Examples/Other Examples/SkeletonUtility Animated Physics.unity


BIN
spine-unity/Assets/Examples/Other Examples/SkeletonUtility Eyes.unity


BIN
spine-unity/Assets/Examples/Other Examples/SkeletonUtility GroundConstraint.unity


BIN
spine-unity/Assets/Examples/Other Examples/SkeletonUtility Ragdoll.unity


BIN
spine-unity/Assets/Examples/Other Examples/SpineGauge.unity


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
spine-unity/Assets/Examples/Spine/Dragon/dragon.json


+ 1 - 1
spine-unity/Assets/Examples/Spine/Dragon/dragon.png.meta

@@ -1,6 +1,6 @@
 fileFormatVersion: 2
 guid: 6bc52290ef03f2846ba38d67e2823598
-timeCreated: 1455501336
+timeCreated: 1467205225
 licenseType: Free
 TextureImporter:
   fileIDToRecycleName: {}

+ 1 - 1
spine-unity/Assets/Examples/Spine/Dragon/dragon2.png.meta

@@ -1,6 +1,6 @@
 fileFormatVersion: 2
 guid: 12c126994123f12468cf4c5a2684078a
-timeCreated: 1455501336
+timeCreated: 1467205225
 licenseType: Free
 TextureImporter:
   fileIDToRecycleName: {}

BIN
spine-unity/Assets/Examples/Spine/Dragon/dragon_Atlas.asset


BIN
spine-unity/Assets/Examples/Spine/Dragon/dragon_SkeletonData.asset


BIN
spine-unity/Assets/Examples/Spine/Dragon/dragon_dragon.mat


BIN
spine-unity/Assets/Examples/Spine/Dragon/dragon_dragon2.mat


+ 36 - 1
spine-unity/Assets/Examples/Spine/Eyes/eyes.json

@@ -1 +1,36 @@
-{"skeleton":{"hash":"LxkXoqW3SPQ5weFYd4V71R86WUk","spine":"2.0.20","width":868,"height":322},"bones":[{"name":"root"},{"name":"L_Eye","parent":"root","x":-223.18,"y":2.99},{"name":"R_Eye","parent":"root","x":237.32,"y":6.4}],"slots":[{"name":"EyeWhite","bone":"root","attachment":"EyeWhite"},{"name":"R_Eye","bone":"R_Eye","attachment":"R_Eye"},{"name":"L_Eye","bone":"L_Eye","attachment":"L_Eye"},{"name":"EyeLines","bone":"root","attachment":"EyeLines"}],"skins":{"default":{"EyeLines":{"EyeLines":{"y":28,"width":868,"height":322}},"EyeWhite":{"EyeWhite":{"x":3,"y":2,"width":700,"height":148}},"L_Eye":{"L_Eye":{"x":-0.82,"y":2,"width":148,"height":148}},"R_Eye":{"R_Eye":{"x":0.67,"y":-1.4,"width":148,"height":148}}}}}
+{
+"skeleton": {
+	"hash": "4JK7uGWbzO7qeQSyyuOyKLXQ5oI",
+	"spine": "3.3.07",
+	"width": 868,
+	"height": 322,
+	"images": "C:\\Users\\John Eric\\Desktop\\old exports"
+},
+"bones": [
+	{ "name": "root" },
+	{ "name": "L_Eye", "parent": "root", "x": -223.18, "y": 2.99 },
+	{ "name": "R_Eye", "parent": "root", "x": 237.32, "y": 6.4 }
+],
+"slots": [
+	{ "name": "EyeWhite", "bone": "root", "attachment": "EyeWhite" },
+	{ "name": "R_Eye", "bone": "R_Eye", "attachment": "R_Eye" },
+	{ "name": "L_Eye", "bone": "L_Eye", "attachment": "L_Eye" },
+	{ "name": "EyeLines", "bone": "root", "attachment": "EyeLines" }
+],
+"skins": {
+	"default": {
+		"EyeLines": {
+			"EyeLines": { "y": 28, "width": 868, "height": 322 }
+		},
+		"EyeWhite": {
+			"EyeWhite": { "x": 3, "y": 2, "width": 700, "height": 148 }
+		},
+		"L_Eye": {
+			"L_Eye": { "x": -0.82, "y": 2, "width": 148, "height": 148 }
+		},
+		"R_Eye": {
+			"R_Eye": { "x": 0.67, "y": -1.4, "width": 148, "height": 148 }
+		}
+	}
+}
+}

+ 1 - 1
spine-unity/Assets/Examples/Spine/Eyes/eyes.png.meta

@@ -1,6 +1,6 @@
 fileFormatVersion: 2
 guid: 49441e5a1682e564694545bd9b509785
-timeCreated: 1455501336
+timeCreated: 1467205225
 licenseType: Free
 TextureImporter:
   fileIDToRecycleName: {}

BIN
spine-unity/Assets/Examples/Spine/Eyes/eyes_Atlas.asset


BIN
spine-unity/Assets/Examples/Spine/Eyes/eyes_Material.mat


BIN
spine-unity/Assets/Examples/Spine/Eyes/eyes_SkeletonData.asset


+ 1 - 1
spine-unity/Assets/Examples/Spine/FootSoldier/Equipment/Equipment.png.meta

@@ -1,6 +1,6 @@
 fileFormatVersion: 2
 guid: ddb89f63d0296cf4f8572b0448bb6b30
-timeCreated: 1455501337
+timeCreated: 1467205225
 licenseType: Free
 TextureImporter:
   fileIDToRecycleName: {}

BIN
spine-unity/Assets/Examples/Spine/FootSoldier/Equipment/Equipment_Atlas.asset


BIN
spine-unity/Assets/Examples/Spine/FootSoldier/Equipment/Equipment_Material.mat


+ 1 - 1
spine-unity/Assets/Examples/Spine/FootSoldier/FS_White.png.meta

@@ -1,6 +1,6 @@
 fileFormatVersion: 2
 guid: 57b57f94df266f94ea0981915a4472e1
-timeCreated: 1455501336
+timeCreated: 1467205225
 licenseType: Free
 TextureImporter:
   fileIDToRecycleName: {}

BIN
spine-unity/Assets/Examples/Spine/FootSoldier/FS_White_Atlas.asset


BIN
spine-unity/Assets/Examples/Spine/FootSoldier/FS_White_Material.mat


+ 21 - 21
spine-unity/Assets/Examples/Spine/FootSoldier/FootSoldier.json

@@ -1,18 +1,18 @@
 {
-"skeleton": { "hash": "uZfvh80BNvvngM3EGMfAkHebg00", "spine": "2.1.08", "width": 147.68, "height": 268.92, "images": "./images/" },
+"skeleton": { "hash": "Nxia9637znam+9FXwv6fILb3hpo", "spine": "3.3.07", "width": 0, "height": 0, "images": "./images/" },
 "bones": [
 	{ "name": "Root" },
 	{ "name": "Hip", "parent": "Root", "x": -0.93, "y": 73.4 },
-	{ "name": "Body", "parent": "Hip", "length": 60.98, "x": 2.46, "y": -7.69, "rotation": 89.52 },
-	{ "name": "Leg", "parent": "Hip", "length": 31.39, "x": -20.32, "y": -13.85, "rotation": -105.82 },
-	{ "name": "Leg2", "parent": "Hip", "length": 31.09, "x": 22.48, "y": -12.01, "rotation": -74.17 },
-	{ "name": "Arm", "parent": "Body", "length": 51.63, "x": 49.91, "y": 37.35, "rotation": 166.67 },
-	{ "name": "Arm2", "parent": "Body", "length": 52.61, "x": 53.81, "y": -28.52, "rotation": -157.17 },
-	{ "name": "Feet", "parent": "Leg", "length": 15.4, "x": 39.56, "y": 1.59, "rotation": 14.56 },
-	{ "name": "Feet2", "parent": "Leg2", "length": 12.32, "x": 41.33, "y": 0.12, "rotation": -17.19 },
-	{ "name": "Head", "parent": "Body", "length": 65.29, "x": 73.6, "y": 1.09, "rotation": -88.23 },
-	{ "name": "Shield", "parent": "Arm", "x": 45.01, "y": -2.1, "rotation": 123.56 },
-	{ "name": "Weapon", "parent": "Arm2", "length": 137.65, "x": 48.2, "y": 12.78, "rotation": 92.5 }
+	{ "name": "Body", "parent": "Hip", "length": 60.98, "rotation": 89.52, "x": 2.46, "y": -7.69 },
+	{ "name": "Arm", "parent": "Body", "length": 51.63, "rotation": 166.67, "x": 49.91, "y": 37.34 },
+	{ "name": "Arm2", "parent": "Body", "length": 52.61, "rotation": -157.17, "x": 53.81, "y": -28.52 },
+	{ "name": "Leg", "parent": "Hip", "length": 31.39, "rotation": -105.82, "x": -20.32, "y": -13.85 },
+	{ "name": "Feet", "parent": "Leg", "length": 15.4, "rotation": 14.56, "x": 39.56, "y": 1.59 },
+	{ "name": "Leg2", "parent": "Hip", "length": 31.09, "rotation": -74.17, "x": 22.48, "y": -12.01 },
+	{ "name": "Feet2", "parent": "Leg2", "length": 12.32, "rotation": -17.19, "x": 41.33, "y": 0.12 },
+	{ "name": "Head", "parent": "Body", "length": 65.29, "rotation": -88.23, "x": 73.6, "y": 1.09 },
+	{ "name": "Shield", "parent": "Arm", "rotation": 123.56, "x": 45.01, "y": -2.09 },
+	{ "name": "Weapon", "parent": "Arm2", "length": 137.64, "rotation": 92.5, "x": 48.2, "y": 12.78 }
 ],
 "slots": [
 	{ "name": "Arm2", "bone": "Arm2", "attachment": "Arm2" },
@@ -61,7 +61,7 @@
 			"Leg": { "name": "leg", "path": "White/leg", "x": 16.86, "y": -4.3, "rotation": 104.82, "width": 48, "height": 55 }
 		},
 		"Leg2": {
-			"Leg2": { "name": "leg 2", "path": "White/leg 2", "x": 16.44, "y": -2.11, "rotation": 74.17, "width": 50, "height": 58 }
+			"Leg2": { "name": "leg 2", "path": "White/leg 2", "x": 16.44, "y": -2.1, "rotation": 74.17, "width": 50, "height": 58 }
 		},
 		"Mouth": {
 			"Closed": { "path": "White/mouth", "x": 10.96, "y": 3.69, "rotation": -1.29, "width": 28, "height": 21 }
@@ -239,11 +239,11 @@
 						"curve": [ 0.25, 0, 0.75, 1 ]
 					},
 					{ "time": 0.3333, "x": 4.5, "y": -0.72 },
-					{ "time": 0.4666, "x": -1.04, "y": -2.35, "curve": "stepped" },
+					{ "time": 0.4666, "x": -1.04, "y": -2.34, "curve": "stepped" },
 					{
 						"time": 0.6,
 						"x": -1.04,
-						"y": -2.35,
+						"y": -2.34,
 						"curve": [ 0.25, 0, 0.75, 1 ]
 					},
 					{ "time": 0.8333, "x": 0, "y": 0 }
@@ -345,7 +345,7 @@
 				"translate": [
 					{ "time": 0, "x": 0, "y": 0 },
 					{ "time": 0.2, "x": -50.02, "y": -6.59 },
-					{ "time": 0.3333, "x": -83.36, "y": -37.67 }
+					{ "time": 0.3333, "x": -83.36, "y": -37.66 }
 				]
 			},
 			"Body": {
@@ -491,7 +491,7 @@
 						"y": 0,
 						"curve": [ 0.25, 0, 0.758, 0.67 ]
 					},
-					{ "time": 0.3333, "x": -4.47, "y": 3.29 }
+					{ "time": 0.3333, "x": -4.46, "y": 3.29 }
 				]
 			},
 			"Leg2": {
@@ -686,7 +686,7 @@
 					},
 					{
 						"time": 0.3666,
-						"angle": -2.34,
+						"angle": -2.33,
 						"curve": [ 0.25, 0, 0.75, 1 ]
 					},
 					{ "time": 0.8666, "angle": 0 },
@@ -1518,17 +1518,17 @@
 					{
 						"time": 0,
 						"x": -0.84,
-						"y": -4.45,
+						"y": -4.44,
 						"curve": [ 0.25, 0, 0.75, 1 ]
 					},
-					{ "time": 0.2, "x": -1.03, "y": -4.7, "curve": "stepped" },
+					{ "time": 0.2, "x": -1.03, "y": -4.69, "curve": "stepped" },
 					{
 						"time": 0.5333,
 						"x": -1.03,
-						"y": -4.7,
+						"y": -4.69,
 						"curve": [ 0.25, 0, 0.75, 1 ]
 					},
-					{ "time": 0.6666, "x": -0.84, "y": -4.45 }
+					{ "time": 0.6666, "x": -0.84, "y": -4.44 }
 				]
 			},
 			"Feet2": {

BIN
spine-unity/Assets/Examples/Spine/FootSoldier/FootSoldier_SkeletonData.asset


+ 1 - 1
spine-unity/Assets/Examples/Spine/Gauge/Gauge.json

@@ -1,5 +1,5 @@
 {
-"skeleton": { "hash": "IKLl/62j+Y1bsQ8rdHoVK9PDip8", "spine": "2.1.27", "width": 250, "height": 60, "images": "./images/" },
+"skeleton": { "hash": "C69Pg+RG3DYyHmt9bOPYVrESJBQ", "spine": "3.3.07", "width": 250, "height": 60, "images": "./images/" },
 "bones": [
 	{ "name": "root" },
 	{ "name": "Bar", "parent": "root", "x": -112.29 }

+ 1 - 1
spine-unity/Assets/Examples/Spine/Gauge/Gauge.png.meta

@@ -1,6 +1,6 @@
 fileFormatVersion: 2
 guid: a11301aad15ed6b4995485a02a81b132
-timeCreated: 1455501336
+timeCreated: 1467205225
 licenseType: Free
 TextureImporter:
   fileIDToRecycleName: {}

BIN
spine-unity/Assets/Examples/Spine/Gauge/Gauge_Atlas.asset


BIN
spine-unity/Assets/Examples/Spine/Gauge/Gauge_Material.mat


BIN
spine-unity/Assets/Examples/Spine/Gauge/Gauge_SkeletonData.asset


+ 0 - 4
spine-unity/Assets/Examples/Spine/Goblins/goblins-mesh.atlas.txt.meta

@@ -1,4 +0,0 @@
-fileFormatVersion: 2
-guid: 3586e5ccd2041c24eb20eb4764168abd
-TextScriptImporter:
-  userData: 

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
spine-unity/Assets/Examples/Spine/Goblins/goblins-mesh.json


+ 0 - 4
spine-unity/Assets/Examples/Spine/Goblins/goblins-mesh.json.meta

@@ -1,4 +0,0 @@
-fileFormatVersion: 2
-guid: f3a3248bc50115241ae81702fde448eb
-TextScriptImporter:
-  userData: 

BIN
spine-unity/Assets/Examples/Spine/Goblins/goblins-mesh.png


BIN
spine-unity/Assets/Examples/Spine/Goblins/goblins-mesh_Atlas.asset


+ 0 - 4
spine-unity/Assets/Examples/Spine/Goblins/goblins-mesh_Atlas.asset.meta

@@ -1,4 +0,0 @@
-fileFormatVersion: 2
-guid: df2c4ad0c6709fd4f9b1c19ab43878ae
-NativeFormatImporter:
-  userData: 

BIN
spine-unity/Assets/Examples/Spine/Goblins/goblins-mesh_Material.mat


+ 0 - 4
spine-unity/Assets/Examples/Spine/Goblins/goblins-mesh_Material.mat.meta

@@ -1,4 +0,0 @@
-fileFormatVersion: 2
-guid: 64a66fecd89237b478156e7cc4d2da4a
-NativeFormatImporter:
-  userData: 

BIN
spine-unity/Assets/Examples/Spine/Goblins/goblins-mesh_SkeletonData.asset


+ 0 - 4
spine-unity/Assets/Examples/Spine/Goblins/goblins-mesh_SkeletonData.asset.meta

@@ -1,4 +0,0 @@
-fileFormatVersion: 2
-guid: 066917a2cc5e8824b9b7e2944feee6f1
-NativeFormatImporter:
-  userData: 

+ 66 - 66
spine-unity/Assets/Examples/Spine/Goblins/goblins-mesh.atlas.txt → spine-unity/Assets/Examples/Spine/Goblins/goblins.atlas.txt

@@ -1,292 +1,292 @@
 
-goblins-mesh.png
-size: 1024,128
+goblins.png
+size: 795,142
 format: RGBA8888
 filter: Linear,Linear
 repeat: none
 dagger
-  rotate: true
-  xy: 372, 100
+  rotate: false
+  xy: 1, 33
   size: 26, 108
   orig: 26, 108
   offset: 0, 0
   index: -1
 goblin/eyes-closed
   rotate: false
-  xy: 2, 7
+  xy: 760, 129
   size: 34, 12
   orig: 34, 12
   offset: 0, 0
   index: -1
 goblin/head
-  rotate: false
-  xy: 107, 36
+  rotate: true
+  xy: 110, 38
   size: 103, 66
   orig: 103, 66
   offset: 0, 0
   index: -1
 goblin/left-arm
-  rotate: false
-  xy: 901, 56
+  rotate: true
+  xy: 659, 7
   size: 37, 35
   orig: 37, 35
   offset: 0, 0
   index: -1
 goblin/left-foot
   rotate: false
-  xy: 929, 95
+  xy: 1, 1
   size: 65, 31
   orig: 65, 31
   offset: 0, 0
   index: -1
 goblin/left-hand
   rotate: false
-  xy: 452, 2
+  xy: 347, 3
   size: 36, 41
   orig: 36, 41
   offset: 0, 0
   index: -1
 goblin/left-lower-leg
-  rotate: true
-  xy: 713, 93
+  rotate: false
+  xy: 420, 71
   size: 33, 70
   orig: 33, 70
   offset: 0, 0
   index: -1
 goblin/left-shoulder
-  rotate: false
-  xy: 610, 44
+  rotate: true
+  xy: 684, 48
   size: 29, 44
   orig: 29, 44
   offset: 0, 0
   index: -1
 goblin/left-upper-leg
-  rotate: true
-  xy: 638, 93
+  rotate: false
+  xy: 315, 68
   size: 33, 73
   orig: 33, 73
   offset: 0, 0
   index: -1
 goblin/neck
   rotate: false
-  xy: 490, 2
+  xy: 384, 3
   size: 36, 41
   orig: 36, 41
   offset: 0, 0
   index: -1
 goblin/pelvis
   rotate: false
-  xy: 482, 45
+  xy: 221, 1
   size: 62, 43
   orig: 62, 43
   offset: 0, 0
   index: -1
 goblin/right-arm
-  rotate: true
-  xy: 690, 2
+  rotate: false
+  xy: 732, 91
   size: 23, 50
   orig: 23, 50
   offset: 0, 0
   index: -1
 goblin/right-foot
-  rotate: false
-  xy: 771, 58
+  rotate: true
+  xy: 624, 78
   size: 63, 33
   orig: 63, 33
   offset: 0, 0
   index: -1
 goblin/right-hand
   rotate: false
-  xy: 940, 56
+  xy: 585, 7
   size: 36, 37
   orig: 36, 37
   offset: 0, 0
   index: -1
 goblin/right-lower-leg
   rotate: true
-  xy: 482, 90
+  xy: 67, 1
   size: 36, 76
   orig: 36, 76
   offset: 0, 0
   index: -1
 goblin/right-shoulder
   rotate: true
-  xy: 602, 3
+  xy: 493, 5
   size: 39, 45
   orig: 39, 45
   offset: 0, 0
   index: -1
 goblin/right-upper-leg
-  rotate: true
-  xy: 641, 57
+  rotate: false
+  xy: 554, 78
   size: 34, 63
   orig: 34, 63
   offset: 0, 0
   index: -1
 goblin/torso
-  rotate: true
-  xy: 212, 34
+  rotate: false
+  xy: 177, 45
   size: 68, 96
   orig: 68, 96
   offset: 0, 0
   index: -1
 goblin/undie-straps
-  rotate: false
-  xy: 380, 5
+  rotate: true
+  xy: 692, 86
   size: 55, 19
   orig: 55, 19
   offset: 0, 0
   index: -1
 goblin/undies
   rotate: false
-  xy: 174, 5
+  xy: 756, 99
   size: 36, 29
   orig: 36, 29
   offset: 0, 0
   index: -1
 goblingirl/eyes-closed
-  rotate: false
-  xy: 269, 11
+  rotate: true
+  xy: 729, 48
   size: 37, 21
   orig: 37, 21
   offset: 0, 0
   index: -1
 goblingirl/head
-  rotate: false
-  xy: 2, 21
+  rotate: true
+  xy: 28, 38
   size: 103, 81
   orig: 103, 81
   offset: 0, 0
   index: -1
 goblingirl/left-arm
   rotate: true
-  xy: 978, 56
+  xy: 724, 10
   size: 37, 35
   orig: 37, 35
   offset: 0, 0
   index: -1
 goblingirl/left-foot
-  rotate: false
-  xy: 107, 3
+  rotate: true
+  xy: 522, 76
   size: 65, 31
   orig: 65, 31
   offset: 0, 0
   index: -1
 goblingirl/left-hand
   rotate: false
-  xy: 565, 2
+  xy: 457, 4
   size: 35, 40
   orig: 35, 40
   offset: 0, 0
   index: -1
 goblingirl/left-lower-leg
-  rotate: true
-  xy: 785, 93
+  rotate: false
+  xy: 454, 71
   size: 33, 70
   orig: 33, 70
   offset: 0, 0
   index: -1
 goblingirl/left-shoulder
-  rotate: true
-  xy: 690, 27
+  rotate: false
+  xy: 695, 1
   size: 28, 46
   orig: 28, 46
   offset: 0, 0
   index: -1
 goblingirl/left-upper-leg
-  rotate: true
-  xy: 857, 93
+  rotate: false
+  xy: 488, 71
   size: 33, 70
   orig: 33, 70
   offset: 0, 0
   index: -1
 goblingirl/neck
   rotate: false
-  xy: 528, 2
+  xy: 421, 3
   size: 35, 41
   orig: 35, 41
   offset: 0, 0
   index: -1
 goblingirl/pelvis
   rotate: false
-  xy: 546, 45
+  xy: 284, 1
   size: 62, 43
   orig: 62, 43
   offset: 0, 0
   index: -1
 goblingirl/right-arm
   rotate: false
-  xy: 452, 48
+  xy: 756, 48
   size: 28, 50
   orig: 28, 50
   offset: 0, 0
   index: -1
 goblingirl/right-foot
-  rotate: false
-  xy: 836, 58
+  rotate: true
+  xy: 658, 78
   size: 63, 33
   orig: 63, 33
   offset: 0, 0
   index: -1
 goblingirl/right-hand
-  rotate: true
-  xy: 771, 20
+  rotate: false
+  xy: 622, 7
   size: 36, 37
   orig: 36, 37
   offset: 0, 0
   index: -1
 goblingirl/right-lower-leg
   rotate: true
-  xy: 560, 90
+  xy: 144, 1
   size: 36, 76
   orig: 36, 76
   offset: 0, 0
   index: -1
 goblingirl/right-shoulder
-  rotate: false
-  xy: 649, 10
+  rotate: true
+  xy: 539, 5
   size: 39, 45
   orig: 39, 45
   offset: 0, 0
   index: -1
 goblingirl/right-upper-leg
-  rotate: true
-  xy: 706, 57
+  rotate: false
+  xy: 589, 78
   size: 34, 63
   orig: 34, 63
   offset: 0, 0
   index: -1
 goblingirl/torso
   rotate: false
-  xy: 310, 2
+  xy: 246, 45
   size: 68, 96
   orig: 68, 96
   offset: 0, 0
   index: -1
 goblingirl/undie-straps
-  rotate: false
-  xy: 212, 13
+  rotate: true
+  xy: 712, 86
   size: 55, 19
   orig: 55, 19
   offset: 0, 0
   index: -1
 goblingirl/undies
-  rotate: false
-  xy: 810, 27
+  rotate: true
+  xy: 760, 11
   size: 36, 29
   orig: 36, 29
   offset: 0, 0
   index: -1
 shield
   rotate: false
-  xy: 380, 26
+  xy: 349, 69
   size: 70, 72
   orig: 70, 72
   offset: 0, 0
   index: -1
 spear
   rotate: true
-  xy: 2, 104
+  xy: 315, 45
   size: 22, 368
   orig: 22, 368
   offset: 0, 0

+ 8 - 0
spine-unity/Assets/Examples/Spine/Goblins/goblins.atlas.txt.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 179f09b47e5402545a1aa69bf5cb2cba
+timeCreated: 1467115504
+licenseType: Free
+TextScriptImporter:
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 1060 - 0
spine-unity/Assets/Examples/Spine/Goblins/goblins.json

@@ -0,0 +1,1060 @@
+{
+"skeleton": {
+	"hash": "JkhrYH1FWGINlDaz60K/E43JLYM",
+	"spine": "3.3.07",
+	"width": 266.99,
+	"height": 349.62,
+	"images": "C:/Program Files (x86)/Spine/examples/goblins/images/"
+},
+"bones": [
+	{ "name": "root" },
+	{ "name": "hip", "parent": "root", "x": 0.64, "y": 114.41 },
+	{ "name": "torso", "parent": "hip", "length": 85.82, "rotation": 93.92, "x": -6.42, "y": 1.97 },
+	{ "name": "neck", "parent": "torso", "length": 18.38, "rotation": -1.51, "x": 81.67, "y": -6.34 },
+	{ "name": "head", "parent": "neck", "length": 68.28, "rotation": -13.92, "x": 20.93, "y": 11.59 },
+	{ "name": "left shoulder", "parent": "torso", "length": 35.43, "rotation": -156.96, "x": 74.04, "y": -20.38 },
+	{ "name": "left arm", "parent": "left shoulder", "length": 35.62, "rotation": 28.16, "x": 37.85, "y": -2.34 },
+	{ "name": "left upper leg", "parent": "hip", "length": 50.39, "rotation": -89.09, "x": 14.45, "y": 2.81 },
+	{ "name": "left lower leg", "parent": "left upper leg", "length": 49.89, "rotation": -16.65, "x": 56.34, "y": 0.98 },
+	{ "name": "left foot", "parent": "left lower leg", "length": 46.5, "rotation": 102.43, "x": 58.94, "y": -7.61 },
+	{ "name": "left hand", "parent": "left arm", "length": 11.52, "rotation": 2.7, "x": 35.62, "y": 0.07 },
+	{ "name": "pelvis", "parent": "hip", "x": 1.41, "y": -6.57 },
+	{ "name": "right shoulder", "parent": "torso", "length": 37.24, "rotation": 133.88, "x": 76.02, "y": 18.14 },
+	{ "name": "right arm", "parent": "right shoulder", "length": 36.74, "rotation": 36.32, "x": 37.6, "y": 0.31 },
+	{ "name": "right upper leg", "parent": "hip", "length": 42.45, "rotation": -97.49, "x": -20.07, "y": -6.83 },
+	{ "name": "right lower leg", "parent": "right upper leg", "length": 58.52, "rotation": -14.34, "x": 42.99, "y": -0.61 },
+	{ "name": "right foot", "parent": "right lower leg", "length": 45.45, "rotation": 110.3, "x": 64.88, "y": 0.04 },
+	{ "name": "right hand", "parent": "right arm", "length": 15.32, "rotation": 2.35, "x": 36.9, "y": 0.34 }
+],
+"slots": [
+	{ "name": "left shoulder", "bone": "left shoulder", "attachment": "left shoulder" },
+	{ "name": "left arm", "bone": "left arm", "attachment": "left arm" },
+	{ "name": "left hand item", "bone": "left hand", "attachment": "spear" },
+	{ "name": "left hand", "bone": "left hand", "attachment": "left hand" },
+	{ "name": "left foot", "bone": "left foot", "attachment": "left foot" },
+	{ "name": "left lower leg", "bone": "left lower leg", "attachment": "left lower leg" },
+	{ "name": "left upper leg", "bone": "left upper leg", "attachment": "left upper leg" },
+	{ "name": "neck", "bone": "neck", "attachment": "neck" },
+	{ "name": "torso", "bone": "torso", "attachment": "torso" },
+	{ "name": "pelvis", "bone": "pelvis", "attachment": "pelvis" },
+	{ "name": "right foot", "bone": "right foot", "attachment": "right foot" },
+	{ "name": "right lower leg", "bone": "right lower leg", "attachment": "right lower leg" },
+	{ "name": "undie straps", "bone": "pelvis", "attachment": "undie straps" },
+	{ "name": "undies", "bone": "pelvis", "attachment": "undies" },
+	{ "name": "right upper leg", "bone": "right upper leg", "attachment": "right upper leg" },
+	{ "name": "head", "bone": "head", "attachment": "head" },
+	{ "name": "eyes", "bone": "head" },
+	{ "name": "right shoulder", "bone": "right shoulder", "attachment": "right shoulder" },
+	{ "name": "right arm", "bone": "right arm", "attachment": "right arm" },
+	{ "name": "right hand thumb", "bone": "right hand", "attachment": "right hand thumb" },
+	{ "name": "right hand item", "bone": "right hand", "attachment": "dagger" },
+	{ "name": "right hand", "bone": "right hand", "attachment": "right hand" },
+	{ "name": "right hand item 2", "bone": "right hand", "attachment": "shield" }
+],
+"skins": {
+	"default": {
+		"left hand item": {
+			"dagger": { "x": 7.88, "y": -23.45, "rotation": 10.47, "width": 26, "height": 108 },
+			"spear": {
+				"type": "mesh",
+				"uvs": [ 1, 0.11236, 0.77096, 0.13278, 0.72117, 1, 0.28838, 1, 0.20043, 0.13242, 0, 0.11519, 0.4527, 0, 0.58399, 0 ],
+				"triangles": [ 4, 5, 6, 1, 7, 0, 1, 3, 4, 6, 7, 1, 1, 4, 6, 2, 3, 1 ],
+				"vertices": [ -26.02, 180.65, -29.24, 172.19, 41.7, -138.95, 32.41999, -141.1, -41.49, 169.49, -47.22, 174.67, -47.08, 218.22, -44.27, 218.87 ],
+				"hull": 8,
+				"edges": [ 12, 10, 10, 8, 8, 6, 4, 6, 4, 2, 2, 0, 12, 14, 0, 14 ],
+				"width": 22,
+				"height": 368
+			}
+		},
+		"right hand item": {
+			"dagger": {
+				"type": "mesh",
+				"uvs": [ 0.78091, 0.38453, 1, 0.38405, 1, 0.44881, 0.73953, 0.4687, 0.74641, 0.81344, 0.34022, 1, 0.15434, 1, 0.11303, 0.78858, 0.23007, 0.47367, 0, 0.45047, 0, 0.38621, 0.22367, 0.38573, 0.24384, 0, 1, 0 ],
+				"triangles": [ 0, 12, 13, 11, 12, 0, 0, 1, 2, 9, 10, 11, 3, 11, 0, 3, 0, 2, 8, 11, 3, 9, 11, 8, 5, 6, 7, 4, 5, 8, 4, 8, 3, 5, 7, 8 ],
+				"vertices": [ 15.49, -12.82, 21.13, -13.57, 20.16, -20.49, 13.15, -21.67, 8.13, -58.56, -5.13, -77.04, -9.92, -76.36, -7.79, -53.6, -0.03, -20.36, -5.6, -17.04, -4.63, -10.17, 1.12, -10.93, 7.46, 30.24, 26.93, 27.49 ],
+				"hull": 14,
+				"edges": [ 22, 20, 24, 26, 22, 24, 2, 0, 0, 22, 0, 26, 12, 14, 14, 16, 18, 20, 16, 18, 2, 4, 4, 6, 6, 8, 10, 12, 8, 10 ],
+				"width": 26,
+				"height": 108
+			}
+		},
+		"right hand item 2": {
+			"shield": { "rotation": 93.49, "width": 70, "height": 72 }
+		}
+	},
+	"goblin": {
+		"eyes": {
+			"eyes closed": { "name": "goblin/eyes-closed", "x": 29.19, "y": -24.89, "rotation": -88.92, "width": 34, "height": 12 }
+		},
+		"head": {
+			"head": {
+				"name": "goblin/head",
+				"type": "mesh",
+				"uvs": [ 0, 0.60494, 0.14172, 0.5145, 0.24218, 0.55229, 0.32667, 0.67806, 0.37969, 0.79352, 0.53505, 0.93014, 0.86056, 1, 0.94071, 0.94169, 0.92098, 0.69923, 0.9888, 0.65497, 0.99003, 0.51643, 0.89632, 0.43561, 0.94487, 0.41916, 1, 0.39713, 1, 0.2836, 0.94017, 0.27027, 0.87906, 0.25666, 0.80754, 0.16044, 0.66698, 0.01997, 0.4734, 0.01805, 0.29215, 0.19893, 0.25392, 0.31823, 0.09117, 0.324, 0, 0.44331, 0.43271, 0.69153, 0.466, 0.47794, 0.35996, 0.31246, 0.73473, 0.68593, 0.72215, 0.57425, 0.88179, 0.5583, 0.80267, 0.51015 ],
+				"triangles": [ 26, 20, 19, 21, 20, 26, 15, 14, 13, 12, 15, 13, 11, 16, 15, 11, 15, 12, 26, 17, 25, 18, 26, 19, 17, 26, 18, 30, 25, 17, 30, 17, 16, 30, 16, 11, 1, 22, 21, 23, 22, 1, 2, 1, 21, 2, 21, 26, 29, 30, 11, 29, 11, 10, 28, 25, 30, 0, 23, 1, 9, 29, 10, 25, 3, 2, 25, 2, 26, 29, 27, 28, 29, 28, 30, 24, 3, 25, 24, 25, 28, 24, 28, 27, 8, 29, 9, 27, 29, 8, 4, 3, 24, 5, 24, 27, 4, 24, 5, 7, 6, 27, 7, 27, 8, 5, 27, 6 ],
+				"vertices": [ 14.56, 50.42, 23.12, 35.47, 17.45999, 26.36, 11.57, 16.86, 3.74, 11.71, -5.89, -3.91, -11.83, -37.23, -8.31, -45.63, 7.75, -44.24, 10.39, -51.33, 19.52, -51.82, 25.21, -43.15, 26.12, -47.43, 27.35, -53.16, 34.84, -53.46, 35.96, -47.33, 37.11, -41.08, 43.75, -33.97, 53.58, -19.87, 54.5, 0.03, 43.31, 19.16, 35.59999, 23.41, 35.89, 40.16999, 28.39, 49.87, 10.25, 5.99, 24.2, 2, 35.55, 12.48, 9.39, -25.1, 16.79999, -24.31, 17.2, -40.65, 20.68, -33.02 ],
+				"hull": 24,
+				"edges": [ 0, 2, 6, 8, 8, 10, 10, 12, 12, 14, 14, 16, 16, 18, 18, 20, 20, 22, 26, 28, 32, 34, 34, 36, 36, 38, 38, 40, 40, 42, 42, 44, 44, 46, 0, 46, 6, 48, 48, 50, 50, 52, 52, 42, 2, 4, 4, 6, 4, 52, 2, 44, 22, 32, 22, 24, 24, 26, 28, 30, 30, 32, 24, 30, 16, 54, 54, 56, 20, 58, 58, 54, 16, 58, 22, 60, 60, 56, 58, 60 ],
+				"width": 103,
+				"height": 66
+			}
+		},
+		"left arm": {
+			"left arm": {
+				"name": "goblin/left-arm",
+				"type": "mesh",
+				"uvs": [ 0.68992, 0.29284, 1, 0.46364, 1, 0.74643, 0.84089, 1, 0.66344, 1, 0.33765, 0.64284, 0, 0.44124, 0, 0, 0.34295, 0 ],
+				"triangles": [ 6, 7, 8, 5, 6, 8, 0, 5, 8, 0, 1, 2, 5, 0, 2, 4, 5, 2, 3, 4, 2 ],
+				"vertices": [ 18.6, 8.81, 32.18999, 10.31, 38.02, 1.62, 38.08, -9.63, 32.31, -13.49, 14.37, -9.62, -0.75, -10.78, -9.84, 2.77, 1.29, 10.25 ],
+				"hull": 9,
+				"edges": [ 14, 16, 16, 0, 0, 2, 2, 4, 6, 4, 6, 8, 8, 10, 12, 14, 10, 12 ],
+				"width": 37,
+				"height": 35
+			}
+		},
+		"left foot": {
+			"left foot": {
+				"name": "goblin/left-foot",
+				"type": "mesh",
+				"uvs": [ 0.15733, 0.31873, 0.08195, 0.78502, 0.15884, 0.99366, 0.41633, 0.96804, 0.68822, 0.97636, 1, 0.96388, 0.99385, 0.73501, 0.85294, 0.51862, 0.61479, 0.31056, 0.46991, 0, 0.48032, 0.75604, 0.75994, 0.77706 ],
+				"triangles": [ 0, 9, 8, 10, 0, 8, 10, 8, 7, 11, 10, 7, 11, 7, 6, 1, 0, 10, 11, 6, 5, 3, 1, 10, 4, 10, 11, 4, 11, 5, 3, 10, 4, 2, 1, 3 ],
+				"vertices": [ 2.28, 13.07, -1.76, -1.64, 3.59, -7.8, 20.25, -6.04, 37.91, -5.27, 58.12, -3.71, 57.31, 3.34, 47.78, 9.51, 31.95, 15.05, 21.99, 24.11, 24.03, 0.75, 42.21, 1.16 ],
+				"hull": 10,
+				"edges": [ 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10, 12, 12, 14, 14, 16, 16, 18, 0, 18, 6, 20, 20, 16, 2, 20, 8, 22, 22, 14, 20, 22, 22, 10 ],
+				"width": 65,
+				"height": 31
+			}
+		},
+		"left hand": {
+			"left hand": {
+				"name": "goblin/left-hand",
+				"type": "mesh",
+				"uvs": [ 0.518, 0.12578, 1, 0.16285, 0.99788, 0.50578, 0.69745, 1, 0.37445, 1, 0, 0.80051, 0, 0.42792, 0.17601, 0, 0.43567, 0 ],
+				"triangles": [ 2, 0, 1, 0, 5, 6, 6, 7, 0, 0, 7, 8, 3, 4, 0, 4, 5, 0, 2, 3, 0 ],
+				"vertices": [ -3.11, 15.42, 10.83, 22.27, 15.5, 14.55, 18.35, -8.96, 9.47999, -14.32, -4.58, -14.3, -11.63, -2.63, -14.89, 13.68, -7.75, 17.99 ],
+				"hull": 9,
+				"edges": [ 16, 0, 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10, 12, 14, 16, 12, 14 ],
+				"width": 36,
+				"height": 41
+			}
+		},
+		"left lower leg": {
+			"left lower leg": {
+				"name": "goblin/left-lower-leg",
+				"type": "mesh",
+				"uvs": [ 0.95508, 0.20749, 0.81927, 0.65213, 0.94754, 0.77308, 0.67842, 0.97346, 0.46463, 1, 0.26845, 1, 0.04963, 0.90706, 0.2106, 0.60115, 0.07478, 0.40195, 0.18545, 0, 0.28857, 0 ],
+				"triangles": [ 10, 8, 9, 1, 7, 10, 7, 8, 10, 0, 1, 10, 1, 4, 7, 3, 1, 2, 5, 6, 7, 7, 4, 5, 1, 3, 4 ],
+				"vertices": [ -0.19, 6.82, 30.97, 10.96, 37.97, 17.33, 53.88, 12.6, 57.58, 6.31, 59.34, 0.08, 55.04, -8.63, 32.99, -9.33, 20.79, -17.43, -7.27, -21.56, -8.18999, -18.29 ],
+				"hull": 11,
+				"edges": [ 20, 0, 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10, 12, 12, 14, 14, 16, 18, 20, 16, 18 ],
+				"width": 33,
+				"height": 70
+			}
+		},
+		"left shoulder": {
+			"left shoulder": {
+				"name": "goblin/left-shoulder",
+				"type": "mesh",
+				"uvs": [ 0.7377, 0.40692, 1, 0.75237, 1, 1, 0.62046, 1, 0.26184, 0.56601, 0, 0.29783, 0, 0, 0.44115, 0 ],
+				"triangles": [ 5, 6, 7, 4, 5, 7, 4, 7, 0, 3, 4, 0, 3, 0, 1, 3, 1, 2 ],
+				"vertices": [ 15.18, 5.74, 32.16999, 5.32, 41.79, 0.21, 36.63, -9.5, 14.88, -9.72, 0.9, -10.89, -10.66, -4.73999, -4.66, 6.54 ],
+				"hull": 8,
+				"edges": [ 12, 14, 14, 0, 4, 2, 0, 2, 4, 6, 6, 8, 10, 12, 8, 10 ],
+				"width": 29,
+				"height": 44
+			}
+		},
+		"left upper leg": {
+			"left upper leg": {
+				"name": "goblin/left-upper-leg",
+				"type": "mesh",
+				"uvs": [ 1, 0.12167, 1, 0.54873, 0.91067, 0.78907, 0.76567, 1, 0.3087, 0.9579, 0, 0.68777, 0, 0.219, 0.51961, 0, 0.87552, 0 ],
+				"triangles": [ 7, 8, 0, 5, 6, 7, 0, 1, 7, 4, 5, 7, 1, 4, 7, 2, 4, 1, 3, 4, 2 ],
+				"vertices": [ 2.33, 13.06, 33.5, 12.57, 51, 9.34, 66.32, 4.31, 63, -10.71, 43.13, -20.58, 8.91, -20.04, -6.79, -2.64, -6.61, 9.1 ],
+				"hull": 9,
+				"edges": [ 10, 8, 8, 6, 6, 4, 4, 2, 10, 12, 12, 14, 14, 16, 2, 0, 16, 0 ],
+				"width": 33,
+				"height": 73
+			}
+		},
+		"neck": {
+			"neck": {
+				"name": "goblin/neck",
+				"type": "mesh",
+				"uvs": [ 0.81967, 0.27365, 0.92101, 0.82048, 0.47134, 1, 0.15679, 0.9354, 0, 0.7556, 0.19268, 0.51833, 0.15468, 0.35706, 0, 0.21989, 0.13568, 0, 0.68878, 0, 0.70145, 0.53872 ],
+				"triangles": [ 6, 8, 9, 6, 9, 0, 7, 8, 6, 10, 5, 6, 0, 10, 6, 10, 0, 1, 3, 4, 5, 2, 5, 10, 2, 10, 1, 3, 5, 2 ],
+				"vertices": [ 18.62, -11.65, -3.98, -13.85, -10.28, 2.76, -6.91, 13.89, 0.8, 19.04999, 10.06, 11.51, 16.74, 12.45, 22.71, 17.64, 31.4, 12.19, 30.12, -7.67, 8.05, -6.71 ],
+				"hull": 10,
+				"edges": [ 14, 12, 12, 10, 10, 8, 8, 6, 6, 4, 4, 2, 2, 20, 20, 0, 0, 18, 16, 18, 14, 16, 0, 2 ],
+				"width": 36,
+				"height": 41
+			}
+		},
+		"pelvis": {
+			"pelvis": {
+				"name": "goblin/pelvis",
+				"type": "mesh",
+				"uvs": [ 1, 1, 0, 1, 0, 0, 1, 0 ],
+				"triangles": [ 1, 2, 3, 1, 3, 0 ],
+				"vertices": [ 25.38, -20.73, -36.61, -20.73, -36.61, 22.26, 25.38, 22.26 ],
+				"hull": 4,
+				"edges": [ 0, 2, 2, 4, 4, 6, 0, 6 ],
+				"width": 62,
+				"height": 43
+			}
+		},
+		"right arm": {
+			"right arm": {
+				"name": "goblin/right-arm",
+				"type": "mesh",
+				"uvs": [ 1, 0.09223, 1, 0.8501, 0.72058, 1, 0.24384, 1, 0, 0.86558, 0.20822, 0.10919, 0.50903, 0, 0.85342, 0 ],
+				"triangles": [ 6, 7, 0, 2, 3, 5, 4, 5, 3, 1, 6, 0, 6, 2, 5, 1, 2, 6 ],
+				"vertices": [ -4.75, 8.89, 33.03, 11.74, 40.99, 5.89, 41.81, -5.03, 35.53, -11.13, -2.53, -9.2, -8.5, -2.71, -9.09, 5.17999 ],
+				"hull": 8,
+				"edges": [ 8, 6, 4, 6, 4, 2, 12, 14, 2, 0, 14, 0, 10, 12, 8, 10 ],
+				"width": 23,
+				"height": 50
+			}
+		},
+		"right foot": {
+			"right foot": {
+				"name": "goblin/right-foot",
+				"type": "mesh",
+				"uvs": [ 0.40851, 0.0047, 0.59087, 0.33404, 0.75959, 0.48311, 0.88907, 0.59751, 0.97532, 0.89391, 0.90385, 1, 0.6722, 1, 0.38633, 1, 0.08074, 1, 0, 0.88921, 0, 0.65984, 0, 0.46577, 0.0906, 0.0988, 0.305, 0, 0.47461, 0.71257, 0.715, 0.74681 ],
+				"triangles": [ 1, 10, 11, 1, 13, 0, 14, 1, 2, 1, 12, 13, 12, 1, 11, 14, 10, 1, 15, 14, 2, 15, 2, 3, 9, 10, 14, 15, 3, 4, 7, 8, 9, 14, 7, 9, 6, 14, 15, 5, 6, 15, 7, 14, 6, 4, 5, 15 ],
+				"vertices": [ 17.36, 25.99, 29.13, 15.44, 39.89, 10.8, 48.14, 7.24, 53.84, -2.38, 49.43, -6, 34.84, -6.39, 16.84, -6.87, -2.4, -7.38, -7.58, -3.86, -7.78, 3.7, -7.95, 10.1, -2.57, 22.36, 10.84, 25.97, 22.14, 2.75, 37.31, 2.03 ],
+				"hull": 14,
+				"edges": [ 0, 2, 6, 8, 8, 10, 16, 18, 22, 24, 24, 26, 0, 26, 10, 12, 2, 4, 4, 6, 12, 14, 14, 16, 18, 20, 20, 22, 2, 28, 28, 14, 20, 28, 4, 30, 30, 12, 28, 30, 30, 8 ],
+				"width": 63,
+				"height": 33
+			}
+		},
+		"right hand": {
+			"right hand": {
+				"name": "goblin/right-hand",
+				"type": "mesh",
+				"uvs": [ 0.17957, 0, 0, 0.44772, 0, 0.79734, 0.20057, 0.94264, 0.55057, 1, 0.8539, 1, 0.89823, 0.82004, 0.8259, 0.74285, 0.84223, 0.49993, 0.96356, 0.34102, 0.66023, 0 ],
+				"triangles": [ 8, 10, 9, 0, 10, 1, 8, 2, 1, 8, 1, 10, 7, 3, 8, 3, 2, 8, 4, 3, 7, 5, 7, 6, 4, 7, 5 ],
+				"vertices": [ -10.82, -9.45, 5.95, -15.34, 18.87999, -14.9, 24, -7.5, 25.69, 5.16, 25.31, 16.07, 18.61, 17.44, 15.84, 14.74, 6.84, 15.02, 0.81, 19.18, -11.41, 7.83 ],
+				"hull": 11,
+				"edges": [ 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10, 12, 12, 14, 14, 16, 16, 18, 18, 20, 0, 20 ],
+				"width": 36,
+				"height": 37
+			}
+		},
+		"right hand thumb": {
+			"right hand thumb": {
+				"name": "goblin/right-hand",
+				"type": "mesh",
+				"uvs": [ 0.88538, 0.22262, 0.76167, 0.3594, 0.75088, 0.78308, 0.95326, 0.84981, 1, 0.60302 ],
+				"triangles": [ 1, 0, 4, 2, 1, 4, 3, 2, 4 ],
+				"vertices": [ -2.82, 15.97, 2.4, 11.71, 18.08, 11.9, 20.27, 19.27, 11.09, 20.62 ],
+				"hull": 5,
+				"edges": [ 2, 4, 4, 6, 6, 8, 2, 0, 0, 8 ],
+				"width": 36,
+				"height": 37
+			}
+		},
+		"right lower leg": {
+			"right lower leg": {
+				"name": "goblin/right-lower-leg",
+				"type": "mesh",
+				"uvs": [ 1, 0.27261, 0.81312, 0.52592, 0.79587, 0.71795, 0.95544, 0.80988, 0.85193, 0.95493, 0.47241, 1, 0.14033, 1, 0, 0.8773, 0.14896, 0.67914, 0.1619, 0.30325, 0.60611, 0 ],
+				"triangles": [ 1, 10, 0, 9, 10, 1, 8, 9, 1, 2, 8, 1, 4, 2, 3, 6, 7, 8, 5, 6, 8, 2, 5, 8, 4, 5, 2 ],
+				"vertices": [ 6.26, 8.46, 23.32, 8.04, 37.09999, 12.89, 41.45, 20.82, 53.07, 21.46, 61.33, 10.06, 65.76999, -1.03, 58.99, -9.18999, 43.02, -9.81, 16.33, -20, -12.79, -9.26 ],
+				"hull": 11,
+				"edges": [ 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10, 12, 12, 14, 14, 16, 16, 18, 0, 20, 18, 20 ],
+				"width": 36,
+				"height": 76
+			}
+		},
+		"right shoulder": {
+			"right shoulder": {
+				"name": "goblin/right-shoulder",
+				"type": "mesh",
+				"uvs": [ 0.62008, 0.03708, 0.92131, 0.09048, 1, 0.38319, 0.72049, 0.6937, 0.31656, 1, 0, 1, 0, 0.75106, 0.28233, 0.49988 ],
+				"triangles": [ 2, 3, 0, 2, 0, 1, 7, 0, 3, 4, 5, 6, 4, 7, 3, 4, 6, 7 ],
+				"vertices": [ -3.17, -11.05, -9, -0.57, -1.01, 10.33, 16.69, 11.17, 37.41, 8.2, 45.45, -1.16, 36.95, -8.46, 21.2, -7.47 ],
+				"hull": 8,
+				"edges": [ 10, 12, 12, 14, 14, 0, 0, 2, 2, 4, 4, 6, 8, 10, 6, 8 ],
+				"width": 39,
+				"height": 45
+			}
+		},
+		"right upper leg": {
+			"right upper leg": {
+				"name": "goblin/right-upper-leg",
+				"type": "mesh",
+				"uvs": [ 0.27018, 0, 0.11618, 0.18177, 0, 0.70688, 0, 0.89577, 0.26668, 1, 0.48718, 1, 0.67618, 0.83532, 1, 0.5161, 1, 0.25543, 0.74618, 0.0571 ],
+				"triangles": [ 9, 8, 7, 9, 1, 0, 6, 9, 7, 6, 1, 9, 2, 1, 6, 4, 3, 2, 6, 4, 2, 5, 4, 6 ],
+				"vertices": [ -9.85, -10.37, 2.17, -14.07, 35.49, -13.66, 47.29, -12.11, 52.61, -2.26, 51.63, 5.16, 40.50999, 10.18, 19.12999, 18.46999, 2.85, 16.32, -8.39999, 6.14 ],
+				"hull": 10,
+				"edges": [ 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10, 12, 12, 14, 14, 16, 16, 18, 0, 18 ],
+				"width": 34,
+				"height": 63
+			}
+		},
+		"torso": {
+			"torso": {
+				"name": "goblin/torso",
+				"type": "mesh",
+				"uvs": [ 0, 0.33287, 0.15945, 0.46488, 0.15761, 0.60314, 0.15502, 0.79806, 0.32807, 0.93478, 0.6875, 1, 0.80731, 1, 1, 0.77763, 1, 0.66147, 1, 0.56703, 0.93207, 0.4771, 0.86944, 0.39416, 0.83837, 0.226, 0.68085, 0, 0.14836, 0, 0, 0.07199, 0.78734, 0.86249, 0.43679, 0.79649, 0.76738, 0.61733, 0.44345, 0.58747, 0.54329, 0.38316, 0.77692, 0.73446, 0.66478, 0.51012 ],
+				"triangles": [ 0, 15, 14, 20, 14, 13, 20, 13, 12, 1, 0, 14, 20, 12, 11, 20, 1, 14, 22, 20, 11, 22, 11, 10, 19, 1, 20, 19, 20, 22, 2, 1, 19, 18, 22, 10, 18, 10, 9, 19, 22, 18, 18, 9, 8, 21, 18, 8, 21, 8, 7, 17, 2, 19, 21, 17, 19, 21, 19, 18, 3, 2, 17, 16, 21, 7, 17, 21, 16, 4, 3, 17, 5, 17, 16, 4, 17, 5, 6, 16, 7, 5, 16, 6 ],
+				"vertices": [ 56.93, 27.95, 43.37, 18.23, 30.16, 19.5, 11.53, 21.28, -2.55, 10.69, -10.89, -13.12, -11.59, -21.23, 8.54, -36.12, 19.65, -37.08, 28.68, -37.86, 37.68, -34, 45.98, -30.44, 56.4, -29.07, 84.78, -20.92, 87.9, 15.15, 81.87999, 25.79, 1.67, -21.01, 10.03, 2.18, 25.23, -21.69, 29.98, 0, 48.54, -8.39, 13.98, -21.36, 35.9, -15.6 ],
+				"hull": 16,
+				"edges": [ 0, 2, 6, 8, 8, 10, 10, 12, 12, 14, 22, 24, 24, 26, 26, 28, 28, 30, 0, 30, 14, 32, 32, 34, 34, 6, 18, 36, 36, 38, 2, 4, 4, 6, 38, 4, 2, 40, 40, 22, 40, 38, 38, 34, 32, 10, 34, 8, 40, 28, 14, 16, 16, 18, 32, 42, 42, 36, 16, 42, 42, 34, 18, 20, 20, 22, 36, 44, 44, 40, 20, 44 ],
+				"width": 68,
+				"height": 96
+			}
+		},
+		"undie straps": {
+			"undie straps": {
+				"name": "goblin/undie-straps",
+				"type": "mesh",
+				"uvs": [ 0.36097, 0.44959, 0.66297, 0.60591, 1, 0.19486, 1, 0.57117, 0.75897, 1, 0.38697, 1, 0, 0.26433, 0, 0, 0.12497, 0 ],
+				"triangles": [ 6, 7, 8, 6, 8, 0, 3, 1, 2, 5, 0, 1, 6, 0, 5, 4, 1, 3, 5, 1, 4 ],
+				"vertices": [ -10.56, 12.87, 6.53, 9.89999, 25.62, 17.70999, 25.62, 10.56, 11.97, 2.41, -9.09, 2.41, -31, 16.39, -31, 21.41, -23.92, 21.41 ],
+				"hull": 9,
+				"edges": [ 14, 16, 16, 0, 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 12, 14, 10, 12, 0, 10, 2, 8 ],
+				"width": 55,
+				"height": 19
+			}
+		},
+		"undies": {
+			"undies": {
+				"name": "goblin/undies",
+				"type": "mesh",
+				"uvs": [ 0, 0.32029, 0.14893, 0.59457, 0.22437, 1, 0.35909, 1, 0.50998, 1, 0.79559, 0.58453, 0.9842, 0.28015, 1, 0.00588, 0.46957, 0.17646, 0, 0.03933, 0.48843, 0.59122, 0.48114, 0.43099 ],
+				"triangles": [ 6, 8, 7, 0, 9, 8, 11, 8, 6, 0, 8, 11, 5, 11, 6, 10, 11, 5, 1, 0, 11, 1, 11, 10, 3, 2, 1, 10, 3, 1, 4, 10, 5, 3, 10, 4 ],
+				"vertices": [ -13.22, 5.56, -8, -2.47, -5.49, -14.27, -0.64, -14.36, 4.78, -14.45, 15.27, -2.58999, 22.22, 6.11, 22.92, 14.05, 3.75, 9.43999, -13.08, 13.71, 4.21, -2.58999, 4.03, 2.05 ],
+				"hull": 10,
+				"edges": [ 0, 2, 2, 4, 8, 10, 10, 12, 12, 14, 14, 16, 16, 18, 0, 18, 4, 6, 6, 8, 6, 20, 16, 22, 22, 20, 0, 22, 22, 12, 2, 20, 20, 10 ],
+				"width": 36,
+				"height": 29
+			}
+		}
+	},
+	"goblingirl": {
+		"eyes": {
+			"eyes closed": { "name": "goblingirl/eyes-closed", "x": 28, "y": -25.54, "rotation": -87.04, "width": 37, "height": 21 }
+		},
+		"head": {
+			"head": { "name": "goblingirl/head", "x": 27.71, "y": -4.32, "rotation": -85.58, "width": 103, "height": 81 }
+		},
+		"left arm": {
+			"left arm": { "name": "goblingirl/left-arm", "x": 19.64, "y": -2.42, "rotation": 33.05, "width": 37, "height": 35 }
+		},
+		"left foot": {
+			"left foot": { "name": "goblingirl/left-foot", "type": "linkedmesh", "skin": "goblin", "parent": "left foot", "width": 65, "height": 31 }
+		},
+		"left hand": {
+			"left hand": {
+				"name": "goblingirl/left-hand",
+				"x": 4.34,
+				"y": 2.39,
+				"scaleX": 0.896,
+				"scaleY": 0.896,
+				"rotation": 30.34,
+				"width": 35,
+				"height": 40
+			}
+		},
+		"left lower leg": {
+			"left lower leg": { "name": "goblingirl/left-lower-leg", "x": 25.02, "y": -0.6, "rotation": 105.75, "width": 33, "height": 70 }
+		},
+		"left shoulder": {
+			"left shoulder": { "name": "goblingirl/left-shoulder", "x": 19.8, "y": -0.42, "rotation": 61.21, "width": 28, "height": 46 }
+		},
+		"left upper leg": {
+			"left upper leg": { "name": "goblingirl/left-upper-leg", "x": 30.21, "y": -2.95, "rotation": 89.09, "width": 33, "height": 70 }
+		},
+		"neck": {
+			"neck": { "name": "goblingirl/neck", "x": 6.16, "y": -3.14, "rotation": -98.86, "width": 35, "height": 41 }
+		},
+		"pelvis": {
+			"pelvis": { "name": "goblingirl/pelvis", "type": "linkedmesh", "skin": "goblin", "parent": "pelvis", "width": 62, "height": 43 }
+		},
+		"right arm": {
+			"right arm": { "name": "goblingirl/right-arm", "x": 16.85, "y": -0.66, "rotation": 93.52, "width": 28, "height": 50 }
+		},
+		"right foot": {
+			"right foot": { "name": "goblingirl/right-foot", "type": "linkedmesh", "skin": "goblin", "parent": "right foot", "width": 63, "height": 33 }
+		},
+		"right hand": {
+			"right hand": { "name": "goblingirl/right-hand", "x": 7.21, "y": 3.43, "rotation": 91.16, "width": 36, "height": 37 }
+		},
+		"right hand thumb": {
+			"right hand thumb": { "name": "goblingirl/right-hand", "x": 7.21, "y": 3.43, "rotation": 91.16, "width": 36, "height": 37 }
+		},
+		"right lower leg": {
+			"right lower leg": { "name": "goblingirl/right-lower-leg", "x": 26.15, "y": -3.27, "rotation": 111.83, "width": 36, "height": 76 }
+		},
+		"right shoulder": {
+			"right shoulder": { "name": "goblingirl/right-shoulder", "x": 14.46, "y": 0.45, "rotation": 129.85, "width": 39, "height": 45 }
+		},
+		"right upper leg": {
+			"right upper leg": {
+				"name": "goblingirl/right-upper-leg",
+				"type": "linkedmesh",
+				"skin": "goblin",
+				"parent": "right upper leg",
+				"width": 34,
+				"height": 63
+			}
+		},
+		"torso": {
+			"torso": { "name": "goblingirl/torso", "x": 36.28, "y": -5.14, "rotation": -95.74, "width": 68, "height": 96 }
+		},
+		"undie straps": {
+			"undie straps": { "name": "goblingirl/undie-straps", "x": -1.51, "y": 14.18, "width": 55, "height": 19 }
+		},
+		"undies": {
+			"undies": { "name": "goblingirl/undies", "x": 5.4, "y": 1.7, "width": 36, "height": 29 }
+		}
+	}
+},
+"animations": {
+	"walk": {
+		"slots": {
+			"eyes": {
+				"attachment": [
+					{ "time": 0.7, "name": "eyes closed" },
+					{ "time": 0.8, "name": null }
+				]
+			}
+		},
+		"bones": {
+			"left upper leg": {
+				"rotate": [
+					{ "time": 0, "angle": -26.55 },
+					{ "time": 0.1333, "angle": -8.78 },
+					{ "time": 0.2333, "angle": 9.51 },
+					{ "time": 0.3666, "angle": 30.74 },
+					{ "time": 0.5, "angle": 25.33 },
+					{ "time": 0.6333, "angle": 26.11 },
+					{ "time": 0.7333, "angle": 7.45 },
+					{ "time": 0.8666, "angle": -21.19 },
+					{ "time": 1, "angle": -26.55 }
+				],
+				"translate": [
+					{ "time": 0, "x": -1.32, "y": 1.7 },
+					{ "time": 0.3666, "x": -0.06, "y": 2.42 },
+					{ "time": 1, "x": -1.32, "y": 1.7 }
+				]
+			},
+			"right upper leg": {
+				"rotate": [
+					{ "time": 0, "angle": 42.45 },
+					{
+						"time": 0.1333,
+						"angle": 49.86,
+						"curve": [ 0.414, 0, 0.705, 0.99 ]
+					},
+					{ "time": 0.2333, "angle": 22.51 },
+					{ "time": 0.5, "angle": -16.93 },
+					{ "time": 0.6333, "angle": 1.89 },
+					{
+						"time": 0.7333,
+						"angle": 34.86,
+						"curve": [ 0.462, 0.11, 1, 1 ]
+					},
+					{
+						"time": 0.8666,
+						"angle": 58.68,
+						"curve": [ 0.5, 0.02, 1, 1 ]
+					},
+					{ "time": 1, "angle": 42.45 }
+				],
+				"translate": [
+					{ "time": 0, "x": 6.23, "y": 0 },
+					{ "time": 0.2333, "x": 2.14, "y": 2.4 },
+					{ "time": 0.5, "x": 2.44, "y": 4.8 },
+					{ "time": 1, "x": 6.23, "y": 0 }
+				]
+			},
+			"left lower leg": {
+				"rotate": [
+					{ "time": 0, "angle": -18.05 },
+					{ "time": 0.1333, "angle": -63.5 },
+					{ "time": 0.2333, "angle": -83.01 },
+					{ "time": 0.5, "angle": 5.11 },
+					{ "time": 0.6333, "angle": -28.29 },
+					{ "time": 0.7333, "angle": -27.52 },
+					{ "time": 0.8666, "angle": 3.53 },
+					{ "time": 1, "angle": -18.05 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 },
+					{ "time": 0.2333, "x": 2.55, "y": -0.47 },
+					{ "time": 0.5, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 1, "x": 0, "y": 0 }
+				]
+			},
+			"left foot": {
+				"rotate": [
+					{ "time": 0, "angle": -14.56 },
+					{ "time": 0.1333, "angle": -10.42 },
+					{ "time": 0.2333, "angle": -5.01 },
+					{ "time": 0.3, "angle": 6.67 },
+					{ "time": 0.3666, "angle": 3.87 },
+					{ "time": 0.5, "angle": -3.87 },
+					{ "time": 0.6333, "angle": 2.78 },
+					{ "time": 0.7333, "angle": -11.99 },
+					{ "time": 0.8666, "angle": -12.45 },
+					{ "time": 1, "angle": -14.56 }
+				]
+			},
+			"right shoulder": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": 5.29,
+						"curve": [ 0.264, 0, 0.75, 1 ]
+					},
+					{ "time": 0.6333, "angle": 6.65 },
+					{ "time": 1, "angle": 5.29 }
+				]
+			},
+			"right arm": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": -4.02,
+						"curve": [ 0.267, 0, 0.804, 0.99 ]
+					},
+					{
+						"time": 0.6333,
+						"angle": 19.78,
+						"curve": [ 0.307, 0, 0.787, 0.99 ]
+					},
+					{ "time": 1, "angle": -4.02 }
+				]
+			},
+			"right hand": {
+				"rotate": [
+					{ "time": 0, "angle": 8.98 },
+					{ "time": 0.6333, "angle": 0.51 },
+					{ "time": 1, "angle": 8.98 }
+				]
+			},
+			"left shoulder": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": 6.25,
+						"curve": [ 0.339, 0, 0.683, 1 ]
+					},
+					{
+						"time": 0.5,
+						"angle": -11.78,
+						"curve": [ 0.281, 0, 0.686, 0.99 ]
+					},
+					{ "time": 1, "angle": 6.25 }
+				],
+				"translate": [
+					{ "time": 0, "x": 1.15, "y": 0.23 }
+				]
+			},
+			"left hand": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": -21.23,
+						"curve": [ 0.295, 0, 0.755, 0.98 ]
+					},
+					{
+						"time": 0.5,
+						"angle": -27.28,
+						"curve": [ 0.241, 0, 0.75, 0.97 ]
+					},
+					{ "time": 1, "angle": -21.23 }
+				]
+			},
+			"left arm": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": 28.37,
+						"curve": [ 0.339, 0, 0.683, 1 ]
+					},
+					{
+						"time": 0.5,
+						"angle": 60.09,
+						"curve": [ 0.281, 0, 0.686, 0.99 ]
+					},
+					{ "time": 1, "angle": 28.37 }
+				]
+			},
+			"torso": {
+				"rotate": [
+					{ "time": 0, "angle": -10.28 },
+					{
+						"time": 0.1333,
+						"angle": -15.38,
+						"curve": [ 0.545, 0, 0.818, 1 ]
+					},
+					{
+						"time": 0.3666,
+						"angle": -9.78,
+						"curve": [ 0.58, 0.17, 0.669, 0.99 ]
+					},
+					{
+						"time": 0.6333,
+						"angle": -15.75,
+						"curve": [ 0.235, 0.01, 0.795, 1 ]
+					},
+					{
+						"time": 0.8666,
+						"angle": -7.06,
+						"curve": [ 0.209, 0, 0.816, 0.98 ]
+					},
+					{ "time": 1, "angle": -10.28 }
+				],
+				"translate": [
+					{ "time": 0, "x": -3.72, "y": -0.01 }
+				]
+			},
+			"right foot": {
+				"rotate": [
+					{ "time": 0, "angle": -5.25 },
+					{ "time": 0.2333, "angle": -17.76 },
+					{ "time": 0.3666, "angle": -20.09 },
+					{ "time": 0.5, "angle": -19.73 },
+					{ "time": 0.7333, "angle": -11.68 },
+					{ "time": 0.8, "angle": 4.46 },
+					{ "time": 0.8666, "angle": 0.46 },
+					{ "time": 1, "angle": -5.25 }
+				]
+			},
+			"right lower leg": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": -3.39,
+						"curve": [ 0.316, 0.01, 0.741, 0.98 ]
+					},
+					{
+						"time": 0.1333,
+						"angle": -43.21,
+						"curve": [ 0.414, 0, 0.705, 0.99 ]
+					},
+					{ "time": 0.2333, "angle": -25.98 },
+					{ "time": 0.5, "angle": -19.53 },
+					{ "time": 0.6333, "angle": -64.8 },
+					{
+						"time": 0.7333,
+						"angle": -89.54,
+						"curve": [ 0.557, 0.18, 1, 1 ]
+					},
+					{ "time": 1, "angle": -3.39 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 0.5, "x": 0, "y": 0 },
+					{ "time": 0.6333, "x": 2.18, "y": 0.21 },
+					{ "time": 1, "x": 0, "y": 0 }
+				]
+			},
+			"hip": {
+				"rotate": [
+					{ "time": 0, "angle": 0, "curve": "stepped" },
+					{ "time": 1, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": -8.4 },
+					{
+						"time": 0.1333,
+						"x": 0,
+						"y": -9.35,
+						"curve": [ 0.326, 0.05, 0.674, 0.93 ]
+					},
+					{
+						"time": 0.2333,
+						"x": 0,
+						"y": -0.59,
+						"curve": [ 0.325, 0.39, 0.643, 0.7 ]
+					},
+					{ "time": 0.3666, "x": 0, "y": -3.96 },
+					{ "time": 0.5, "x": 0, "y": -8.4 },
+					{
+						"time": 0.6333,
+						"x": 0,
+						"y": -10,
+						"curve": [ 0.359, 0.47, 0.646, 0.74 ]
+					},
+					{
+						"time": 0.7333,
+						"x": 0,
+						"y": -5.29,
+						"curve": [ 0.333, 0.36, 0.662, 0.69 ]
+					},
+					{
+						"time": 0.8,
+						"x": 0,
+						"y": -2.49,
+						"curve": [ 0.322, 0.35, 0.651, 0.68 ]
+					},
+					{ "time": 0.8666, "x": 0, "y": -3.96 },
+					{ "time": 1, "x": 0, "y": -8.4 }
+				]
+			},
+			"neck": {
+				"rotate": [
+					{ "time": 0, "angle": 3.6 },
+					{ "time": 0.1333, "angle": 17.49 },
+					{ "time": 0.2333, "angle": 6.1 },
+					{ "time": 0.3666, "angle": 3.45 },
+					{ "time": 0.5, "angle": 5.17 },
+					{ "time": 0.6333, "angle": 18.36 },
+					{ "time": 0.7333, "angle": 6.09 },
+					{ "time": 0.8666, "angle": 2.28 },
+					{ "time": 1, "angle": 3.6 }
+				]
+			},
+			"head": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": 3.6,
+						"curve": [ 0, 0, 0.704, 1.17 ]
+					},
+					{ "time": 0.1333, "angle": -0.2 },
+					{ "time": 0.2333, "angle": 6.1 },
+					{ "time": 0.3666, "angle": 3.45 },
+					{
+						"time": 0.5,
+						"angle": 5.17,
+						"curve": [ 0, 0, 0.704, 1.61 ]
+					},
+					{ "time": 0.6666, "angle": 1.1 },
+					{ "time": 0.7333, "angle": 6.09 },
+					{ "time": 0.8666, "angle": 2.28 },
+					{ "time": 1, "angle": 3.6 }
+				]
+			},
+			"pelvis": {
+				"rotate": [
+					{ "time": 0, "angle": -1.33 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0.39, "y": -0.78 }
+				]
+			}
+		},
+		"deform": {
+			"default": {
+				"right hand item": {
+					"dagger": [
+						{
+							"time": 0,
+							"offset": 26,
+							"vertices": [ 2.34754, 0.14469 ],
+							"curve": [ 0.25, 0, 0.75, 1 ]
+						},
+						{
+							"time": 0.5,
+							"offset": 8,
+							"vertices": [ -1.19415, 4.31531, 0.07279, 6.41351, 1.66048, 6.18882, 1.75232, 3.59555 ],
+							"curve": [ 0.25, 0, 0.75, 1 ]
+						},
+						{
+							"time": 1,
+							"offset": 26,
+							"vertices": [ 2.34754, 0.14469 ]
+						}
+					]
+				}
+			},
+			"goblin": {
+				"head": {
+					"head": [
+						{
+							"time": 0,
+							"curve": [ 0.632, 0, 0.75, 1 ]
+						},
+						{
+							"time": 0.2,
+							"vertices": [ -10.97826, -6.68962, -4.68015, -2.46175, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1.08534, 0.08391, -1.08534, 0.08391, -1.08534, 0.08391, 0, 0, -2.22324, 2.66465, -4.83295, 2.70084, -5.70553, -0.51941, -3.15962, -1.61501, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -6.64741, 0.81612, -11.82285, -1.34955, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1.08534, 0.08391 ],
+							"curve": [ 0.25, 0, 0.75, 1 ]
+						},
+						{
+							"time": 0.3666,
+							"vertices": [ 10.69275, 4.05949, 3.66373, 1.85426, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.47305, 0.09017, 1.47305, 0.09017, 1.47305, 0.09017, 0, 0, 2.69652, -0.22738, 3.77135, 0.11417, 3.6893, 1.55352, 2.49594, 1.65501, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4.4588, -3.9113, 9.19593, -1.66854, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.47305, 0.09017 ],
+							"curve": [ 0.621, 0, 0.75, 1 ]
+						},
+						{
+							"time": 0.7,
+							"vertices": [ -10.97826, -6.68962, -4.68015, -2.46175, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1.1755, -0.17183, -1.1755, -0.17182, -1.1755, -0.17183, 0, 0, -2.22324, 2.66465, -4.83295, 2.70084, -5.70553, -0.51941, -3.15962, -1.61501, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -6.64741, 0.81612, -11.82285, -1.34955, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1.1755, -0.17183 ],
+							"curve": [ 0.25, 0, 0.75, 1 ]
+						},
+						{
+							"time": 0.8666,
+							"vertices": [ 10.69275, 4.05949, 3.66373, 1.85426, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.38687, 0.08446, 0.38687, 0.08446, 0.38687, 0.08446, 0, 0, 2.69652, -0.22738, 3.77135, 0.11417, 3.6893, 1.55352, 2.49594, 1.65501, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4.4588, -3.9113, 9.19593, -1.66854, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.38687, 0.08446 ],
+							"curve": [ 0.25, 0, 0.75, 1 ]
+						},
+						{ "time": 1 }
+					]
+				},
+				"left foot": {
+					"left foot": [
+						{
+							"time": 0,
+							"offset": 8,
+							"vertices": [ 3.69298, 2.37572, -7.16969, 18.79732, -12.78161, 14.7778, -12.75775, 6.50514, -3.13475, 1.98906, -0.44401, 0.36629, 0, 0, -3.80085, 2.98474 ]
+						},
+						{ "time": 0.1333 },
+						{
+							"time": 0.2333,
+							"offset": 8,
+							"vertices": [ -3.96072, -2.34594, -5.80445, -12.47629, -2.23129, -12.99037, 2.02941, -9.1036, 0, 0, 0, 0, 0, 0, -1.35254, -5.2883 ]
+						},
+						{
+							"time": 0.3666,
+							"offset": 8,
+							"vertices": [ 0.66504, 0.33548, 0.33902, 2.69014, -0.4817, 2.54524, -1.13592, 1.38562, 0, 0, 0, 0, 0, 0, -0.11907, 0.79273 ]
+						},
+						{ "time": 0.5, "curve": "stepped" },
+						{ "time": 0.6333 },
+						{
+							"time": 0.7333,
+							"offset": 8,
+							"vertices": [ -2.97737, 9.40254, -6.91661, 19.92794, -10.55287, 18.41085, -12.37161, 12.38473, -4.72606, 6.30798, 0, 0, -1.48902, 4.88944, -7.06773, 10.70101 ]
+						},
+						{
+							"time": 0.8333,
+							"offset": 6,
+							"vertices": [ 1.05318, 1.56361, -2.52722, 7.9974, -5.5203, 17.14136, -8.93317, 15.79635, -10.73747, 10.22055, -4.23801, 5.36992, 0, 0, 0, 0, -5.83147, 8.55531 ]
+						},
+						{
+							"time": 1,
+							"offset": 8,
+							"vertices": [ 3.69298, 2.37572, -7.16969, 18.79732, -12.78161, 14.7778, -12.75775, 6.50514, -3.13475, 1.98906, -0.44401, 0.36629, 0, 0, -3.80085, 2.98474 ]
+						}
+					]
+				},
+				"pelvis": {
+					"pelvis": [
+						{ "time": 0 },
+						{
+							"time": 0.1333,
+							"offset": 6,
+							"vertices": [ -0.68989, -4.13283 ]
+						},
+						{
+							"time": 0.3333,
+							"offset": 6,
+							"vertices": [ -1.04945, -3.10476 ]
+						},
+						{
+							"time": 0.7,
+							"offset": 6,
+							"vertices": [ -1.4245, -6.30616 ]
+						},
+						{
+							"time": 0.8666,
+							"offset": 6,
+							"vertices": [ -1.13541, -1.79035 ]
+						},
+						{ "time": 1 }
+					]
+				},
+				"right foot": {
+					"right foot": [
+						{ "time": 0 },
+						{
+							"time": 0.1333,
+							"offset": 2,
+							"vertices": [ -2.81258, 2.63114, -2.35238, 3.89441, -1.99921, 4.8639, -0.93273, 5.57982, -0.48886, 5.09854, -0.34812, 3.42912, -0.17445, 1.36898, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1.31305, 1.91371, -1.32986, 3.65703 ]
+						},
+						{
+							"time": 0.2333,
+							"offset": 2,
+							"vertices": [ -6.39088, 6.41245, -7.74575, 8.27191, -7.02471, 11.35894, -4.0347, 13.93454, -2.50399, 12.62962, -1.46124, 7.58915, -0.17445, 1.36898, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -3.84765, 2.61215, -4.53955, 7.92357 ]
+						},
+						{
+							"time": 0.3,
+							"offset": 2,
+							"vertices": [ -8.27184, 6.68821, -9.29764, 10.13797, -8.62231, 14.71339, -4.58629, 18.81939, -2.20304, 17.10709, -0.07794, 9.9046, 2.54451, 1.01642, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2.94624, 2.38007, -4.59398, 10.01888 ]
+						},
+						{
+							"time": 0.3666,
+							"offset": 2,
+							"vertices": [ -10.47683, 9.44175, -13.36882, 12.40982, -14.32568, 16.94392, -9.24462, 23.55674, -5.51711, 21.51377, -1.19581, 11.53192, 2.54451, 1.01642, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -4.14847, 2.29389, -6.63418, 11.37127 ]
+						},
+						{
+							"time": 0.5,
+							"offset": 2,
+							"vertices": [ -5.42473, 4.36854, -10.59004, 7.04468, -11.64251, 11.55845, -6.19665, 20.12805, -1.45497, 18.05411, 4.86619, 6.41678, 2.81462, 0.27601, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2.96412, 4.94829 ]
+						},
+						{ "time": 0.6333 },
+						{
+							"time": 0.7333,
+							"offset": 4,
+							"vertices": [ 1.31462, -6.84099, -0.87905, -12.54479, -5.9851, -14.08367, -7.15892, -11.63193, -5.6792, -4.83544, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2.06163, -6.93844 ]
+						},
+						{
+							"time": 0.8,
+							"offset": 4,
+							"vertices": [ 0.65731, -3.42049, -0.43952, -6.27239, -2.99255, -7.04183, -3.57946, -5.81596, -2.83959, -2.41772, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2.79687, -1.2802, 0, 0, 0, 0, -1.03081, -3.46922 ]
+						},
+						{ "time": 0.8666 }
+					]
+				},
+				"right hand": {
+					"right hand": [
+						{
+							"time": 0,
+							"offset": 4,
+							"vertices": [ -1.48416, 0.34736, 0, 0, 1.31152, 0.08085, 1.60295, 0.09881, 0.13673, 0.1547, 0, 0, 0, 0, -0.72862, -0.0449 ]
+						},
+						{ "time": 0.5 },
+						{
+							"time": 1,
+							"offset": 4,
+							"vertices": [ -1.48416, 0.34736, 0, 0, 1.31152, 0.08085, 1.60295, 0.09881, 0.13673, 0.1547, 0, 0, 0, 0, -0.72862, -0.0449 ]
+						}
+					]
+				},
+				"right lower leg": {
+					"right lower leg": [
+						{ "time": 0 },
+						{
+							"time": 0.6,
+							"offset": 6,
+							"vertices": [ 1.80396, -1.56552 ]
+						},
+						{ "time": 1 }
+					]
+				},
+				"right upper leg": {
+					"right upper leg": [
+						{
+							"time": 0,
+							"vertices": [ -6.03856, -1.46324, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -0.34684, -1.93101, -1.86047, -5.05265, -2.5014, -3.09984 ]
+						},
+						{ "time": 0.3333 },
+						{
+							"time": 0.8666,
+							"offset": 14,
+							"vertices": [ 0.13424, -2.35377, -1.33317, -5.99572, -1.35861, -4.43323 ]
+						},
+						{
+							"time": 1,
+							"vertices": [ -6.03856, -1.46324, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -0.34684, -1.93101, -1.86047, -5.05265, -2.5014, -3.09984 ]
+						}
+					]
+				},
+				"torso": {
+					"torso": [
+						{
+							"time": 0,
+							"offset": 14,
+							"vertices": [ -1.48952, -0.24021, -2.72312, -2.15489, -0.51183, -3.39752, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.0964, -2.61458, 0, 0, 0.57686, -1.24874, 0, 0, 0, 0, -2.11251, -3.29932 ]
+						},
+						{
+							"time": 0.1333,
+							"offset": 14,
+							"vertices": [ 1.31318, -0.59727, -0.97944, -1.62934, 0.74861, -0.6123, -1.44598, 1.97515, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2.65701, -3.95221, 0, 0, -1.46987, -0.31373, 0, 0, 0, 0, -3.31756, -3.5535, -2.56329, 0.29673 ]
+						},
+						{
+							"time": 0.3,
+							"offset": 14,
+							"vertices": [ 6.03761, -3.13561, 7.55475, -1.38111, 6.79747, 0.31171, 4.23503, 1.14012, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4.07575, -5.16824, 0, 0, 4.0041, 0.27245, 0, 0, 0, 0, 3.4376, -3.52286 ]
+						},
+						{
+							"time": 0.5,
+							"offset": 14,
+							"vertices": [ 2.25942, -0.87202, 2.575, -0.56861, 3.17112, -0.57003, 1.48704, 0.9924, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3.22451, -4.43862, 0, 0, 1.48691, 0.01586, 0, 0, 0, 0, 0.31388, -3.28095, -1.53797, 0.17803 ]
+						},
+						{
+							"time": 0.6333,
+							"offset": 14,
+							"vertices": [ 0.75002, -1.51701, -0.97944, -1.62934, 0.74861, -0.6123, -1.44598, 1.97515, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2.65701, -3.95221, 0, 0, -1.46987, -0.31373, 0, 0, 0, 0, -3.31756, -3.5535, -2.56329, 0.29673 ]
+						},
+						{
+							"time": 0.8666,
+							"offset": 14,
+							"vertices": [ 0.62202, -1.26262, 0.38489, -2.20701, 3.25048, -0.50042, 2.41108, 2.39315, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.6639, -3.10099, 0, 0, 2.30655, -1.15196, 0, 0, 0, 0, -0.07676, -3.63497, -0.9321, 0.1079 ]
+						},
+						{
+							"time": 1,
+							"offset": 14,
+							"vertices": [ -1.48952, -0.24021, -2.72312, -2.15489, -0.51183, -3.39752, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.0964, -2.61458, 0, 0, 0.57686, -1.24874, 0, 0, 0, 0, -2.11251, -3.29932 ]
+						}
+					]
+				},
+				"undie straps": {
+					"undie straps": [
+						{
+							"time": 0,
+							"offset": 2,
+							"vertices": [ -1.77696, 0.54759, -0.96145, -1.03793, -0.39148, -0.24071, -1.77696, 0.54759 ]
+						},
+						{
+							"time": 0.1333,
+							"offset": 2,
+							"vertices": [ -2.25683, -1.03177, -1.49719, -4.23861, -0.74469, -2.84906, -1.90072, 0.54477 ]
+						},
+						{
+							"time": 0.3333,
+							"offset": 2,
+							"vertices": [ -2.37974, -0.05431, -0.49433, 0.19436, -0.90861, 1.16519, -1.60956, 2.70798, 0.96186, 0.80615 ]
+						},
+						{
+							"time": 0.7,
+							"offset": 2,
+							"vertices": [ -0.91714, -2.76567, -0.62214, -3.63489, -0.8494, -2.26772, -2.56076, 0.5297 ]
+						},
+						{
+							"time": 0.8666,
+							"offset": 2,
+							"vertices": [ -2.56076, 0.5297, -1.58064, 0.32031, -1.3847, 0.32476, -2.56076, 0.5297 ]
+						},
+						{
+							"time": 1,
+							"offset": 2,
+							"vertices": [ -1.77696, 0.54759, -0.80128, 0.53413, -0.80128, 0.53413, -1.77696, 0.54759 ]
+						}
+					]
+				},
+				"undies": {
+					"undies": [
+						{
+							"time": 0,
+							"vertices": [ 0.43098, 0.722, 10.60295, -0.11699, 2.29598, 0, 2.29598, 0, 2.29598, 0, 0.58798, 0.24399, -2.40018, -0.65335, -2.2782, -0.77533, 2.29598, 0, 0.58798, -0.48799, 4.98697, -0.11699, 6.50796, -0.23399 ]
+						},
+						{
+							"time": 0.1333,
+							"vertices": [ 0.72659, 0.43319, 7.20416, -0.1638, 1.37759, 0, 1.37759, 0, 1.37759, 0, 1.25279, 0.0464, -0.99861, -2.95085, -1.37542, -3.07404, 1.37759, 0, 0.35279, -0.29279, 2.99218, -0.07019, 3.90478, -0.14039 ]
+						},
+						{
+							"time": 0.3333,
+							"vertices": [ 1.16999, 0, 2.10599, -0.234, 0, 0, 0, 0, 0, 0, 2.24999, -0.24999, -0.4344, 0.60551, -1.55939, 0.48051 ]
+						},
+						{
+							"time": 0.5333,
+							"vertices": [ 1.16999, 0, -0.234, -0.93599, -2.92499, 0.35099, 0, 0, 0, 0, 0.49999, -0.24999, -0.64078, -2.07914, -0.64078, -2.07914 ]
+						},
+						{
+							"time": 0.7,
+							"vertices": [ 1.8627, -0.11514, 4.66326, -0.09099, -1.76428, 0.21171, 0, 0, -0.56832, 0.32832, -1.13833, -1.1511, -2.19996, -3.47068, -1.29718, -3.47068, 0, 0, 0, 0, 1.58785, -0.04642, 2.65941, 0.16714 ]
+						},
+						{
+							"time": 0.8333,
+							"vertices": [ 2.41687, -0.20725, 8.58108, 0.585, -0.83571, 0.10028, 0, 0, -1.02299, 0.59098, -2.44899, -1.872, -1.62499, 0, 0, 0, 0, 0, 0, 0, 2.85813, -0.08356, 4.78695, 0.30086 ]
+						},
+						{
+							"time": 0.8666,
+							"vertices": [ 2.01969, -0.0214, 8.98545, 0.4446, -0.20937, 0.08022, 0.45919, 0, -0.35919, 0.47279, -1.84159, -1.4488, -0.79153, 1.2642, 0.53285, 1.23981, 0.45919, 0, 0.11759, -0.09759, 3.2839, -0.09025, 5.13115, 0.19388 ]
+						},
+						{
+							"time": 1,
+							"vertices": [ 0.43098, 0.722, 10.60295, -0.11699, 2.29598, 0, 2.29598, 0, 2.29598, 0, 0.58798, 0.24399, -2.40018, -0.65335, -2.2782, -0.77533, 2.29598, 0, 0.58798, -0.48799, 4.98697, -0.11699, 6.50796, -0.23399 ]
+						}
+					]
+				}
+			}
+		}
+	}
+}
+}

+ 8 - 0
spine-unity/Assets/Examples/Spine/Goblins/goblins.json.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: c3921acb20cbc25418859f1b213d3d3f
+timeCreated: 1467115504
+licenseType: Free
+TextScriptImporter:
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
spine-unity/Assets/Examples/Spine/Goblins/goblins.png


+ 6 - 6
spine-unity/Assets/Examples/Spine/Goblins/goblins-mesh.png.meta → spine-unity/Assets/Examples/Spine/Goblins/goblins.png.meta

@@ -1,6 +1,6 @@
 fileFormatVersion: 2
-guid: 803c2e614a63081439fde6276d110661
-timeCreated: 1455501336
+guid: 5fb7efec30c79cb46a705e0d04debb04
+timeCreated: 1467205225
 licenseType: Free
 TextureImporter:
   fileIDToRecycleName: {}
@@ -30,15 +30,15 @@ TextureImporter:
   maxTextureSize: 2048
   textureSettings:
     filterMode: -1
-    aniso: -1
+    aniso: 16
     mipBias: -1
-    wrapMode: -1
-  nPOTScale: 1
+    wrapMode: 1
+  nPOTScale: 0
   lightmap: 0
   rGBM: 0
   compressionQuality: 50
   allowsAlphaSplitting: 0
-  spriteMode: 0
+  spriteMode: 1
   spriteExtrude: 1
   spriteMeshType: 1
   alignment: 0

+ 16 - 0
spine-unity/Assets/Examples/Spine/Goblins/goblins_Atlas.asset

@@ -0,0 +1,16 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!114 &11400000
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_PrefabParentObject: {fileID: 0}
+  m_PrefabInternal: {fileID: 0}
+  m_GameObject: {fileID: 0}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: a6b194f808b1af6499c93410e504af42, type: 3}
+  m_Name: goblins_Atlas
+  m_EditorClassIdentifier: 
+  atlasFile: {fileID: 4900000, guid: 179f09b47e5402545a1aa69bf5cb2cba, type: 3}
+  materials:
+  - {fileID: 2100000, guid: 54091ef934c41eb4192f72bfd8e3bcc9, type: 2}

+ 8 - 0
spine-unity/Assets/Examples/Spine/Goblins/goblins_Atlas.asset.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: bb54bdab69af2bb49b35577b80dcaad9
+timeCreated: 1467115504
+licenseType: Free
+NativeFormatImporter:
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 30 - 0
spine-unity/Assets/Examples/Spine/Goblins/goblins_Material.mat

@@ -0,0 +1,30 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!21 &2100000
+Material:
+  serializedVersion: 6
+  m_ObjectHideFlags: 0
+  m_PrefabParentObject: {fileID: 0}
+  m_PrefabInternal: {fileID: 0}
+  m_Name: goblins_Material
+  m_Shader: {fileID: 4800000, guid: 1e8a610c9e01c3648bac42585e5fc676, type: 3}
+  m_ShaderKeywords: 
+  m_LightmapFlags: 5
+  m_CustomRenderQueue: -1
+  stringTagMap: {}
+  m_SavedProperties:
+    serializedVersion: 2
+    m_TexEnvs:
+      data:
+        first:
+          name: _MainTex
+        second:
+          m_Texture: {fileID: 2800000, guid: 5fb7efec30c79cb46a705e0d04debb04, type: 3}
+          m_Scale: {x: 1, y: 1}
+          m_Offset: {x: 0, y: 0}
+    m_Floats:
+      data:
+        first:
+          name: _Cutoff
+        second: 0.1
+    m_Colors: {}

+ 8 - 0
spine-unity/Assets/Examples/Spine/Goblins/goblins_Material.mat.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 54091ef934c41eb4192f72bfd8e3bcc9
+timeCreated: 1467115504
+licenseType: Free
+NativeFormatImporter:
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

Vissa filer visades inte eftersom för många filer har ändrats