Explorar el Código

[csharp][unity][monogame][xna] Updated for compatibility with Spine 3.5.x

pharan hace 8 años
padre
commit
a05ee4130f
Se han modificado 93 ficheros con 3785 adiciones y 2858 borrados
  1. 1 1
      spine-csharp/README.md
  2. 25 72
      spine-csharp/spine-csharp.csproj
  3. 447 249
      spine-csharp/src/Animation.cs
  4. 758 141
      spine-csharp/src/AnimationState.cs
  5. 6 0
      spine-csharp/src/AnimationStateData.cs
  6. 1 1
      spine-csharp/src/Attachments/AtlasAttachmentLoader.cs
  7. 1 1
      spine-csharp/src/Attachments/PathAttachment.cs
  8. 1 2
      spine-csharp/src/Attachments/RegionAttachment.cs
  9. 10 7
      spine-csharp/src/Attachments/VertexAttachment.cs
  10. 137 116
      spine-csharp/src/Bone.cs
  11. 16 3
      spine-csharp/src/BoneData.cs
  12. 15 9
      spine-csharp/src/Event.cs
  13. 11 0
      spine-csharp/src/ExposedList.cs
  14. 5 0
      spine-csharp/src/IConstraint.cs
  15. 23 20
      spine-csharp/src/IkConstraint.cs
  16. 2 0
      spine-csharp/src/IkConstraintData.cs
  17. 49 51
      spine-csharp/src/Json.cs
  18. 14 14
      spine-csharp/src/MathUtils.cs
  19. 8 8
      spine-csharp/src/PathConstraint.cs
  20. 11 5
      spine-csharp/src/PathConstraintData.cs
  21. 103 82
      spine-csharp/src/Skeleton.cs
  22. 27 15
      spine-csharp/src/SkeletonBinary.cs
  23. 8 1
      spine-csharp/src/SkeletonBounds.cs
  24. 12 5
      spine-csharp/src/SkeletonData.cs
  25. 13 7
      spine-csharp/src/SkeletonJson.cs
  26. 1 1
      spine-csharp/src/Skin.cs
  27. 21 12
      spine-csharp/src/TransformConstraint.cs
  28. 2 0
      spine-csharp/src/TransformConstraintData.cs
  29. 16 15
      spine-monogame/LICENSE
  30. 1 1
      spine-monogame/README.md
  31. 3 3
      spine-unity/Assets/Examples/Getting Started/Scripts/BasicPlatformerController.cs
  32. 1 1
      spine-unity/Assets/Examples/Getting Started/Scripts/Raptor.cs
  33. 11 3
      spine-unity/Assets/Examples/Getting Started/Scripts/SpineboyBeginnerView.cs
  34. 1 1
      spine-unity/Assets/Examples/Scripts/SpineGauge.cs
  35. 13 21
      spine-unity/Assets/Examples/Scripts/Spineboy.cs
  36. 4 4
      spine-unity/Assets/Examples/Spine/Dragon/dragon.json
  37. 4 10
      spine-unity/Assets/Examples/Spine/Eyes/eyes.json
  38. 193 193
      spine-unity/Assets/Examples/Spine/FootSoldier/FootSoldier.json
  39. 1 1
      spine-unity/Assets/Examples/Spine/Gauge/Gauge.json
  40. 35 34
      spine-unity/Assets/Examples/Spine/Goblins/goblins.json
  41. 30 6
      spine-unity/Assets/Examples/Spine/Hero/hero-mesh.json
  42. 20 22
      spine-unity/Assets/Examples/Spine/Raggedy Spineboy/Raggedy Spineboy.json
  43. 20 10
      spine-unity/Assets/Examples/Spine/Raptor/raptor.json
  44. 177 67
      spine-unity/Assets/Examples/Spine/Spineboy/spineboy.json
  45. 13 12
      spine-unity/Assets/Examples/Spine/Spineunitygirl/Doi.json
  46. 60 13
      spine-unity/Assets/spine-unity/Asset Types/AtlasAsset.cs
  47. 74 82
      spine-unity/Assets/spine-unity/Asset Types/Editor/AtlasAssetInspector.cs
  48. 118 133
      spine-unity/Assets/spine-unity/Asset Types/Editor/SkeletonDataAssetInspector.cs
  49. 23 0
      spine-unity/Assets/spine-unity/Asset Types/SkeletonDataAsset.cs
  50. 1 15
      spine-unity/Assets/spine-unity/Editor/BoneFollowerInspector.cs
  51. 3 3
      spine-unity/Assets/spine-unity/Editor/Menus.cs
  52. 25 25
      spine-unity/Assets/spine-unity/Editor/SkeletonAnimationInspector.cs
  53. 20 34
      spine-unity/Assets/spine-unity/Editor/SkeletonBaker.cs
  54. 116 80
      spine-unity/Assets/spine-unity/Editor/SkeletonRendererInspector.cs
  55. 4 2
      spine-unity/Assets/spine-unity/Editor/SpineAttributeDrawers.cs
  56. 79 100
      spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs
  57. 173 58
      spine-unity/Assets/spine-unity/Editor/SpineInspectorUtility.cs
  58. 0 2
      spine-unity/Assets/spine-unity/ISkeletonAnimation.cs
  59. 0 3
      spine-unity/Assets/spine-unity/Mesh Generation/DoubleBuffered.cs
  60. 78 56
      spine-unity/Assets/spine-unity/Modules/BoundingBoxFollower/BoundingBoxFollower.cs
  61. 112 28
      spine-unity/Assets/spine-unity/Modules/BoundingBoxFollower/Editor/BoundingBoxFollowerInspector.cs
  62. 1 1
      spine-unity/Assets/spine-unity/Modules/CustomMaterials/Editor/SkeletonRendererCustomMaterialsInspector.cs
  63. 5 5
      spine-unity/Assets/spine-unity/Modules/Ghost/SkeletonGhost.cs
  64. 1 1
      spine-unity/Assets/spine-unity/Modules/Ragdoll/SkeletonRagdoll.cs
  65. 2 2
      spine-unity/Assets/spine-unity/Modules/Ragdoll/SkeletonRagdoll2D.cs
  66. 30 29
      spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/Editor/SkeletonGraphicInspector.cs
  67. 2 1
      spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/SkeletonGraphic.cs
  68. 1 31
      spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonPartsRendererInspector.cs
  69. 12 12
      spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonRenderSeparatorInspector.cs
  70. 0 17
      spine-unity/Assets/spine-unity/Modules/SkeletonUtility Modules/SkeletonUtilityGroundConstraint.cs
  71. 9 7
      spine-unity/Assets/spine-unity/Modules/SpriteAttacher.cs
  72. 1 1
      spine-unity/Assets/spine-unity/Modules/YieldInstructions/WaitForSpineAnimationComplete.cs
  73. 4 8
      spine-unity/Assets/spine-unity/Modules/YieldInstructions/WaitForSpineEvent.cs
  74. 28 27
      spine-unity/Assets/spine-unity/SkeletonAnimation.cs
  75. 67 174
      spine-unity/Assets/spine-unity/SkeletonAnimator.cs
  76. 78 72
      spine-unity/Assets/spine-unity/SkeletonExtensions.cs
  77. 14 42
      spine-unity/Assets/spine-unity/SkeletonRenderer.cs
  78. 26 28
      spine-unity/Assets/spine-unity/SkeletonUtility/Editor/SkeletonUtilityBoneInspector.cs
  79. 124 159
      spine-unity/Assets/spine-unity/SkeletonUtility/Editor/SkeletonUtilityInspector.cs
  80. 37 97
      spine-unity/Assets/spine-unity/SkeletonUtility/SkeletonUtility.cs
  81. 38 143
      spine-unity/Assets/spine-unity/SkeletonUtility/SkeletonUtilityBone.cs
  82. 1 1
      spine-unity/Assets/spine-unity/SkeletonUtility/SkeletonUtilityConstraint.cs
  83. 3 1
      spine-unity/README.md
  84. 16 15
      spine-xna/LICENSE
  85. 1 1
      spine-xna/README.md
  86. 8 8
      spine-xna/example/src/ExampleGame.cs
  87. 0 1
      spine-xna/example/src/ExampleProgram.cs
  88. 20 19
      spine-xna/src/MeshBatcher.cs
  89. 20 19
      spine-xna/src/RegionBatcher.cs
  90. 19 18
      spine-xna/src/SkeletonMeshRenderer.cs
  91. 19 18
      spine-xna/src/SkeletonRegionRenderer.cs
  92. 22 21
      spine-xna/src/Util.cs
  93. 19 18
      spine-xna/src/XnaTextureLoader.cs

+ 1 - 1
spine-csharp/README.md

@@ -10,7 +10,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f
 
 
 ## Spine version
 ## Spine version
 
 
-spine-csharp works with data exported from Spine 3.4.02.
+spine-csharp works with data exported from Spine 3.5.x.
 
 
 spine-csharp supports all Spine features.
 spine-csharp supports all Spine features.
 
 

+ 25 - 72
spine-csharp/spine-csharp.csproj

@@ -53,18 +53,10 @@
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
-    <Compile Include="src\Animation.cs">
-      <SubType>Code</SubType>
-    </Compile>
-    <Compile Include="src\AnimationState.cs">
-      <SubType>Code</SubType>
-    </Compile>
-    <Compile Include="src\AnimationStateData.cs">
-      <SubType>Code</SubType>
-    </Compile>
-    <Compile Include="src\Atlas.cs">
-      <SubType>Code</SubType>
-    </Compile>
+    <Compile Include="src\Animation.cs" />
+    <Compile Include="src\AnimationState.cs" />
+    <Compile Include="src\AnimationStateData.cs" />
+    <Compile Include="src\Atlas.cs" />
     <Compile Include="src\Attachments\AtlasAttachmentLoader.cs" />
     <Compile Include="src\Attachments\AtlasAttachmentLoader.cs" />
     <Compile Include="src\Attachments\Attachment.cs" />
     <Compile Include="src\Attachments\Attachment.cs" />
     <Compile Include="src\Attachments\AttachmentLoader.cs" />
     <Compile Include="src\Attachments\AttachmentLoader.cs" />
@@ -74,69 +66,30 @@
     <Compile Include="src\Attachments\PathAttachment.cs" />
     <Compile Include="src\Attachments\PathAttachment.cs" />
     <Compile Include="src\Attachments\RegionAttachment.cs" />
     <Compile Include="src\Attachments\RegionAttachment.cs" />
     <Compile Include="src\Attachments\VertexAttachment.cs" />
     <Compile Include="src\Attachments\VertexAttachment.cs" />
-    <Compile Include="src\BlendMode.cs">
-      <SubType>Code</SubType>
-    </Compile>
-    <Compile Include="src\Bone.cs">
-      <SubType>Code</SubType>
-    </Compile>
-    <Compile Include="src\BoneData.cs">
-      <SubType>Code</SubType>
-    </Compile>
-    <Compile Include="src\Event.cs">
-      <SubType>Code</SubType>
-    </Compile>
-    <Compile Include="src\EventData.cs">
-      <SubType>Code</SubType>
-    </Compile>
-    <Compile Include="src\ExposedList.cs">
-      <SubType>Code</SubType>
-    </Compile>
-    <Compile Include="src\IkConstraint.cs">
-      <SubType>Code</SubType>
-    </Compile>
-    <Compile Include="src\IkConstraintData.cs">
-      <SubType>Code</SubType>
-    </Compile>
-    <Compile Include="src\IUpdatable.cs">
-      <SubType>Code</SubType>
-    </Compile>
+    <Compile Include="src\BlendMode.cs" />
+    <Compile Include="src\Bone.cs" />
+    <Compile Include="src\BoneData.cs" />
+    <Compile Include="src\Event.cs" />
+    <Compile Include="src\EventData.cs" />
+    <Compile Include="src\ExposedList.cs" />
+    <Compile Include="src\IConstraint.cs" />
+    <Compile Include="src\IkConstraint.cs" />
+    <Compile Include="src\IkConstraintData.cs" />
+    <Compile Include="src\IUpdatable.cs" />
     <Compile Include="src\Json.cs" />
     <Compile Include="src\Json.cs" />
-    <Compile Include="src\MathUtils.cs">
-      <SubType>Code</SubType>
-    </Compile>
+    <Compile Include="src\MathUtils.cs" />
     <Compile Include="src\PathConstraint.cs" />
     <Compile Include="src\PathConstraint.cs" />
     <Compile Include="src\PathConstraintData.cs" />
     <Compile Include="src\PathConstraintData.cs" />
-    <Compile Include="src\Skeleton.cs">
-      <SubType>Code</SubType>
-    </Compile>
-    <Compile Include="src\SkeletonBinary.cs">
-      <SubType>Code</SubType>
-    </Compile>
-    <Compile Include="src\SkeletonBounds.cs">
-      <SubType>Code</SubType>
-    </Compile>
-    <Compile Include="src\SkeletonData.cs">
-      <SubType>Code</SubType>
-    </Compile>
-    <Compile Include="src\SkeletonJson.cs">
-      <SubType>Code</SubType>
-    </Compile>
-    <Compile Include="src\Skin.cs">
-      <SubType>Code</SubType>
-    </Compile>
-    <Compile Include="src\Slot.cs">
-      <SubType>Code</SubType>
-    </Compile>
-    <Compile Include="src\SlotData.cs">
-      <SubType>Code</SubType>
-    </Compile>
-    <Compile Include="src\TransformConstraint.cs">
-      <SubType>Code</SubType>
-    </Compile>
-    <Compile Include="src\TransformConstraintData.cs">
-      <SubType>Code</SubType>
-    </Compile>
+    <Compile Include="src\Skeleton.cs" />
+    <Compile Include="src\SkeletonBinary.cs" />
+    <Compile Include="src\SkeletonBounds.cs" />
+    <Compile Include="src\SkeletonData.cs" />
+    <Compile Include="src\SkeletonJson.cs" />
+    <Compile Include="src\Skin.cs" />
+    <Compile Include="src\Slot.cs" />
+    <Compile Include="src\SlotData.cs" />
+    <Compile Include="src\TransformConstraint.cs" />
+    <Compile Include="src\TransformConstraintData.cs" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup />
   <ItemGroup />
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />

+ 447 - 249
spine-csharp/src/Animation.cs

@@ -49,10 +49,17 @@ namespace Spine {
 			this.duration = duration;
 			this.duration = duration;
 		}
 		}
 
 
-		/// <summary>Poses the skeleton at the specified time for this animation.</summary>
+		/// <summary>Applies all the animation's timelines to the specified skeleton.</summary>
+		/// <param name="skeleton">The skeleton to be posed.</param>
 		/// <param name="lastTime">The last time the animation was applied.</param>
 		/// <param name="lastTime">The last time the animation was applied.</param>
+		/// <param name="time">The point in time in the animation to apply to the skeleton.</param>
+		/// <param name="loop">If true, time wraps within the animation duration.</param>
 		/// <param name="events">Any triggered events are added. May be null.</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) {
+		/// <param name="alpha">The percentage between this animation's pose and the current pose.</param>
+		/// <param name="setupPose">If true, the animation is mixed with the setup pose, else it is mixed with the current pose. Passing true when alpha is 1 is slightly more efficient.</param>
+		/// <param name="mixingOut">True when mixing over time toward the setup or current pose, false when mixing toward the keyed pose. Irrelevant when alpha is 1.</param>
+		/// <seealso cref="Timeline.Apply(Skeleton, float, float, ExposedList, float, bool, bool)"/>
+		public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList<Event> events, float alpha, bool setupPose, bool mixingOut) {
 			if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
 			if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
 
 
 			if (loop && duration != 0) {
 			if (loop && duration != 0) {
@@ -62,28 +69,11 @@ namespace Spine {
 
 
 			ExposedList<Timeline> timelines = this.timelines;
 			ExposedList<Timeline> timelines = this.timelines;
 			for (int i = 0, n = timelines.Count; i < n; i++)
 			for (int i = 0, n = timelines.Count; i < n; i++)
-				timelines.Items[i].Apply(skeleton, lastTime, time, events, 1);
-		}
-
-		/// <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. 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", "skeleton cannot be null.");
-
-			if (loop && duration != 0) {
-				time %= duration;
-				if (lastTime > 0) lastTime %= duration;
-			}
-
-			ExposedList<Timeline> timelines = this.timelines;
-			for (int i = 0, n = timelines.Count; i < n; i++)
-				timelines.Items[i].Apply(skeleton, lastTime, time, events, alpha);
+				timelines.Items[i].Apply(skeleton, lastTime, time, events, alpha, setupPose, mixingOut);
 		}
 		}
 
 
 		/// <param name="target">After the first and before the last entry.</param>
 		/// <param name="target">After the first and before the last entry.</param>
-		internal static int binarySearch (float[] values, float target, int step) {
+		internal static int BinarySearch (float[] values, float target, int step) {
 			int low = 0;
 			int low = 0;
 			int high = values.Length / step - 2;
 			int high = values.Length / step - 2;
 			if (high == 0) return step;
 			if (high == 0) return step;
@@ -99,7 +89,7 @@ namespace Spine {
 		}
 		}
 
 
 		/// <param name="target">After the first and before the last entry.</param>
 		/// <param name="target">After the first and before the last entry.</param>
-		internal static int binarySearch (float[] values, float target) {
+		internal static int BinarySearch (float[] values, float target) {
 			int low = 0;
 			int low = 0;
 			int high = values.Length - 2;
 			int high = values.Length - 2;
 			if (high == 0) return 1;
 			if (high == 0) return 1;
@@ -114,7 +104,7 @@ namespace Spine {
 			}
 			}
 		}
 		}
 
 
-		internal static int linearSearch (float[] values, float target, int step) {
+		internal static int LinearSearch (float[] values, float target, int step) {
 			for (int i = 0, last = values.Length - step; i <= last; i += step)
 			for (int i = 0, last = values.Length - step; i <= last; i += step)
 				if (values[i] > target) return i;
 				if (values[i] > target) return i;
 			return -1;
 			return -1;
@@ -123,8 +113,20 @@ namespace Spine {
 
 
 	public interface Timeline {
 	public interface Timeline {
 		/// <summary>Sets the value(s) for the specified time.</summary>
 		/// <summary>Sets the value(s) for the specified time.</summary>
-		/// <param name="events">May be null to not collect fired events.</param>
-		void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> events, float alpha);
+		/// <param name="events">Any triggered events are added. May be null.</param>
+		/// <param name="setupPose">True when the timeline is mixed with the setup pose, false when it is mixed with the current pose. Passing true when alpha is 1 is slightly more efficient.</param>
+		/// <param name="mixingOut">True when mixing over time toward the setup or current pose, false when mixing toward the keyed pose.
+		/// Used for timelines with instant transitions, eg draw order, attachment visibility, scale sign.</param>
+		void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> events, float alpha, bool setupPose, bool mixingOut);
+		int PropertyId { get; }
+	}
+
+	internal enum TimelineType {
+		Rotate = 0, Translate, Scale, Shear, //
+		Attachment, Color, Deform, //
+		Event, DrawOrder, //
+		IkConstraint, TransformConstraint, //
+		PathConstraintPosition, PathConstraintSpacing, PathConstraintMix
 	}
 	}
 
 
 	/// <summary>Base class for frames that use an interpolation bezier curve.</summary>
 	/// <summary>Base class for frames that use an interpolation bezier curve.</summary>
@@ -140,7 +142,9 @@ namespace Spine {
 			curves = new float[(frameCount - 1) * BEZIER_SIZE];
 			curves = new float[(frameCount - 1) * BEZIER_SIZE];
 		}
 		}
 
 
-		abstract public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha);
+		abstract public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, bool setupPose, bool mixingOut);
+
+		abstract public int PropertyId { get; }
 
 
 		public void SetLinear (int frameIndex) {
 		public void SetLinear (int frameIndex) {
 			curves[frameIndex * BEZIER_SIZE] = LINEAR;
 			curves[frameIndex * BEZIER_SIZE] = LINEAR;
@@ -218,6 +222,10 @@ namespace Spine {
 		public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } }
 		public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } }
 		public float[] Frames { get { return frames; } set { frames = value; } } // time, angle, ...
 		public float[] Frames { get { return frames; } set { frames = value; } } // time, angle, ...
 
 
+		override public int PropertyId {
+			get { return ((int)TimelineType.Rotate << 24) + boneIndex; }
+		}
+
 		public RotateTimeline (int frameCount)
 		public RotateTimeline (int frameCount)
 			: base(frameCount) {
 			: base(frameCount) {
 			frames = new float[frameCount << 1];
 			frames = new float[frameCount << 1];
@@ -230,41 +238,42 @@ namespace Spine {
 			frames[frameIndex + ROTATION] = degrees;
 			frames[frameIndex + ROTATION] = degrees;
 		}
 		}
 
 
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
+		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, bool setupPose, bool mixingOut) {
 			float[] frames = this.frames;
 			float[] frames = this.frames;
 			if (time < frames[0]) return; // Time is before first frame.
 			if (time < frames[0]) return; // Time is before first frame.
 
 
 			Bone bone = skeleton.bones.Items[boneIndex];
 			Bone bone = skeleton.bones.Items[boneIndex];
 
 
-			float amount;
+			float r;
 
 
 			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
 			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)
-					amount += 360;
-				bone.rotation += amount * alpha;
+				if (setupPose) {
+					bone.rotation = bone.data.rotation + frames[frames.Length + PREV_ROTATION] * alpha;
+				} else {
+					r = bone.data.rotation + frames[frames.Length + PREV_ROTATION] - bone.rotation;
+					r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; // Wrap within -180 and 180.
+					bone.rotation += r * alpha;
+				}
 				return;
 				return;
 			}
 			}
 
 
 			// Interpolate between the previous frame and the current frame.
 			// Interpolate between the previous frame and the current frame.
-			int frame = Animation.binarySearch(frames, time, ENTRIES);
+			int frame = Animation.BinarySearch(frames, time, ENTRIES);
 			float prevRotation = frames[frame + PREV_ROTATION];
 			float prevRotation = frames[frame + PREV_ROTATION];
 			float frameTime = frames[frame];
 			float frameTime = frames[frame];
 			float percent = GetCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
 			float percent = GetCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
 
 
-			amount = frames[frame + ROTATION] - prevRotation;
-			while (amount > 180)
-				amount -= 360;
-			while (amount < -180)
-				amount += 360;
-			amount = bone.data.rotation + (prevRotation + amount * percent) - bone.rotation;
-			while (amount > 180)
-				amount -= 360;
-			while (amount < -180)
-				amount += 360;
-			bone.rotation += amount * alpha;
+			r = frames[frame + ROTATION] - prevRotation;
+			r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360;
+			r = prevRotation + r * percent;
+			if (setupPose) {
+				r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360;
+				bone.rotation = bone.data.rotation + r * alpha;
+			} else {
+				r = bone.data.rotation + r - bone.rotation;
+				r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360;
+				bone.rotation += r * alpha;
+			}
 		}
 		}
 	}
 	}
 
 
@@ -279,6 +288,10 @@ namespace Spine {
 		public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } }
 		public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } }
 		public float[] Frames { get { return frames; } set { frames = value; } } // time, value, value, ...
 		public float[] Frames { get { return frames; } set { frames = value; } } // time, value, value, ...
 
 
+		override public int PropertyId {
+			get { return ((int)TimelineType.Translate << 24) + boneIndex; }
+		}
+
 		public TranslateTimeline (int frameCount)
 		public TranslateTimeline (int frameCount)
 			: base(frameCount) {
 			: base(frameCount) {
 			frames = new float[frameCount * ENTRIES];
 			frames = new float[frameCount * ENTRIES];
@@ -292,83 +305,132 @@ namespace Spine {
 			frames[frameIndex + Y] = y;
 			frames[frameIndex + Y] = y;
 		}
 		}
 
 
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
+		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, bool setupPose, bool mixingOut) {
 			float[] frames = this.frames;
 			float[] frames = this.frames;
 			if (time < frames[0]) return; // Time is before first frame.
 			if (time < frames[0]) return; // Time is before first frame.
 
 
 			Bone bone = skeleton.bones.Items[boneIndex];
 			Bone bone = skeleton.bones.Items[boneIndex];
 
 
+			float x, y;
 			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
 			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, ENTRIES);
-			float prevX = frames[frame + PREV_X];
-			float prevY = frames[frame + PREV_Y];
-			float frameTime = frames[frame];
-			float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+				x = frames[frames.Length + PREV_X];
+				y = frames[frames.Length + PREV_Y];
+			} else {
+				// Interpolate between the previous frame and the current frame.
+				int frame = Animation.BinarySearch(frames, time, ENTRIES);
+				x = frames[frame + PREV_X];
+				y = frames[frame + PREV_Y];
+				float frameTime = frames[frame];
+				float percent = GetCurvePercent(frame / ENTRIES - 1,
+					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
 
 
-			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;
+				x += (frames[frame + X] - x) * percent;
+				y += (frames[frame + Y] - y) * percent;
+			}
+			if (setupPose) {
+				bone.x = bone.data.x + x * alpha;
+				bone.y = bone.data.y + y * alpha;
+			} else {
+				bone.x += (bone.data.x + x - bone.x) * alpha;
+				bone.y += (bone.data.y + y - bone.y) * alpha;
+			}
 		}
 		}
 	}
 	}
 
 
 	public class ScaleTimeline : TranslateTimeline {
 	public class ScaleTimeline : TranslateTimeline {
+		override public int PropertyId {
+			get { return ((int)TimelineType.Scale << 24) + boneIndex; }
+		}
+
 		public ScaleTimeline (int frameCount)
 		public ScaleTimeline (int frameCount)
 			: base(frameCount) {
 			: base(frameCount) {
 		}
 		}
 
 
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
+		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, bool setupPose, bool mixingOut) {
 			float[] frames = this.frames;
 			float[] frames = this.frames;
 			if (time < frames[0]) return; // Time is before first frame.
 			if (time < frames[0]) return; // Time is before first frame.
 
 
 			Bone bone = skeleton.bones.Items[boneIndex];
 			Bone bone = skeleton.bones.Items[boneIndex];
-			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, ENTRIES);
-			float prevX = frames[frame + PREV_X];
-			float prevY = frames[frame + PREV_Y];
-			float frameTime = frames[frame];
-			float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+			float x, y;
+			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
+				x = frames[frames.Length + PREV_X] * bone.data.scaleX;
+				y = frames[frames.Length + PREV_Y] * bone.data.scaleY;
+			} else {
+				// Interpolate between the previous frame and the current frame.
+				int frame = Animation.BinarySearch(frames, time, ENTRIES);
+				x = frames[frame + PREV_X];
+				y = frames[frame + PREV_Y];
+				float frameTime = frames[frame];
+				float percent = GetCurvePercent(frame / ENTRIES - 1,
+					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
 
 
-			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;
+				x = (x + (frames[frame + X] - x) * percent) * bone.data.scaleX;
+				y = (y + (frames[frame + Y] - y) * percent) * bone.data.scaleY;
+			}
+			if (alpha == 1) {
+				bone.scaleX = x;
+				bone.scaleY = y;
+			} else {
+				float bx, by;
+				if (setupPose) {
+					bx = bone.data.scaleX;
+					by = bone.data.scaleY;
+				} else {
+					bx = bone.scaleX;
+					by = bone.scaleY;
+				}
+				// Mixing out uses sign of setup or current pose, else use sign of key.
+				if (mixingOut) {
+					x = Math.Abs(x) * Math.Sign(bx);
+					y = Math.Abs(y) * Math.Sign(by);
+				} else {
+					bx = Math.Abs(bx) * Math.Sign(x);
+					by = Math.Abs(by) * Math.Sign(y);
+				}
+				bone.scaleX = bx + (x - bx) * alpha;
+				bone.scaleY = by + (y - by) * alpha;
+			}
 		}
 		}
 	}
 	}
 
 
 	public class ShearTimeline : TranslateTimeline {
 	public class ShearTimeline : TranslateTimeline {
+		override public int PropertyId {
+			get { return ((int)TimelineType.Shear << 24) + boneIndex; }
+		}
+
 		public ShearTimeline (int frameCount)
 		public ShearTimeline (int frameCount)
 			: base(frameCount) {
 			: base(frameCount) {
 		}
 		}
 
 
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
+		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, bool setupPose, bool mixingOut) {
 			float[] frames = this.frames;
 			float[] frames = this.frames;
 			if (time < frames[0]) return; // Time is before first frame.
 			if (time < frames[0]) return; // Time is before first frame.
 
 
 			Bone bone = skeleton.bones.Items[boneIndex];
 			Bone bone = skeleton.bones.Items[boneIndex];
+			float x, y;
 			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
 			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, ENTRIES);
-			float prevX = frames[frame + PREV_X];
-			float prevY = frames[frame + PREV_Y];
-			float frameTime = frames[frame];
-			float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+				x = frames[frames.Length + PREV_X];
+				y = frames[frames.Length + PREV_Y];
+			} else {
+				// Interpolate between the previous frame and the current frame.
+				int frame = Animation.BinarySearch(frames, time, ENTRIES);
+				x = frames[frame + PREV_X];
+				y = frames[frame + PREV_Y];
+				float frameTime = frames[frame];
+				float percent = GetCurvePercent(frame / ENTRIES - 1,
+					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
 
 
-			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;
+				x = x + (frames[frame + X] - x) * percent;
+				y = y + (frames[frame + Y] - y) * percent;
+			}
+			if (setupPose) {
+				bone.shearX = bone.data.shearX + x * alpha;
+				bone.shearY = bone.data.shearY + y * alpha;
+			} else {
+				bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha;
+				bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha;
+			}
 		}
 		}
 	}
 	}
 
 
@@ -383,6 +445,10 @@ namespace Spine {
 		public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } }
 		public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } }
 		public float[] Frames { get { return frames; } set { frames = value; } } // time, r, g, b, a, ...
 		public float[] Frames { get { return frames; } set { frames = value; } } // time, r, g, b, a, ...
 
 
+		override public int PropertyId {
+			get { return ((int)TimelineType.Color << 24) + slotIndex; }
+		}
+
 		public ColorTimeline (int frameCount)
 		public ColorTimeline (int frameCount)
 			: base(frameCount) {
 			: base(frameCount) {
 			frames = new float[frameCount * ENTRIES];
 			frames = new float[frameCount * ENTRIES];
@@ -398,7 +464,7 @@ namespace Spine {
 			frames[frameIndex + A] = a;
 			frames[frameIndex + A] = a;
 		}
 		}
 
 
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
+		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, bool setupPose, bool mixingOut) {
 			float[] frames = this.frames;
 			float[] frames = this.frames;
 			if (time < frames[0]) return; // Time is before first frame.
 			if (time < frames[0]) return; // Time is before first frame.
 
 
@@ -411,7 +477,7 @@ namespace Spine {
 				a = frames[i + PREV_A];
 				a = frames[i + PREV_A];
 			} else {
 			} else {
 				// Interpolate between the previous frame and the current frame.
 				// Interpolate between the previous frame and the current frame.
-				int frame = Animation.binarySearch(frames, time, ENTRIES);
+				int frame = Animation.BinarySearch(frames, time, ENTRIES);
 				r = frames[frame + PREV_R];
 				r = frames[frame + PREV_R];
 				g = frames[frame + PREV_G];
 				g = frames[frame + PREV_G];
 				b = frames[frame + PREV_B];
 				b = frames[frame + PREV_B];
@@ -426,16 +492,28 @@ namespace Spine {
 				a += (frames[frame + A] - a) * percent;
 				a += (frames[frame + A] - a) * percent;
 			}
 			}
 			Slot slot = skeleton.slots.Items[slotIndex];
 			Slot slot = skeleton.slots.Items[slotIndex];
-			if (alpha < 1) {
-				slot.r += (r - slot.r) * alpha;
-				slot.g += (g - slot.g) * alpha;
-				slot.b += (b - slot.b) * alpha;
-				slot.a += (a - slot.a) * alpha;
-			} else {
+			if (alpha == 1) {
 				slot.r = r;
 				slot.r = r;
 				slot.g = g;
 				slot.g = g;
 				slot.b = b;
 				slot.b = b;
 				slot.a = a;
 				slot.a = a;
+			} else {
+				float br, bg, bb, ba;
+				if (setupPose) {
+					br = slot.data.r;
+					bg = slot.data.g;
+					bb = slot.data.b;
+					ba = slot.data.a;
+				} else {
+					br = slot.r;
+					bg = slot.g;
+					bb = slot.b;
+					ba = slot.a;
+				}
+				slot.r = br + ((r - br) * alpha);
+				slot.g = bg + ((g - bg) * alpha);
+				slot.b = bb + ((b - bb) * alpha);
+				slot.a = ba + ((a - ba) * alpha);
 			}
 			}
 		}
 		}
 	}
 	}
@@ -450,6 +528,10 @@ namespace Spine {
 		public String[] AttachmentNames { get { return attachmentNames; } set { attachmentNames = value; } }
 		public String[] AttachmentNames { get { return attachmentNames; } set { attachmentNames = value; } }
 		public int FrameCount { get { return frames.Length; } }
 		public int FrameCount { get { return frames.Length; } }
 
 
+		public int PropertyId {
+			get { return ((int)TimelineType.Attachment << 24) + slotIndex; }
+		}
+
 		public AttachmentTimeline (int frameCount) {
 		public AttachmentTimeline (int frameCount) {
 			frames = new float[frameCount];
 			frames = new float[frameCount];
 			attachmentNames = new String[frameCount];
 			attachmentNames = new String[frameCount];
@@ -461,7 +543,15 @@ namespace Spine {
 			attachmentNames[frameIndex] = attachmentName;
 			attachmentNames[frameIndex] = attachmentName;
 		}
 		}
 
 
-		public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
+		public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, bool setupPose, bool mixingOut) {
+			string attachmentName;
+			if (mixingOut && setupPose) {
+				Slot slot = skeleton.slots.Items[slotIndex];
+				attachmentName = slot.data.attachmentName;
+				slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName);
+				return;
+			}
+
 			float[] frames = this.frames;
 			float[] frames = this.frames;
 			if (time < frames[0]) return; // Time is before first frame.
 			if (time < frames[0]) return; // Time is before first frame.
 
 
@@ -469,14 +559,125 @@ namespace Spine {
 			if (time >= frames[frames.Length - 1]) // Time is after last frame.
 			if (time >= frames[frames.Length - 1]) // Time is after last frame.
 				frameIndex = frames.Length - 1;
 				frameIndex = frames.Length - 1;
 			else
 			else
-				frameIndex = Animation.binarySearch(frames, time, 1) - 1;
+				frameIndex = Animation.BinarySearch(frames, time, 1) - 1;
 
 
-			String attachmentName = attachmentNames[frameIndex];
+			attachmentName = attachmentNames[frameIndex];
 			skeleton.slots.Items[slotIndex]
 			skeleton.slots.Items[slotIndex]
 				.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName);
 				.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName);
 		}
 		}
 	}
 	}
 
 
+	public class DeformTimeline : CurveTimeline {
+		internal int slotIndex;
+		internal float[] frames;
+		internal float[][] frameVertices;
+		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 VertexAttachment Attachment { get { return attachment; } set { attachment = value; } }
+
+		override public int PropertyId {
+			get { return ((int)TimelineType.Deform << 24) + slotIndex; }
+		}
+
+		public DeformTimeline (int frameCount)
+			: base(frameCount) {
+			frames = new float[frameCount];
+			frameVertices = new float[frameCount][];
+		}
+
+		/// <summary>Sets the time and value of the specified keyframe.</summary>
+		public void SetFrame (int frameIndex, float time, float[] vertices) {
+			frames[frameIndex] = time;
+			frameVertices[frameIndex] = vertices;
+		}
+
+		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, bool setupPose, bool mixingOut) {
+			Slot slot = skeleton.slots.Items[slotIndex];
+			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.
+
+			float[][] frameVertices = this.frameVertices;
+			int vertexCount = frameVertices[0].Length;
+
+			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];
+				if (alpha == 1) {
+					// Vertex positions or deform offsets, no alpha.
+					Array.Copy(lastVertices, 0, vertices, 0, vertexCount);
+				} else if (setupPose) {
+					VertexAttachment vertexAttachment = slotAttachment;
+					if (vertexAttachment.bones == null) {
+						// Unweighted vertex positions, with alpha.
+						float[] setupVertices = vertexAttachment.vertices;
+						for (int i = 0; i < vertexCount; i++) {
+							float setup = setupVertices[i];
+							vertices[i] = setup + (lastVertices[i] - setup) * alpha;
+						}
+					} else {
+						// Weighted deform offsets, with alpha.
+						for (int i = 0; i < vertexCount; i++)
+							vertices[i] = lastVertices[i] * alpha;
+					}
+				} else {
+					// Vertex positions or deform offsets, with alpha.
+					for (int i = 0; i < vertexCount; i++)
+						vertices[i] += (lastVertices[i] - vertices[i]) * alpha;
+				}
+				return;
+			}
+
+			// Interpolate between the previous frame and the current frame.
+			int frame = Animation.BinarySearch(frames, time);
+			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) {
+				// Vertex positions or deform offsets, no alpha.
+				for (int i = 0; i < vertexCount; i++) {
+					float prev = prevVertices[i];
+					vertices[i] = prev + (nextVertices[i] - prev) * percent;
+				}
+			} else if (setupPose) {
+				VertexAttachment vertexAttachment = (VertexAttachment)slotAttachment;
+				if (vertexAttachment.bones == null) {
+					// Unweighted vertex positions, with alpha.
+					var setupVertices = vertexAttachment.vertices;
+					for (int i = 0; i < vertexCount; i++) {
+						float prev = prevVertices[i], setup = setupVertices[i];
+						vertices[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha;
+					}
+				} else {
+					// Weighted deform offsets, with alpha.
+					for (int i = 0; i < vertexCount; i++) {
+						float prev = prevVertices[i];
+						vertices[i] = (prev + (nextVertices[i] - prev) * percent) * alpha;
+					}
+				}
+			} else {
+				// Vertex positions or deform offsets, with alpha.
+				for (int i = 0; i < vertexCount; i++) {
+					float prev = prevVertices[i];
+					vertices[i] += (prev + (nextVertices[i] - prev) * percent - vertices[i]) * alpha;
+				}
+			}
+		}
+	}
+
 	public class EventTimeline : Timeline {
 	public class EventTimeline : Timeline {
 		internal float[] frames;
 		internal float[] frames;
 		private Event[] events;
 		private Event[] events;
@@ -485,6 +686,10 @@ namespace Spine {
 		public Event[] Events { get { return events; } set { events = value; } }
 		public Event[] Events { get { return events; } set { events = value; } }
 		public int FrameCount { get { return frames.Length; } }
 		public int FrameCount { get { return frames.Length; } }
 
 
+		public int PropertyId {
+			get { return ((int)TimelineType.Event << 24); }
+		}
+
 		public EventTimeline (int frameCount) {
 		public EventTimeline (int frameCount) {
 			frames = new float[frameCount];
 			frames = new float[frameCount];
 			events = new Event[frameCount];
 			events = new Event[frameCount];
@@ -497,13 +702,13 @@ namespace Spine {
 		}
 		}
 
 
 		/// <summary>Fires events for frames &gt; lastTime and &lt;= 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) {
+		public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, bool setupPose, bool mixingOut) {
 			if (firedEvents == null) return;
 			if (firedEvents == null) return;
 			float[] frames = this.frames;
 			float[] frames = this.frames;
 			int frameCount = frames.Length;
 			int frameCount = frames.Length;
 
 
 			if (lastTime > time) { // Fire events after last time for looped animations.
 			if (lastTime > time) { // Fire events after last time for looped animations.
-				Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha);
+				Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha, setupPose, mixingOut);
 				lastTime = -1f;
 				lastTime = -1f;
 			} else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame.
 			} else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame.
 				return;
 				return;
@@ -513,7 +718,7 @@ namespace Spine {
 			if (lastTime < frames[0])
 			if (lastTime < frames[0])
 				frame = 0;
 				frame = 0;
 			else {
 			else {
-				frame = Animation.binarySearch(frames, lastTime);
+				frame = Animation.BinarySearch(frames, lastTime);
 				float frameTime = frames[frame];
 				float frameTime = frames[frame];
 				while (frame > 0) { // Fire multiple events with the same frame.
 				while (frame > 0) { // Fire multiple events with the same frame.
 					if (frames[frame - 1] != frameTime) break;
 					if (frames[frame - 1] != frameTime) break;
@@ -533,6 +738,10 @@ namespace Spine {
 		public int[][] DrawOrders { get { return drawOrders; } set { drawOrders = value; } }
 		public int[][] DrawOrders { get { return drawOrders; } set { drawOrders = value; } }
 		public int FrameCount { get { return frames.Length; } }
 		public int FrameCount { get { return frames.Length; } }
 
 
+		public int PropertyId {
+			get { return ((int)TimelineType.DrawOrder << 24); }
+		}
+
 		public DrawOrderTimeline (int frameCount) {
 		public DrawOrderTimeline (int frameCount) {
 			frames = new float[frameCount];
 			frames = new float[frameCount];
 			drawOrders = new int[frameCount][];
 			drawOrders = new int[frameCount][];
@@ -545,7 +754,12 @@ namespace Spine {
 			drawOrders[frameIndex] = drawOrder;
 			drawOrders[frameIndex] = drawOrder;
 		}
 		}
 
 
-		public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
+		public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, bool setupPose, bool mixingOut) {
+			if (mixingOut && setupPose) {
+				Array.Copy(skeleton.slots.Items, 0, skeleton.drawOrder.Items, 0, skeleton.slots.Count);
+				return;
+			}
+
 			float[] frames = this.frames;
 			float[] frames = this.frames;
 			if (time < frames[0]) return; // Time is before first frame.
 			if (time < frames[0]) return; // Time is before first frame.
 
 
@@ -553,7 +767,7 @@ namespace Spine {
 			if (time >= frames[frames.Length - 1]) // Time is after last frame.
 			if (time >= frames[frames.Length - 1]) // Time is after last frame.
 				frame = frames.Length - 1;
 				frame = frames.Length - 1;
 			else
 			else
-				frame = Animation.binarySearch(frames, time) - 1;
+				frame = Animation.BinarySearch(frames, time) - 1;
 
 
 			ExposedList<Slot> drawOrder = skeleton.drawOrder;
 			ExposedList<Slot> drawOrder = skeleton.drawOrder;
 			ExposedList<Slot> slots = skeleton.slots;
 			ExposedList<Slot> slots = skeleton.slots;
@@ -571,81 +785,6 @@ namespace Spine {
 		}
 		}
 	}
 	}
 
 
-	public class DeformTimeline : CurveTimeline {
-		internal int slotIndex;
-		internal float[] frames;
-		private float[][] frameVertices;
-		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 VertexAttachment Attachment { get { return attachment; } set { attachment = value; } }
-
-		public DeformTimeline (int frameCount)
-			: base(frameCount) {
-			frames = new float[frameCount];
-			frameVertices = new float[frameCount][];
-		}
-
-		/// <summary>Sets the time and value of the specified keyframe.</summary>
-		public void SetFrame (int frameIndex, float time, float[] vertices) {
-			frames[frameIndex] = time;
-			frameVertices[frameIndex] = vertices;
-		}
-
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
-			Slot slot = skeleton.slots.Items[slotIndex];
-			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.
-
-			float[][] frameVertices = this.frameVertices;
-			int vertexCount = frameVertices[0].Length;
-
-			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];
-				if (alpha < 1) {
-					for (int i = 0; i < vertexCount; i++) {
-						float vertex = vertices[i];
-						vertices[i] = vertex + (lastVertices[i] - vertex) * alpha;
-					}
-				} else
-					Array.Copy(lastVertices, 0, vertices, 0, vertexCount);
-				return;
-			}
-
-			// Interpolate between the previous frame and the current frame.
-			int frame = Animation.binarySearch(frames, time);
-			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++) {
-					float prev = prevVertices[i];
-					float vertex = vertices[i];
-					vertices[i] = vertex + (prev + (nextVertices[i] - prev) * percent - vertex) * alpha;
-				}
-			} else {
-				for (int i = 0; i < vertexCount; i++) {
-					float prev = prevVertices[i];
-					vertices[i] = prev + (nextVertices[i] - prev) * percent;
-				}
-			}
-		}
-	}
-
 	public class IkConstraintTimeline : CurveTimeline {
 	public class IkConstraintTimeline : CurveTimeline {
 		public const int ENTRIES = 3;
 		public const int ENTRIES = 3;
 		private const int PREV_TIME = -3, PREV_MIX = -2, PREV_BEND_DIRECTION = -1;
 		private const int PREV_TIME = -3, PREV_MIX = -2, PREV_BEND_DIRECTION = -1;
@@ -657,11 +796,15 @@ namespace Spine {
 		public int IkConstraintIndex { get { return ikConstraintIndex; } set { ikConstraintIndex = value; } }
 		public int IkConstraintIndex { get { return ikConstraintIndex; } set { ikConstraintIndex = value; } }
 		public float[] Frames { get { return frames; } set { frames = value; } } // time, mix, bendDirection, ...
 		public float[] Frames { get { return frames; } set { frames = value; } } // time, mix, bendDirection, ...
 
 
+		override public int PropertyId {
+			get { return ((int)TimelineType.IkConstraint << 24) + ikConstraintIndex; }
+		}
+
 		public IkConstraintTimeline (int frameCount)
 		public IkConstraintTimeline (int frameCount)
 			: base(frameCount) {
 			: base(frameCount) {
 			frames = new float[frameCount * ENTRIES];
 			frames = new float[frameCount * ENTRIES];
 		}
 		}
-
+			
 		/// <summary>Sets the time, mix and bend direction of the specified keyframe.</summary>
 		/// <summary>Sets the time, mix and bend direction of the specified keyframe.</summary>
 		public void SetFrame (int frameIndex, float time, float mix, int bendDirection) {
 		public void SetFrame (int frameIndex, float time, float mix, int bendDirection) {
 			frameIndex *= ENTRIES;
 			frameIndex *= ENTRIES;
@@ -670,26 +813,37 @@ namespace Spine {
 			frames[frameIndex + BEND_DIRECTION] = bendDirection;
 			frames[frameIndex + BEND_DIRECTION] = bendDirection;
 		}
 		}
 
 
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
+		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, bool setupPose, bool mixingOut) {
 			float[] frames = this.frames;
 			float[] frames = this.frames;
 			if (time < frames[0]) return; // Time is before first frame.
 			if (time < frames[0]) return; // Time is before first frame.
 
 
 			IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex];
 			IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex];
 
 
 			if (time >= frames[frames.Length - ENTRIES]) { // 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];
+				if (setupPose) {
+					constraint.mix = constraint.data.mix + (frames[frames.Length + PREV_MIX] - constraint.data.mix) * alpha;
+					constraint.bendDirection = mixingOut ? constraint.data.bendDirection
+						: (int)frames[frames.Length + PREV_BEND_DIRECTION];
+				} else {
+					constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha;
+					if (!mixingOut) constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION];
+				}
 				return;
 				return;
 			}
 			}
 
 
 			// Interpolate between the previous frame and the current frame.
 			// Interpolate between the previous frame and the current frame.
-			int frame = Animation.binarySearch(frames, time, ENTRIES);
+			int frame = Animation.BinarySearch(frames, time, ENTRIES);
 			float mix = frames[frame + PREV_MIX];
 			float mix = frames[frame + PREV_MIX];
 			float frameTime = frames[frame];
 			float frameTime = frames[frame];
 			float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
 			float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
 
 
-			constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha;
-			constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION];
+			if (setupPose) {
+				constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha;
+				constraint.bendDirection = mixingOut ? constraint.data.bendDirection : (int)frames[frame + PREV_BEND_DIRECTION];
+			} else {
+				constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha;
+				if (!mixingOut) constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION];
+			}
 		}
 		}
 	}
 	}
 
 
@@ -704,6 +858,10 @@ namespace Spine {
 		public int TransformConstraintIndex { get { return transformConstraintIndex; } set { transformConstraintIndex = value; } }
 		public int TransformConstraintIndex { get { return transformConstraintIndex; } set { transformConstraintIndex = value; } }
 		public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, scale mix, shear mix, ...
 		public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, scale mix, shear mix, ...
 
 
+		override public int PropertyId {
+			get { return ((int)TimelineType.TransformConstraint << 24) + transformConstraintIndex; }
+		}
+
 		public TransformConstraintTimeline (int frameCount)
 		public TransformConstraintTimeline (int frameCount)
 			: base(frameCount) {
 			: base(frameCount) {
 			frames = new float[frameCount * ENTRIES];
 			frames = new float[frameCount * ENTRIES];
@@ -718,35 +876,47 @@ namespace Spine {
 			frames[frameIndex + SHEAR] = shearMix;
 			frames[frameIndex + SHEAR] = shearMix;
 		}
 		}
 
 
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
+		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, bool setupPose, bool mixingOut) {
 			float[] frames = this.frames;
 			float[] frames = this.frames;
 			if (time < frames[0]) return; // Time is before first frame.
 			if (time < frames[0]) return; // Time is before first frame.
 
 
 			TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex];
 			TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex];
 
 
+			float rotate, translate, scale, shear;
 			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
 			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
 				int i = frames.Length;
 				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));
+				rotate = frames[i + PREV_ROTATE];
+				translate = frames[i + PREV_TRANSLATE];
+				scale = frames[i + PREV_SCALE];
+				shear = frames[i + PREV_SHEAR];
+			} else {
+				// Interpolate between the previous frame and the current frame.
+				int frame = Animation.BinarySearch(frames, time, ENTRIES);
+				rotate = frames[frame + PREV_ROTATE];
+				translate = frames[frame + PREV_TRANSLATE];
+				scale = frames[frame + PREV_SCALE];
+				shear = frames[frame + PREV_SHEAR];
+				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;
+				rotate += (frames[frame + ROTATE] - rotate) * percent;
+				translate += (frames[frame + TRANSLATE] - translate) * percent;
+				scale += (frames[frame + SCALE] - scale) * percent;
+				shear += (frames[frame + SHEAR] - shear) * percent;
+			}
+			if (setupPose) {
+				TransformConstraintData data = constraint.data;
+				constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha;
+				constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha;
+				constraint.scaleMix = data.scaleMix + (scale - data.scaleMix) * alpha;
+				constraint.shearMix = data.shearMix + (shear - data.shearMix) * alpha;
+			} else {
+				constraint.rotateMix += (rotate - constraint.rotateMix) * alpha;
+				constraint.translateMix += (translate - constraint.translateMix) * alpha;
+				constraint.scaleMix += (scale - constraint.scaleMix) * alpha;
+				constraint.shearMix += (shear - constraint.shearMix) * alpha;
+			}
 		}
 		}
 	}
 	}
 
 
@@ -758,6 +928,10 @@ namespace Spine {
 		internal int pathConstraintIndex;
 		internal int pathConstraintIndex;
 		internal float[] frames;
 		internal float[] frames;
 
 
+		override public int PropertyId {
+			get { return ((int)TimelineType.PathConstraintPosition << 24) + pathConstraintIndex; }
+		}
+
 		public PathConstraintPositionTimeline (int frameCount)
 		public PathConstraintPositionTimeline (int frameCount)
 			: base(frameCount) {
 			: base(frameCount) {
 			frames = new float[frameCount * ENTRIES];
 			frames = new float[frameCount * ENTRIES];
@@ -773,52 +947,65 @@ namespace Spine {
 			frames[frameIndex + VALUE] = value;
 			frames[frameIndex + VALUE] = value;
 		}
 		}
 
 
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> events, float alpha) {
+		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, bool setupPose, bool mixingOut) {
 			float[] frames = this.frames;
 			float[] frames = this.frames;
 			if (time < frames[0]) return; // Time is before first frame.
 			if (time < frames[0]) return; // Time is before first frame.
 
 
 			PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
 			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));
+			float position;
+			if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame.
+				position = frames[frames.Length + PREV_VALUE];
+			else {
+				// Interpolate between the previous frame and the current frame.
+				int frame = Animation.BinarySearch(frames, time, ENTRIES);
+				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;
+				position += (frames[frame + VALUE] - position) * percent;
+			}
+			if (setupPose)
+				constraint.position = constraint.data.position + (position - constraint.data.position) * alpha;
+			else
+				constraint.position += (position - constraint.position) * alpha;
 		}
 		}
 	}
 	}
 
 
 	public class PathConstraintSpacingTimeline : PathConstraintPositionTimeline {
 	public class PathConstraintSpacingTimeline : PathConstraintPositionTimeline {
+		override public int PropertyId {
+			get { return ((int)TimelineType.PathConstraintSpacing << 24) + pathConstraintIndex; }
+		}
+
 		public PathConstraintSpacingTimeline (int frameCount)
 		public PathConstraintSpacingTimeline (int frameCount)
 			: base(frameCount) {
 			: base(frameCount) {
 		}
 		}
 
 
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> events, float alpha) {
+		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, bool setupPose, bool mixingOut) {
 			float[] frames = this.frames;
 			float[] frames = this.frames;
 			if (time < frames[0]) return; // Time is before first frame.
 			if (time < frames[0]) return; // Time is before first frame.
 
 
 			PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
 			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;
-			}
+			float spacing;
+			if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame.
+				spacing = frames[frames.Length + PREV_VALUE];
+			else {
+				// Interpolate between the previous frame and the current frame.
+				int frame = Animation.BinarySearch(frames, time, ENTRIES);
+				spacing = frames[frame + PREV_VALUE];
+				float frameTime = frames[frame];
+				float percent = GetCurvePercent(frame / ENTRIES - 1,
+					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
 
 
-			// 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));
+				spacing += (frames[frame + VALUE] - spacing) * percent;
+			}
 
 
-			constraint.spacing += (spacing + (frames[frame + VALUE] - spacing) * percent - constraint.spacing) * alpha;
+			if (setupPose)
+				constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha;
+			else
+				constraint.spacing += (spacing - constraint.spacing) * alpha;
 		}
 		}
 	}
 	}
 
 
@@ -833,12 +1020,16 @@ namespace Spine {
 		public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } }
 		public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } }
 		public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, ...
 		public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, ...
 
 
+		override public int PropertyId {
+			get { return ((int)TimelineType.PathConstraintMix << 24) + pathConstraintIndex; }
+		}
+
 		public PathConstraintMixTimeline (int frameCount)
 		public PathConstraintMixTimeline (int frameCount)
 			: base(frameCount) {
 			: base(frameCount) {
 			frames = new float[frameCount * ENTRIES];
 			frames = new float[frameCount * ENTRIES];
-		}
+		}			
 
 
-		/** Sets the time and mixes of the specified keyframe. */
+		/// <summary>Sets the time and mixes of the specified keyframe.</summary>
 		public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix) {
 		public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix) {
 			frameIndex *= ENTRIES;
 			frameIndex *= ENTRIES;
 			frames[frameIndex] = time;
 			frames[frameIndex] = time;
@@ -846,29 +1037,36 @@ namespace Spine {
 			frames[frameIndex + TRANSLATE] = translateMix;
 			frames[frameIndex + TRANSLATE] = translateMix;
 		}
 		}
 
 
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> events, float alpha) {
+		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, bool setupPose, bool mixingOut) {
 			float[] frames = this.frames;
 			float[] frames = this.frames;
 			if (time < frames[0]) return; // Time is before first frame.
 			if (time < frames[0]) return; // Time is before first frame.
 
 
 			PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
 			PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
 
 
+			float rotate, translate;
 			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
 			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;
-			}
+				rotate = frames[frames.Length + PREV_ROTATE];
+				translate = frames[frames.Length + PREV_TRANSLATE];
+			} else {
+				// Interpolate between the previous frame and the current frame.
+				int frame = Animation.BinarySearch(frames, time, ENTRIES);
+				rotate = frames[frame + PREV_ROTATE];
+				translate = frames[frame + PREV_TRANSLATE];
+				float frameTime = frames[frame];
+				float percent = GetCurvePercent(frame / ENTRIES - 1,
+					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
 
 
-			// Interpolate between the previous frame and the current frame.
-			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 = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+				rotate += (frames[frame + ROTATE] - rotate) * percent;
+				translate += (frames[frame + TRANSLATE] - translate) * percent;
+			}
 
 
-			constraint.rotateMix += (rotate + (frames[frame + ROTATE] - rotate) * percent - constraint.rotateMix) * alpha;
-			constraint.translateMix += (translate + (frames[frame + TRANSLATE] - translate) * percent - constraint.translateMix)
-				* alpha;
+			if (setupPose) {
+				constraint.rotateMix = constraint.data.rotateMix + (rotate - constraint.data.rotateMix) * alpha;
+				constraint.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha;
+			} else {
+				constraint.rotateMix += (rotate - constraint.rotateMix) * alpha;
+				constraint.translateMix += (translate - constraint.translateMix) * alpha;
+			}
 		}
 		}
 	}
 	}
 }
 }

+ 758 - 141
spine-csharp/src/AnimationState.cs

@@ -34,9 +34,14 @@ using System.Text;
 
 
 namespace Spine {
 namespace Spine {
 	public class AnimationState {
 	public class AnimationState {
+		private static Animation EmptyAnimation = new Animation("<empty>", new ExposedList<Timeline>(), 0);
+
 		private AnimationStateData data;
 		private AnimationStateData data;
-		private ExposedList<TrackEntry> tracks = new ExposedList<TrackEntry>();
-		private ExposedList<Event> events = new ExposedList<Event>();
+		private readonly ExposedList<TrackEntry> tracks = new ExposedList<TrackEntry>();
+		private readonly HashSet<int> propertyIDs = new HashSet<int>();
+		private readonly ExposedList<Event> events = new ExposedList<Event>();
+		private readonly EventQueue queue;
+		private bool animationsChanged;
 		private float timeScale = 1;
 		private float timeScale = 1;
 
 
 		public AnimationStateData Data { get { return data; } }
 		public AnimationStateData Data { get { return data; } }
@@ -44,169 +49,391 @@ namespace Spine {
 		public ExposedList<TrackEntry> Tracks { get { return tracks; } }
 		public ExposedList<TrackEntry> Tracks { get { return tracks; } }
 		public float TimeScale { get { return timeScale; } set { timeScale = value; } }
 		public float TimeScale { get { return timeScale; } set { timeScale = value; } }
 
 
-		public delegate void StartEndDelegate (AnimationState state, int trackIndex);
-		public event StartEndDelegate Start;
-		public event StartEndDelegate End;
-
-		public delegate void EventDelegate (AnimationState state, int trackIndex, Event e);
-		public event EventDelegate Event;
+		public delegate void TrackEntryDelegate (TrackEntry trackEntry);
+		public event TrackEntryDelegate Start, Interrupt, End, Dispose, Complete;
 
 
-		public delegate void CompleteDelegate (AnimationState state, int trackIndex, int loopCount);
-		public event CompleteDelegate Complete;
+		public delegate void TrackEntryEventDelegate (TrackEntry trackEntry, Event e);
+		public event TrackEntryEventDelegate Event;
 
 
 		public AnimationState (AnimationStateData data) {
 		public AnimationState (AnimationStateData data) {
 			if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
 			if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
 			this.data = data;
 			this.data = data;
+			this.queue = new EventQueue(this, HandleAnimationsChanged);
 		}
 		}
 
 
+		void HandleAnimationsChanged () {
+			this.animationsChanged = true;
+		}
+
+		/// <summary>
+		/// Increments the track entry times, setting queued animations as current if needed</summary>
+		/// <param name="delta">delta time</param>
 		public void Update (float delta) {
 		public void Update (float delta) {
 			delta *= timeScale;
 			delta *= timeScale;
-			for (int i = 0; i < tracks.Count; i++) {
-				TrackEntry current = tracks.Items[i];
+			var tracksItems = tracks.Items;
+			for (int i = 0, n = tracks.Count; i < n; i++) {
+				TrackEntry current = tracksItems[i];
 				if (current == null) continue;
 				if (current == null) continue;
 
 
-				float trackDelta = delta * current.timeScale;
-				float time = current.time + trackDelta;
-				float endTime = current.endTime;
+				current.animationLast = current.nextAnimationLast;
+				current.trackLast = current.nextTrackLast;
 
 
-				current.time = time;
-				if (current.previous != null) {
-					current.previous.time += trackDelta;
-					current.mixTime += trackDelta;
-				}
+				float currentDelta = delta * current.timeScale;
 
 
-				// Check if completed the animation or a loop iteration.
-				if (current.loop ? (current.lastTime % endTime > time % endTime) : (current.lastTime < endTime && time >= endTime)) {
-					int count = (int)(time / endTime);
-					current.OnComplete(this, i, count);
-					if (Complete != null) Complete(this, i, count);
+				if (current.delay > 0) {
+					current.delay -= currentDelta;
+					if (current.delay > 0) continue;
+					currentDelta = -current.delay;
+					current.delay = 0;
 				}
 				}
 
 
 				TrackEntry next = current.next;
 				TrackEntry next = current.next;
 				if (next != null) {
 				if (next != null) {
-					next.time = current.lastTime - next.delay;
-					if (next.time >= 0) SetCurrent(i, next);
+					// When the next entry's delay is passed, change to the next entry, preserving leftover time.
+					float nextTime = current.trackLast - next.delay;
+					if (nextTime >= 0) {
+						next.delay = 0;
+						next.trackTime = nextTime + (delta * next.timeScale);
+						current.trackTime += currentDelta;
+						SetCurrent(i, next);
+						while (next.mixingFrom != null) {
+							next.mixTime += currentDelta;
+							next = next.mixingFrom;
+						}
+						continue;
+					}
+					UpdateMixingFrom(current, delta, true);
 				} else {
 				} else {
-					// End non-looping animation when it reaches its end time and there is no next entry.
-					if (!current.loop && current.lastTime >= current.endTime) ClearTrack(i);
+					UpdateMixingFrom(current, delta, true);
+					// Clear the track when there is no next entry, the track end time is reached, and there is no mixingFrom.
+					if (current.trackLast >= current.trackEnd && current.mixingFrom == null) {
+						tracksItems[i] = null;
+						queue.End(current);
+						DisposeNext(current);
+						continue;
+					}
 				}
 				}
+
+				current.trackTime += currentDelta;
 			}
 			}
+
+			queue.Drain();
 		}
 		}
 
 
+		private void UpdateMixingFrom (TrackEntry entry, float delta, bool canEnd) {
+			TrackEntry from = entry.mixingFrom;
+			if (from == null) return;
+
+			if (canEnd && entry.mixTime >= entry.mixDuration && entry.mixTime > 0) {
+				queue.End(from);
+				TrackEntry newFrom = from.mixingFrom;
+				entry.mixingFrom = newFrom;
+				if (newFrom == null) return;
+				entry.mixTime = from.mixTime;
+				entry.mixDuration = from.mixDuration;
+				from = newFrom;
+			}
+
+			from.animationLast = from.nextAnimationLast;
+			from.trackLast = from.nextTrackLast;
+			float mixingFromDelta = delta * from.timeScale;
+			from.trackTime += mixingFromDelta;
+			entry.mixTime += mixingFromDelta;
+
+			UpdateMixingFrom(from, delta, canEnd && from.alpha == 1);
+		}
+			
+
+		/// <summary>
+		/// Poses the skeleton using the track entry animations. There are no side effects other than invoking listeners, so the 
+		/// animation state can be applied to multiple skeletons to pose them identically.</summary>
 		public void Apply (Skeleton skeleton) {
 		public void Apply (Skeleton skeleton) {
-			ExposedList<Event> events = this.events;
+			if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
+			if (animationsChanged) AnimationsChanged();
+
+			var events = this.events;
+
+			var tracksItems = tracks.Items;
+			for (int i = 0, m = tracks.Count; i < m; i++) {
+				TrackEntry current = tracksItems[i];
+				if (current == null || current.delay > 0) continue;
+
+				// Apply mixing from entries first.
+				float mix = current.alpha;
+				if (current.mixingFrom != null) mix *= ApplyMixingFrom(current, skeleton);
+
+				// Apply current entry.
+				float animationLast = current.animationLast, animationTime = current.AnimationTime;
+				int timelineCount = current.animation.timelines.Count;
+				var timelines = current.animation.timelines;
+				var timelinesItems = timelines.Items;
+				if (mix == 1) {
+					for (int ii = 0; ii < timelineCount; ii++)
+						timelinesItems[ii].Apply(skeleton, animationLast, animationTime, events, 1, true, false);
+				} else {
+					bool firstFrame = current.timelinesRotation.Count == 0;
+					if (firstFrame) current.timelinesRotation.EnsureCapacity(timelines.Count << 1);
+					var timelinesRotation = current.timelinesRotation.Items;
+
+					var timelinesFirstItems = current.timelinesFirst.Items;
+					for (int ii = 0; ii < timelineCount; ii++) {
+						Timeline timeline = timelinesItems[ii];
+						var rotateTimeline = timeline as RotateTimeline;
+						if (rotateTimeline != null) {
+							ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, timelinesFirstItems[ii], timelinesRotation, ii << 1,
+								firstFrame);
+						} else {
+							timeline.Apply(skeleton, animationLast, animationTime, events, mix, timelinesFirstItems[ii], false);
+						}
+					}
+				}
+				QueueEvents(current, animationTime);
+				current.nextAnimationLast = animationTime;
+				current.nextTrackLast = current.trackTime;
+			}
 
 
-			for (int i = 0; i < tracks.Count; i++) {
-				TrackEntry current = tracks.Items[i];
-				if (current == null) continue;
+			queue.Drain();
+		}
 
 
-				events.Clear();
+		private float ApplyMixingFrom (TrackEntry entry, Skeleton skeleton) {
+			TrackEntry from = entry.mixingFrom;
+			if (from.mixingFrom != null) ApplyMixingFrom(from, skeleton);
 
 
-				float time = current.time;
-				bool loop = current.loop;
-				if (!loop && time > current.endTime) time = current.endTime;
+			float mix;
+			if (entry.mixDuration == 0) // Single frame mix to undo mixingFrom changes.
+				mix = 1;
+			else {
+				mix = entry.mixTime / entry.mixDuration;
+				if (mix > 1) mix = 1;
+			}
 
 
-				TrackEntry previous = current.previous;
-				if (previous == null) {
-					if (current.mix == 1)
-						current.animation.Apply(skeleton, current.lastTime, time, loop, events);
-					else
-						current.animation.Mix(skeleton, current.lastTime, time, loop, events, current.mix);
+			var eventBuffer = mix < from.eventThreshold ? this.events : null;
+			bool attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold;
+			float animationLast = from.animationLast, animationTime = from.AnimationTime;
+			var timelines = from.animation.timelines;
+			var timelinesItems = timelines.Items;
+			int timelineCount = timelines.Count;
+			var timelinesFirst = from.timelinesFirst;
+			var timelinesFirstItems = timelinesFirst.Items;
+			float alpha = from.alpha * entry.mixAlpha * (1 - mix);
+
+			bool firstFrame = entry.timelinesRotation.Count == 0;
+			if (firstFrame) entry.timelinesRotation.Capacity = timelineCount << 1;
+			var timelinesRotation = entry.timelinesRotation.Items;
+
+			for (int i = 0; i < timelineCount; i++) {
+				Timeline timeline = timelinesItems[i];
+				bool setupPose = timelinesFirstItems[i];
+				var rotateTimeline = timeline as RotateTimeline;
+				if (rotateTimeline != null) {
+					ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, alpha, setupPose, timelinesRotation, i << 1, firstFrame);
 				} else {
 				} else {
-					float previousTime = previous.time;
-					if (!previous.loop && previousTime > previous.endTime) previousTime = previous.endTime;
-					previous.animation.Apply(skeleton, previous.lastTime, previousTime, previous.loop, null);
-					// Remove the line above, and uncomment the line below, to allow previous animations to fire events during mixing.
-					//previous.animation.Apply(skeleton, previous.lastTime, previousTime, previous.loop, events);
-					previous.lastTime = previousTime;
-
-					float alpha = current.mixTime / current.mixDuration * current.mix;
-					if (alpha >= 1) {
-						alpha = 1;
-						current.previous = null;
+					if (setupPose) {
+						if (!attachments && timeline is AttachmentTimeline) continue;
+						if (!drawOrder && timeline is DrawOrderTimeline) continue;
 					}
 					}
-					current.animation.Mix(skeleton, current.lastTime, time, loop, events, alpha);
+					timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, alpha, setupPose, true);
 				}
 				}
+			}
+
+			QueueEvents(entry, animationTime);
+			from.nextAnimationLast = animationTime;
+			from.nextTrackLast = from.trackTime;
+
+			return mix;
+		}
 
 
-				for (int ii = 0, nn = events.Count; ii < nn; ii++) {
-					Event e = events.Items[ii];
-					current.OnEvent(this, i, e);
-					if (Event != null) Event(this, i, e);
+		static private void ApplyRotateTimeline (RotateTimeline rotateTimeline, Skeleton skeleton, float time, float alpha, bool setupPose,
+			float[] timelinesRotation, int i, bool firstFrame) {
+			if (alpha == 1) {
+				rotateTimeline.Apply(skeleton, 0, time, null, 1, setupPose, false);
+				return;
+			}
+
+			float[] frames = rotateTimeline.frames;
+			if (time < frames[0]) return; // Time is before first frame.
+
+			Bone bone = skeleton.bones.Items[rotateTimeline.boneIndex];
+
+			float r2;
+			if (time >= frames[frames.Length - RotateTimeline.ENTRIES]) // Time is after last frame.
+				r2 = bone.data.rotation + frames[frames.Length + RotateTimeline.PREV_ROTATION];
+			else {
+				// Interpolate between the previous frame and the current frame.
+				int frame = Animation.BinarySearch(frames, time, RotateTimeline.ENTRIES);
+				float prevRotation = frames[frame + RotateTimeline.PREV_ROTATION];
+				float frameTime = frames[frame];
+				float percent = rotateTimeline.GetCurvePercent((frame >> 1) - 1,
+					1 - (time - frameTime) / (frames[frame + RotateTimeline.PREV_TIME] - frameTime));
+
+				r2 = frames[frame + RotateTimeline.ROTATION] - prevRotation;
+				r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360;
+				r2 = prevRotation + r2 * percent + bone.data.rotation;
+				r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360;
+			}
+
+			// Mix between rotations using the direction of the shortest route on the first frame while detecting crosses.
+			float r1 = setupPose ? bone.data.rotation : bone.rotation;
+			float total, diff = r2 - r1;
+			if (diff == 0) {
+				if (firstFrame) {
+					timelinesRotation[i] = 0;
+					total = 0;
+				} else
+					total = timelinesRotation[i];
+			} else {
+				diff -= (16384 - (int)(16384.499999999996 - diff / 360)) * 360;
+				float lastTotal, lastDiff;
+				if (firstFrame) {
+					lastTotal = 0;
+					lastDiff = diff;
+				} else {
+					lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops.
+					lastDiff = timelinesRotation[i + 1]; // Difference between bones.
 				}
 				}
+				bool current = diff > 0, dir = lastTotal >= 0;
+				// Detect cross at 0 (not 180).
+				if (Math.Sign(lastDiff) != Math.Sign(diff) && Math.Abs(lastDiff) <= 90) {
+					// A cross after a 360 rotation is a loop.
+					if (Math.Abs(lastTotal) > 180) lastTotal += 360 * Math.Sign(lastTotal);
+					dir = current;
+				}
+				total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal.
+				if (dir != current) total += 360 * Math.Sign(lastTotal);
+				timelinesRotation[i] = total;
+			}
+			timelinesRotation[i + 1] = diff;
+			r1 += total * alpha;
+			bone.rotation = r1 - (16384 - (int)(16384.499999999996 - r1 / 360)) * 360;
+		}
 
 
-				current.lastTime = current.time;
+		private void QueueEvents (TrackEntry entry, float animationTime) {
+			float animationStart = entry.animationStart, animationEnd = entry.animationEnd;
+			float duration = animationEnd - animationStart;
+			float trackLastWrapped = entry.trackLast % duration;
+
+			// Queue events before complete.
+			var events = this.events;
+			var eventsItems = events.Items;
+			int i = 0, n = events.Count;
+			for (; i < n; i++) {
+				var e = eventsItems[i];
+				if (e.time < trackLastWrapped) break;
+				if (e.time > animationEnd) continue; // Discard events outside animation start/end.
+				queue.Event(entry, e);
 			}
 			}
+
+			// Queue complete if completed a loop iteration or the animation.
+			if (entry.loop ? (trackLastWrapped > entry.trackTime % duration)
+				: (animationTime >= animationEnd && entry.animationLast < animationEnd)) {
+				queue.Complete(entry);
+			}
+
+			// Queue events after complete.
+			for (; i < n; i++) {
+				Event e = eventsItems[i];
+				if (e.time < animationStart) continue; // Discard events outside animation start/end.
+				queue.Event(entry, eventsItems[i]);
+			}
+			events.Clear(false);
 		}
 		}
 
 
+		/// <summary>
+		/// Removes all animations from all tracks, leaving skeletons in their previous pose. 
+		/// It may be desired to use <see cref="AnimationState.SetEmptyAnimations(float)"/> to mix the skeletons back to the setup pose, 
+		/// rather than leaving them in their previous pose.</summary>
 		public void ClearTracks () {
 		public void ClearTracks () {
-			for (int i = 0, n = tracks.Count; i < n; i++)
+			queue.drainDisabled = true;
+			for (int i = 0, n = tracks.Count; i < n; i++) {
 				ClearTrack(i);
 				ClearTrack(i);
+			}
 			tracks.Clear();
 			tracks.Clear();
+			queue.drainDisabled = false;
+			queue.Drain();
 		}
 		}
 
 
+		/// <summary>
+		/// Removes all animations from the tracks, leaving skeletons in their previous pose. 
+		/// It may be desired to use <see cref="AnimationState.SetEmptyAnimations(float)"/> to mix the skeletons back to the setup pose, 
+		/// rather than leaving them in their previous pose.</summary>
 		public void ClearTrack (int trackIndex) {
 		public void ClearTrack (int trackIndex) {
 			if (trackIndex >= tracks.Count) return;
 			if (trackIndex >= tracks.Count) return;
 			TrackEntry current = tracks.Items[trackIndex];
 			TrackEntry current = tracks.Items[trackIndex];
 			if (current == null) return;
 			if (current == null) return;
 
 
-			current.OnEnd(this, trackIndex);
-			if (End != null) End(this, trackIndex);
+			queue.End(current);
 
 
-			tracks.Items[trackIndex] = null;
-		}
+			DisposeNext(current);
 
 
-		private TrackEntry ExpandToIndex (int index) {
-			if (index < tracks.Count) return tracks.Items[index];
-			while (index >= tracks.Count)
-				tracks.Add(null);
-			return null;
+			TrackEntry entry = current;
+			while (true) {
+				TrackEntry from = entry.mixingFrom;
+				if (from == null) break;
+				queue.End(from);
+				entry.mixingFrom = null;
+				entry = from;
+			}
+
+			tracks.Items[current.trackIndex] = null;
+
+			queue.Drain();
 		}
 		}
 
 
-		private void SetCurrent (int index, TrackEntry entry) {
-			TrackEntry current = ExpandToIndex(index);
-			if (current != null) {
-				TrackEntry previous = current.previous;
-				current.previous = null;
-
-				current.OnEnd(this, index);
-				if (End != null) End(this, index);
-
-				entry.mixDuration = data.GetMix(current.animation, entry.animation);
-				if (entry.mixDuration > 0) {
-					entry.mixTime = 0;
-					// If a mix is in progress, mix from the closest animation.
-					if (previous != null && current.mixTime / current.mixDuration < 0.5f)
-						entry.previous = previous;
-					else
-						entry.previous = current;
-				}
-			}
+		private void SetCurrent (int index, TrackEntry current) {
+			TrackEntry from = ExpandToIndex(index);
+			tracks.Items[index] = current;
 
 
-			tracks.Items[index] = entry;
+			if (from != null) {
+				queue.Interrupt(from);
+				current.mixingFrom = from;
+				current.mixTime = 0;
 
 
-			entry.OnStart(this, index);
-			if (Start != null) Start(this, index);
+				from.timelinesRotation.Clear();
+
+				// If not completely mixed in, set mixAlpha so mixing out happens from current mix to zero.
+				if (from.mixingFrom != null) from.mixAlpha *= Math.Min(from.mixTime / from.mixDuration, 1);
+			}
+
+			queue.Start(current);
 		}
 		}
 
 
-		/// <seealso cref="SetAnimation(int, Animation, bool)" />
+
+		/// <summary>Sets an animation by name. <seealso cref="SetAnimation(int, Animation, bool)" /></summary>
 		public TrackEntry SetAnimation (int trackIndex, String animationName, bool loop) {
 		public TrackEntry SetAnimation (int trackIndex, String animationName, bool loop) {
 			Animation animation = data.skeletonData.FindAnimation(animationName);
 			Animation animation = data.skeletonData.FindAnimation(animationName);
 			if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName");
 			if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName");
 			return SetAnimation(trackIndex, animation, loop);
 			return SetAnimation(trackIndex, animation, loop);
 		}
 		}
 
 
-		/// <summary>Set the current animation. Any queued animations are cleared.</summary>
+		/// <summary>Sets the current animation for a track, discarding any queued animations.</summary>
+		/// <param name="loop">If true, the animation will repeat.
+		/// If false, it will not, instead its last frame is applied if played beyond its duration.
+		/// In either case <see cref="TrackEntry.TrackEnd"/> determines when the track is cleared. </param>
+		/// <returns>
+		/// A track entry to allow further customization of animation playback. References to the track entry must not be kept 
+		/// after <see cref="AnimationState.Dispose"/>.</returns>
 		public TrackEntry SetAnimation (int trackIndex, Animation animation, bool loop) {
 		public TrackEntry SetAnimation (int trackIndex, Animation animation, bool loop) {
 			if (animation == null) throw new ArgumentNullException("animation", "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;
-			entry.time = 0;
-			entry.endTime = animation.Duration;
+			TrackEntry current = ExpandToIndex(trackIndex);
+			if (current != null) {
+				if (current.nextTrackLast == -1) {
+					// Don't mix from an entry that was never applied.
+					tracks.Items[trackIndex] = null;
+					queue.Interrupt(current);
+					queue.End(current);
+					DisposeNext(current);
+					current = null;
+				} else {
+					DisposeNext(current);
+				}
+			}
+			TrackEntry entry = NewTrackEntry(trackIndex, animation, loop, current);
 			SetCurrent(trackIndex, entry);
 			SetCurrent(trackIndex, entry);
+			queue.Drain();
 			return entry;
 			return entry;
 		}
 		}
 
 
+		/// <summary>Queues an animation by name.</summary>
 		/// <seealso cref="AddAnimation(int, Animation, bool, float)" />
 		/// <seealso cref="AddAnimation(int, Animation, bool, float)" />
 		public TrackEntry AddAnimation (int trackIndex, String animationName, bool loop, float delay) {
 		public TrackEntry AddAnimation (int trackIndex, String animationName, bool loop, float delay) {
 			Animation animation = data.skeletonData.FindAnimation(animationName);
 			Animation animation = data.skeletonData.FindAnimation(animationName);
@@ -214,93 +441,483 @@ namespace Spine {
 			return AddAnimation(trackIndex, animation, loop, delay);
 			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 &lt;= 0 to use duration of previous animation minus any mix duration plus the negative delay.</param>
+		/// <summary>Adds an animation to be played delay seconds after the current or last queued animation
+		/// for a track. If the track is empty, it is equivalent to calling <see cref="SetAnimation"/>.</summary>
+		/// <param name="delay">
+		/// Seconds to begin this animation after the start of the previous animation. May be &lt;= 0 to use the animation
+		/// duration of the previous track minus any mix duration plus the negative delay.
+		/// </param>
+		/// <returns>A track entry to allow further customization of animation playback. References to the track entry must not be kept 
+		/// after <see cref="AnimationState.Dispose"/></returns>
 		public TrackEntry AddAnimation (int trackIndex, Animation animation, bool loop, float delay) {
 		public TrackEntry AddAnimation (int trackIndex, Animation animation, bool loop, float delay) {
 			if (animation == null) throw new ArgumentNullException("animation", "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;
-			entry.time = 0;
-			entry.endTime = animation.Duration;
 
 
 			TrackEntry last = ExpandToIndex(trackIndex);
 			TrackEntry last = ExpandToIndex(trackIndex);
 			if (last != null) {
 			if (last != null) {
 				while (last.next != null)
 				while (last.next != null)
 					last = last.next;
 					last = last.next;
+			}
+
+			TrackEntry entry = NewTrackEntry(trackIndex, animation, loop, last);
+
+			if (last == null) {
+				SetCurrent(trackIndex, entry);
+				queue.Drain();
+			} else {
 				last.next = entry;
 				last.next = entry;
-			} else
-				tracks.Items[trackIndex] = entry;
-
-			if (delay <= 0) {
-				if (last != null)
-					delay += last.endTime - data.GetMix(last.animation, animation);
-				else
-					delay = 0;
+				if (delay <= 0) {
+					float duration = last.animationEnd - last.animationStart;
+					if (duration != 0)
+						delay += duration * (1 + (int)(last.trackTime / duration)) - data.GetMix(last.animation, animation);
+					else
+						delay = 0;
+				}
 			}
 			}
+
 			entry.delay = delay;
 			entry.delay = delay;
+			return entry;
+		}
 
 
+		/// <summary>
+		/// Sets an empty animation for a track, discarding any queued animations, and mixes to it over the specified mix duration.</summary>
+		public TrackEntry SetEmptyAnimation (int trackIndex, float mixDuration) {
+			TrackEntry entry = SetAnimation(trackIndex, AnimationState.EmptyAnimation, false);
+			entry.mixDuration = mixDuration;
+			entry.trackEnd = mixDuration;
 			return entry;
 			return entry;
 		}
 		}
 
 
-		/// <returns>May be null.</returns>
+		/// <summary>
+		/// Adds an empty animation to be played after the current or last queued animation for a track, and mixes to it over the 
+		/// specified mix duration.</summary>
+		/// <returns>
+		/// A track entry to allow further customization of animation playback. References to the track entry must not be kept after <see cref="AnimationState.Dispose"/>.
+		/// </returns>
+		/// <param name="trackIndex">Track number.</param>
+		/// <param name="mixDuration">Mix duration.</param>
+		/// <param name="delay">Seconds to begin this animation after the start of the previous animation. May be &lt;= 0 to use the animation 
+		/// duration of the previous track minus any mix duration plus the negative delay.</param>
+		public TrackEntry AddEmptyAnimation (int trackIndex, float mixDuration, float delay) {
+			if (delay <= 0) delay -= mixDuration;
+			TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay);
+			entry.mixDuration = mixDuration;
+			entry.trackEnd = mixDuration;
+			return entry;
+		}
+			
+		/// <summary>
+		/// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix duration.</summary>
+		public void SetEmptyAnimations (float mixDuration) {
+			queue.drainDisabled = true;
+			for (int i = 0, n = tracks.Count; i < n; i++) {
+				TrackEntry current = tracks.Items[i];
+				if (current != null) SetEmptyAnimation(i, mixDuration);
+			}
+			queue.drainDisabled = false;
+			queue.Drain();
+		}
+
+		private TrackEntry ExpandToIndex (int index) {
+			if (index < tracks.Count) return tracks.Items[index];
+			while (index >= tracks.Count)
+				tracks.Add(null);			
+			return null;
+		}
+
+		/// <param name="last">May be null.</param>
+		private TrackEntry NewTrackEntry (int trackIndex, Animation animation, bool loop, TrackEntry last) {
+			return new TrackEntry {
+				trackIndex = trackIndex,
+				animation = animation,
+				loop = loop,
+
+				eventThreshold = 0,
+				attachmentThreshold = 0,
+				drawOrderThreshold = 0,
+
+				animationStart = 0,
+				animationEnd = animation.duration,
+				animationLast = -1,
+				nextAnimationLast = -1,
+
+				delay = 0,
+				trackTime = 0,
+				trackLast = -1,
+				nextTrackLast = -1,
+				trackEnd = loop ? int.MaxValue : animation.duration,
+				timeScale = 1,
+
+				alpha = 1,
+				mixAlpha = 1,
+				mixTime = 0,
+				mixDuration = (last == null) ? 0 : data.GetMix(last.animation, animation),
+			};
+		}
+
+		private void DisposeNext (TrackEntry entry) {
+			TrackEntry next = entry.next;
+			while (next != null) {
+				queue.Dispose(next);
+				next = next.next;
+			}
+			entry.next = null;
+		}
+
+		private void AnimationsChanged () {
+			animationsChanged = false;
+
+			var propertyIDs = this.propertyIDs;
+
+			// Set timelinesFirst for all entries, from lowest track to highest.
+			int i = 0, n = tracks.Count;
+			propertyIDs.Clear();
+			for (; i < n; i++) { // Find first non-null entry.
+				TrackEntry entry = tracks.Items[i];
+				if (entry == null) continue;
+				SetTimelinesFirst(entry);
+				i++;
+				break;
+			}
+			for (; i < n; i++) { // Rest of entries.
+				TrackEntry entry = tracks.Items[i];
+				if (entry != null) CheckTimelinesFirst(entry);
+			}
+		}
+
+		/// <summary>From last to first mixingFrom entries, sets timelinesFirst to true on last, calls checkTimelineUsage on rest.</summary>
+		private void SetTimelinesFirst (TrackEntry entry) {
+			if (entry.mixingFrom != null) {
+				SetTimelinesFirst(entry.mixingFrom);
+				CheckTimelinesUsage(entry, entry.timelinesFirst);
+				return;
+			}
+			var propertyIDs = this.propertyIDs;
+			var timelines = entry.animation.timelines;
+			int n = timelines.Count;
+			entry.timelinesFirst.EnsureCapacity(n); // entry.timelinesFirst.setSize(n);
+			var usage = entry.timelinesFirst.Items;
+			var timelinesItems = timelines.Items;
+			for (int i = 0; i < n; i++) {
+				propertyIDs.Add(timelinesItems[i].PropertyId);
+				usage[i] = true;
+			}
+		}
+
+		/// <summary>From last to first mixingFrom entries, calls checkTimelineUsage.</summary>
+		private void CheckTimelinesFirst (TrackEntry entry) {
+			if (entry.mixingFrom != null) CheckTimelinesFirst(entry.mixingFrom);
+			CheckTimelinesUsage(entry, entry.timelinesFirst);
+		}
+
+		private void CheckTimelinesUsage (TrackEntry entry, ExposedList<bool> usageArray) {
+			var propertyIDs = this.propertyIDs;
+			var timelines = entry.animation.timelines;
+			int n = timelines.Count;
+			usageArray.EnsureCapacity(n);
+			var usage = usageArray.Items;
+			var timelinesItems = timelines.Items;
+			for (int i = 0; i < n; i++)
+				usage[i] = propertyIDs.Add(timelinesItems[i].PropertyId);
+		}
+
+		/// <returns>The track entry for the animation currently playing on the track, or null if no animation is currently playing.</returns>
 		public TrackEntry GetCurrent (int trackIndex) {
 		public TrackEntry GetCurrent (int trackIndex) {
-			if (trackIndex >= tracks.Count) return null;
-			return tracks.Items[trackIndex];
+			return (trackIndex >= tracks.Count) ? null : tracks.Items[trackIndex];
 		}
 		}
 
 
 		override public String ToString () {
 		override public String ToString () {
-			StringBuilder buffer = new StringBuilder();
+			var buffer = new StringBuilder();
 			for (int i = 0, n = tracks.Count; i < n; i++) {
 			for (int i = 0, n = tracks.Count; i < n; i++) {
 				TrackEntry entry = tracks.Items[i];
 				TrackEntry entry = tracks.Items[i];
 				if (entry == null) continue;
 				if (entry == null) continue;
 				if (buffer.Length > 0) buffer.Append(", ");
 				if (buffer.Length > 0) buffer.Append(", ");
 				buffer.Append(entry.ToString());
 				buffer.Append(entry.ToString());
 			}
 			}
-			if (buffer.Length == 0) return "<none>";
-			return buffer.ToString();
+			return buffer.Length == 0 ? "<none>" : buffer.ToString();
 		}
 		}
+
+		internal void OnStart (TrackEntry entry) { if (Start != null) Start(entry); }
+		internal void OnInterrupt (TrackEntry entry) { if (Interrupt != null) Interrupt(entry); }
+		internal void OnEnd (TrackEntry entry) { if (End != null) End(entry); }
+		internal void OnDispose (TrackEntry entry) { if (Dispose != null) Dispose(entry); }
+		internal void OnComplete (TrackEntry entry) { if (Complete != null) Complete(entry); }
+		internal void OnEvent (TrackEntry entry, Event e) { if (Event != null) Event(entry, e); }
 	}
 	}
 
 
+	/// <summary>State for the playback of an animation.</summary>
 	public class TrackEntry {
 	public class TrackEntry {
-		internal TrackEntry next, previous;
 		internal Animation animation;
 		internal Animation animation;
+
+		internal TrackEntry next, mixingFrom;
+		internal int trackIndex;
+
 		internal bool loop;
 		internal bool loop;
-		internal float delay, time, lastTime = -1, endTime, timeScale = 1;
-		internal float mixTime, mixDuration, mix = 1;
+		internal float eventThreshold, attachmentThreshold, drawOrderThreshold;
+		internal float animationStart, animationEnd, animationLast, nextAnimationLast;
+		internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f;
+		internal float alpha, mixTime, mixDuration, mixAlpha;
+		internal readonly ExposedList<bool> timelinesFirst = new ExposedList<bool>(), timelinesLast = new ExposedList<bool>();
+		internal readonly ExposedList<float> timelinesRotation = new ExposedList<float>();
+
+		/// <summary>The index of the track where this entry is either current or queued.</summary>
+		public int TrackIndex { get { return trackIndex; } }
 
 
+		/// <summary>The animation to apply for this track entry.</summary>
 		public Animation Animation { get { return animation; } }
 		public Animation Animation { get { return animation; } }
-		public float Delay { get { return delay; } set { delay = value; } }
-		public float Time { get { return time; } set { time = value; } }
-		public float LastTime { get { return lastTime; } set { lastTime = value; } }
-		public float EndTime { get { return endTime; } set { endTime = value; } }
-		public float TimeScale { get { return timeScale; } set { timeScale = value; } }
-		public float Mix { get { return mix; } set { mix = value; } }
+
+		/// <summary>
+		/// If true, the animation will repeat. If false, it will not, instead its last frame is applied if played beyond its duration.</summary>
 		public bool Loop { get { return loop; } set { loop = value; } }
 		public bool Loop { get { return loop; } set { loop = value; } }
 
 
-		public event AnimationState.StartEndDelegate Start;
-		public event AnimationState.StartEndDelegate End;
-		public event AnimationState.EventDelegate Event;
-		public event AnimationState.CompleteDelegate Complete;
+		///<summary>
+		/// Seconds to postpone playing the animation. When a track entry is the current track entry, delay postpones incrementing 
+		/// the track time. When a track entry is queued, delay is the time from the start of the previous animation to when the 
+		/// track entry will become the current track entry.</summary>
+		public float Delay { get { return delay; } set { delay = value; } }
 
 
-		internal void OnStart (AnimationState state, int index) {
-			if (Start != null) Start(state, index);
+		/// <summary>
+		/// Current time in seconds this track entry has been the current track entry. The track time determines 
+		/// <see cref="TrackEntry.AnimationTime"/>. The track time can be set to start the animation at a time other than 0, without affecting looping.</summary>
+		public float TrackTime { get { return trackTime; } set { trackTime = value; } }
+
+		/// <summary>
+		/// The track time in seconds when this animation will be removed from the track. Defaults to the animation duration for 
+		/// non-looping animations and to <see cref="int.MaxValue"/> for looping animations. If the track end time is reached and no 
+		/// other animations are queued for playback, and mixing from any previous animations is complete, then the track is cleared, 
+		/// leaving skeletons in their previous pose.
+		/// 
+		/// It may be desired to use <see cref="AnimationState.AddEmptyAnimation(int, float, float)"/> to mix the skeletons back to the 
+		/// setup pose, rather than leaving them in their previous pose.
+		/// </summary>
+		public float TrackEnd { get { return trackEnd; } set { trackEnd = value; } }
+
+		/// <summary>
+		/// Seconds when this animation starts, both initially and after looping. Defaults to 0.
+		/// 
+		/// When changing the animation start time, it often makes sense to set <see cref="TrackEntry.AnimationLast"/> to the same value to 
+		/// prevent timeline keys before the start time from triggering.
+		/// </summary>
+		public float AnimationStart { get { return animationStart; } set { animationStart = value; } }
+
+		/// <summary>
+		/// Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will 
+		/// loop back to <see cref="TrackEntry.AnimationStart"/> at this time. Defaults to the animation duration.</summary>
+		public float AnimationEnd { get { return animationEnd; } }
+
+		/// <summary>
+		/// The time in seconds this animation was last applied. Some timelines use this for one-time triggers. Eg, when this
+		/// animation is applied, event timelines will fire all events between the animation last time (exclusive) and animation time 
+		/// (inclusive). Defaults to -1 to ensure triggers on frame 0 happen the first time this animation is applied.</summary>
+		public float AnimationLast {
+			get { return animationLast; }
+			set {
+				animationLast = value;
+				nextAnimationLast = value;
+			}
 		}
 		}
 
 
-		internal void OnEnd (AnimationState state, int index) {
-			if (End != null) End(state, index);
+		/// <summary>
+		/// Uses <see cref="TrackEntry.TrackTime"/> to compute the animation time between <see cref="TrackEntry.AnimationStart"/>. and
+		/// <see cref="TrackEntry.AnimationEnd"/>. When the track time is 0, the animation time is equal to the animation start time.
+		/// </summary>
+		public float AnimationTime {
+			get {
+				if (loop) {
+					float duration = animationEnd - animationStart;
+					if (duration == 0) return animationStart;
+					return (trackTime % duration) + animationStart;
+				}
+				return Math.Min(trackTime + animationStart, animationEnd);
+			}
 		}
 		}
 
 
-		internal void OnEvent (AnimationState state, int index, Event e) {
-			if (Event != null) Event(state, index, e);
+		/// <summary>
+		/// Multiplier for the delta time when the animation state is updated, causing time for this animation to play slower or 
+		/// faster. Defaults to 1.
+		/// </summary>
+		public float TimeScale { get { return timeScale; } set { timeScale = value; } }
+
+		/// <summary>
+		/// Values less than 1 mix this animation with the last skeleton pose. Defaults to 1, which overwrites the last skeleton pose with 
+		/// this animation.
+		/// 
+		/// Typically track 0 is used to completely pose the skeleton, then alpha can be used on higher tracks. It doesn't make sense 
+		/// to use alpha on track 0 if the skeleton pose is from the last frame render. 
+		/// </summary>
+		public float Alpha { get { return alpha; } set { alpha = value; } }
+
+		/// <summary>
+		/// When the mix percentage (mix time / mix duration) is less than the event threshold, event timelines for the animation 
+		/// being mixed out will be applied. Defaults to 0, so event timelines are not applied for an animation being mixed out.</summary>
+		public float EventThreshold { get { return eventThreshold; } set { eventThreshold = value; } }
+
+		/// <summary>
+		/// When the mix percentage (mix time / mix duration) is less than the attachment threshold, attachment timelines for the 
+		/// animation being mixed out will be applied. Defaults to 0, so attachment timelines are not applied for an animation being 
+		/// mixed out.</summary>
+		public float AttachmentThreshold { get { return attachmentThreshold; } set { attachmentThreshold = value; } }
+
+		/// <summary>
+		/// When the mix percentage (mix time / mix duration) is less than the draw order threshold, draw order timelines for the 
+		/// animation being mixed out will be applied. Defaults to 0, so draw order timelines are not applied for an animation being 
+		/// mixed out.
+		/// </summary>
+		public float DrawOrderThreshold { get { return drawOrderThreshold; } set { drawOrderThreshold = value; } }
+
+		/// <summary>
+		/// The animation queued to start after this animation, or null.</summary>
+		public TrackEntry Next { get { return next; } }
+
+		/// <summary>
+		/// Returns true if at least one loop has been completed.</summary>
+		public bool IsComplete {
+			get { return trackTime >= animationEnd - animationStart; }
 		}
 		}
 
 
-		internal void OnComplete (AnimationState state, int index, int loopCount) {
-			if (Complete != null) Complete(state, index, loopCount);
+		/// <summary>
+		/// Seconds from 0 to the mix duration when mixing from the previous animation to this animation. May be slightly more than 
+		/// <see cref="TrackEntry.MixDuration"/>.</summary>
+		public float MixTime { get { return mixTime; } set { mixTime = value; } }
+
+		/// <summary>
+		/// Seconds for mixing from the previous animation to this animation. Defaults to the value provided by 
+		/// <see cref="AnimationStateData"/> based on the animation before this animation (if any).
+		/// 
+		/// The mix duration must be set before <see cref="AnimationState.Update(float)"/> is next called.
+		/// </summary>
+		public float MixDuration { get { return mixDuration; } set { mixDuration = value; } }
+
+		/// <summary>
+		/// The track entry for the previous animation when mixing from the previous animation to this animation, or null if no 
+		/// mixing is currently occuring.</summary>
+		public TrackEntry MixingFrom { get { return mixingFrom; } }
+
+		public event AnimationState.TrackEntryDelegate Start, Interrupt, End, Dispose, Complete;
+		public event AnimationState.TrackEntryEventDelegate Event;
+		internal void OnStart () { if (Start != null) Start(this); }
+		internal void OnInterrupt () { if (Interrupt != null) Interrupt(this); }
+		internal void OnEnd () { if (End != null) End(this); }
+		internal void OnDispose () { if (Dispose != null) Dispose(this); }
+		internal void OnComplete () { if (Complete != null) Complete(this); }
+		internal void OnEvent (Event e) { if (Event != null) Event(this, e); }
+
+		/// <summary>
+		/// Resets the rotation directions for mixing this entry's rotate timelines. This can be useful to avoid bones rotating the 
+		/// long way around when using <see cref="alpha"/> and starting animations on other tracks. 
+		/// 
+		/// Mixing involves finding a rotation between two others, which has two possible solutions: the short way or the long way around. 
+		/// The two rotations likely change over time, so which direction is the short or long way also changes. 
+		/// If the short way was always chosen, bones would flip to the other side when that direction became the long way.
+		/// TrackEntry chooses the short way the first time it is applied and remembers that direction.</summary>
+		public void ResetRotationDirections () {
+			timelinesRotation.Clear();
 		}
 		}
 
 
 		override public String ToString () {
 		override public String ToString () {
 			return animation == null ? "<none>" : animation.name;
 			return animation == null ? "<none>" : animation.name;
 		}
 		}
 	}
 	}
+
+	class EventQueue {
+		private readonly ExposedList<EventQueueEntry> eventQueueEntries = new ExposedList<EventQueueEntry>();
+		public bool drainDisabled;
+
+		private readonly AnimationState state;
+		public event Action AnimationsChanged;
+
+		public EventQueue (AnimationState state, Action HandleAnimationsChanged) {
+			this.state = state;
+			this.AnimationsChanged += HandleAnimationsChanged;
+		}
+
+		struct EventQueueEntry {
+			public EventType type;
+			public TrackEntry entry;
+			public Event e;
+
+			public EventQueueEntry (EventType eventType, TrackEntry trackEntry, Event e = null) {
+				this.type = eventType;
+				this.entry = trackEntry;
+				this.e = e;
+			}
+		}
+
+		enum EventType {
+			Start, Interrupt, End, Dispose, Complete, Event
+		}
+
+		public void Start (TrackEntry entry) {
+			eventQueueEntries.Add(new EventQueueEntry(EventType.Start, entry));
+			if (AnimationsChanged != null) AnimationsChanged();
+		}
+
+		public void Interrupt (TrackEntry entry) {
+			eventQueueEntries.Add(new EventQueueEntry(EventType.Interrupt, entry));
+		}
+
+		public void End (TrackEntry entry) {
+			eventQueueEntries.Add(new EventQueueEntry(EventType.End, entry));
+			if (AnimationsChanged != null) AnimationsChanged();
+		}
+
+		public void Dispose (TrackEntry entry) {
+			eventQueueEntries.Add(new EventQueueEntry(EventType.Dispose, entry));
+		}
+
+		public void Complete (TrackEntry entry) {
+			eventQueueEntries.Add(new EventQueueEntry(EventType.Complete, entry));
+		}
+
+		public void Event (TrackEntry entry, Event e) {
+			eventQueueEntries.Add(new EventQueueEntry(EventType.Event, entry, e));
+		}
+
+		public void Drain () {
+			if (drainDisabled) return;
+			drainDisabled = true;
+
+			var entries = this.eventQueueEntries;
+			var entriesItems = entries.Items;
+			AnimationState state = this.state;
+
+			for (int i = 0, n = entries.Count; i < n; i++) {
+				var queueEntry = entriesItems[i];
+				TrackEntry trackEntry = queueEntry.entry;
+
+				switch (queueEntry.type) {
+				case EventType.Start:
+					trackEntry.OnStart();
+					state.OnStart(trackEntry);
+					break;
+				case EventType.Interrupt:
+					trackEntry.OnInterrupt();
+					state.OnInterrupt(trackEntry);
+					break;
+				case EventType.End:
+					trackEntry.OnEnd();
+					state.OnEnd(trackEntry);
+					goto case EventType.Dispose; // Fall through. (C#)
+				case EventType.Dispose:
+					trackEntry.OnDispose();
+					state.OnDispose(trackEntry);
+					break;
+				case EventType.Complete:
+					trackEntry.OnComplete();
+					state.OnComplete(trackEntry);
+					break;
+				case EventType.Event:
+					trackEntry.OnEvent(queueEntry.e);
+					state.OnEvent(trackEntry, queueEntry.e);
+					break;
+				}
+			}
+			eventQueueEntries.Clear();
+
+			drainDisabled = false;
+		}
+
+		public void Clear () {
+			eventQueueEntries.Clear();
+		}
+	}
 }
 }

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

@@ -62,6 +62,8 @@ namespace Spine {
 		}
 		}
 
 
 		public float GetMix (Animation from, Animation to) {
 		public float GetMix (Animation from, Animation to) {
+			if (from == null) throw new ArgumentNullException("from", "from cannot be null.");
+			if (to == null) throw new ArgumentNullException("to", "to cannot be null.");
 			AnimationPair key = new AnimationPair(from, to);
 			AnimationPair key = new AnimationPair(from, to);
 			float duration;
 			float duration;
 			if (animationToMixTime.TryGetValue(key, out duration)) return duration;
 			if (animationToMixTime.TryGetValue(key, out duration)) return duration;
@@ -76,6 +78,10 @@ namespace Spine {
 				this.a1 = a1;
 				this.a1 = a1;
 				this.a2 = a2;
 				this.a2 = a2;
 			}
 			}
+
+			public override string ToString () {
+				return a1.name + "->" + a2.name;
+			}
 		}
 		}
 
 
 		// Avoids boxing in the dictionary.
 		// Avoids boxing in the dictionary.

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

@@ -71,7 +71,7 @@ namespace Spine {
 			attachment.regionOriginalWidth = region.originalWidth;
 			attachment.regionOriginalWidth = region.originalWidth;
 			attachment.regionOriginalHeight = region.originalHeight;
 			attachment.regionOriginalHeight = region.originalHeight;
 			return attachment;
 			return attachment;
-		}
+		}			
 
 
 		public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, String name) {
 		public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, String name) {
 			return new BoundingBoxAttachment(name);
 			return new BoundingBoxAttachment(name);

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

@@ -43,6 +43,6 @@ namespace Spine {
 
 
 		public PathAttachment (String name)
 		public PathAttachment (String name)
 			: base(name) {
 			: base(name) {
-		}
+		}			
 	}
 	}
 }
 }

+ 1 - 2
spine-csharp/src/Attachments/RegionAttachment.cs

@@ -135,8 +135,7 @@ namespace Spine {
 		}
 		}
 
 
 		public void ComputeWorldVertices (Bone bone, float[] worldVertices) {
 		public void ComputeWorldVertices (Bone bone, float[] worldVertices) {
-			Skeleton skeleton = bone.skeleton;
-			float x = skeleton.x + bone.worldX, y = skeleton.y + bone.worldY;
+			float x = bone.worldX, y = bone.worldY;			
 			float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
 			float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
 			float[] offset = this.offset;
 			float[] offset = this.offset;
 			worldVertices[X1] = offset[X1] * a + offset[Y1] * b + x;
 			worldVertices[X1] = offset[X1] * a + offset[Y1] * b + x;

+ 10 - 7
spine-csharp/src/Attachments/VertexAttachment.cs

@@ -32,7 +32,7 @@ using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 
 
 namespace Spine {
 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> 
+	/// <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 {
 	public class VertexAttachment : Attachment {
 		internal int[] bones;
 		internal int[] bones;
 		internal float[] vertices;
 		internal float[] vertices;
@@ -50,18 +50,21 @@ namespace Spine {
 			ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0);
 			ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0);
 		}
 		}
 
 
+		/// <summary>Transforms local vertices to world coordinates.</summary>
+		/// <param name="start">The index of the first <see cref="Vertices"/> value to transform. Each vertex has 2 values, x and y.</param>
+		/// <param name="count">The number of world vertex values to output. Must be less than or equal to <see cref="WorldVerticesLength"/> - start.</param>
+		/// <param name="worldVertices">The output world vertices. Must have a length greater than or equal to <paramref name="offset"/> + <paramref name="count"/>.</param>
+		/// <param name="offset">The <paramref name="worldVertices"/> index to begin writing values.</param>
 		public void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset) {
 		public void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset) {
 			count += offset;
 			count += offset;
 			Skeleton skeleton = slot.Skeleton;
 			Skeleton skeleton = slot.Skeleton;
-			float x = skeleton.x, y = skeleton.y;
 			var deformArray = slot.attachmentVertices;
 			var deformArray = slot.attachmentVertices;
 			float[] vertices = this.vertices;
 			float[] vertices = this.vertices;
 			int[] bones = this.bones;
 			int[] bones = this.bones;
 			if (bones == null) {
 			if (bones == null) {
 				if (deformArray.Count > 0) vertices = deformArray.Items;
 				if (deformArray.Count > 0) vertices = deformArray.Items;
 				Bone bone = slot.bone;
 				Bone bone = slot.bone;
-				x += bone.worldX;
-				y += bone.worldY;
+				float x = bone.worldX, y = bone.worldY;
 				float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
 				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) {
 				for (int vv = start, w = offset; w < count; vv += 2, w += 2) {
 					float vx = vertices[vv], vy = vertices[vv + 1];
 					float vx = vertices[vv], vy = vertices[vv + 1];
@@ -79,7 +82,7 @@ namespace Spine {
 			Bone[] skeletonBones = skeleton.Bones.Items;
 			Bone[] skeletonBones = skeleton.Bones.Items;
 			if (deformArray.Count == 0) {
 			if (deformArray.Count == 0) {
 				for (int w = offset, b = skip * 3; w < count; w += 2) {
 				for (int w = offset, b = skip * 3; w < count; w += 2) {
-					float wx = x, wy = y;
+					float wx = 0, wy = 0;
 					int n = bones[v++];
 					int n = bones[v++];
 					n += v;
 					n += v;
 					for (; v < n; v++, b += 3) {
 					for (; v < n; v++, b += 3) {
@@ -94,7 +97,7 @@ namespace Spine {
 			} else {
 			} else {
 				float[] deform = deformArray.Items;
 				float[] deform = deformArray.Items;
 				for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += 2) {
 				for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += 2) {
-					float wx = x, wy = y;
+					float wx = 0, wy = 0;
 					int n = bones[v++];
 					int n = bones[v++];
 					n += v;
 					n += v;
 					for (; v < n; v++, b += 3, f += 2) {
 					for (; v < n; v++, b += 3, f += 2) {
@@ -112,6 +115,6 @@ namespace Spine {
 		/// <summary>Returns true if a deform originally applied to the specified attachment should be applied to this attachment.</summary>
 		/// <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) {
 		virtual public bool ApplyDeform (VertexAttachment sourceAttachment) {
 			return this == sourceAttachment;
 			return this == sourceAttachment;
-		}
+		}			
 	}
 	}
 }
 }

+ 137 - 116
spine-csharp/src/Bone.cs

@@ -39,11 +39,15 @@ namespace Spine {
 		internal Bone parent;
 		internal Bone parent;
 		internal ExposedList<Bone> children = new ExposedList<Bone>();
 		internal ExposedList<Bone> children = new ExposedList<Bone>();
 		internal float x, y, rotation, scaleX, scaleY, shearX, shearY;
 		internal float x, y, rotation, scaleX, scaleY, shearX, shearY;
-		internal float appliedRotation;
+		internal float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY;
+		internal bool appliedValid;
 
 
 		internal float a, b, worldX;
 		internal float a, b, worldX;
 		internal float c, d, worldY;
 		internal float c, d, worldY;
-		internal float worldSignX, worldSignY;
+
+//		internal float worldSignX, worldSignY;
+//		public float WorldSignX { get { return worldSignX; } }
+//		public float WorldSignY { get { return worldSignY; } }
 
 
 		internal bool sorted;
 		internal bool sorted;
 
 
@@ -55,7 +59,7 @@ namespace Spine {
 		public float Y { get { return y; } set { y = value; } }
 		public float Y { get { return y; } set { y = value; } }
 		public float Rotation { get { return rotation; } set { rotation = value; } }
 		public float Rotation { get { return rotation; } set { rotation = value; } }
 		/// <summary>The rotation, as calculated by any constraints.</summary>
 		/// <summary>The rotation, as calculated by any constraints.</summary>
-		public float AppliedRotation { get { return appliedRotation; } set { appliedRotation = value; } }
+		public float AppliedRotation { get { return arotation; } set { arotation = value; } }
 		public float ScaleX { get { return scaleX; } set { scaleX = value; } }
 		public float ScaleX { get { return scaleX; } set { scaleX = value; } }
 		public float ScaleY { get { return scaleY; } set { scaleY = value; } }
 		public float ScaleY { get { return scaleY; } set { scaleY = value; } }
 		public float ShearX { get { return shearX; } set { shearX = value; } }
 		public float ShearX { get { return shearX; } set { shearX = value; } }
@@ -67,12 +71,13 @@ namespace Spine {
 		public float D { get { return d; } }
 		public float D { get { return d; } }
 		public float WorldX { get { return worldX; } }
 		public float WorldX { get { return worldX; } }
 		public float WorldY { get { return worldY; } }
 		public float WorldY { get { return worldY; } }
-		public float WorldSignX { get { return worldSignX; } }
-		public float WorldSignY { get { return worldSignY; } }
-		public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.radDeg; } }
-		public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.radDeg; } }
-		public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c) * worldSignX; } }
-		public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d) * worldSignY; } }
+		public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.RadDeg; } }
+		public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.RadDeg; } }
+
+		/// <summary>Returns the magnitide (always positive) of the world scale X.</summary>
+		public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } }
+		/// <summary>Returns the magnitide (always positive) of the world scale Y.</summary>
+		public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } }
 
 
 		/// <param name="parent">May be null.</param>
 		/// <param name="parent">May be null.</param>
 		public Bone (BoneData data, Skeleton skeleton, Bone parent) {
 		public Bone (BoneData data, Skeleton skeleton, Bone parent) {
@@ -96,15 +101,23 @@ namespace Spine {
 
 
 		/// <summary>Computes the world transform using the parent bone and the specified local transform.</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) {
 		public void UpdateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) {
-			appliedRotation = rotation;
-
-			float rotationY = rotation + 90 + shearY;
-			float la = MathUtils.CosDeg(rotation + shearX) * scaleX, lb = MathUtils.CosDeg(rotationY) * scaleY;
-			float lc = MathUtils.SinDeg(rotation + shearX) * scaleX, ld = MathUtils.SinDeg(rotationY) * scaleY;
+			ax = x;
+			ay = y;
+			arotation = rotation;
+			ascaleX = scaleX;
+			ascaleY = scaleY;
+			ashearX = shearX;
+			ashearY = shearY;
+			appliedValid = true;
+			Skeleton skeleton = this.skeleton;
 
 
 			Bone parent = this.parent;
 			Bone parent = this.parent;
 			if (parent == null) { // Root bone.
 			if (parent == null) { // Root bone.
-				Skeleton skeleton = this.skeleton;
+				float rotationY = rotation + 90 + shearY;
+				float la = MathUtils.CosDeg(rotation + shearX) * scaleX;
+				float lb = MathUtils.CosDeg(rotationY) * scaleY;
+				float lc = MathUtils.SinDeg(rotation + shearX) * scaleX;
+				float ld = MathUtils.SinDeg(rotationY) * scaleY;
 				if (skeleton.flipX) {
 				if (skeleton.flipX) {
 					x = -x;
 					x = -x;
 					la = -la;
 					la = -la;
@@ -119,92 +132,101 @@ namespace Spine {
 				b = lb;
 				b = lb;
 				c = lc;
 				c = lc;
 				d = ld;
 				d = ld;
-				worldX = x;
-				worldY = y;
-				worldSignX = Math.Sign(scaleX);
-				worldSignY = Math.Sign(scaleY);
+				worldX = x + skeleton.x;
+				worldY = y + skeleton.y;
+//				worldSignX = Math.Sign(scaleX);
+//				worldSignY = Math.Sign(scaleY);
 				return;
 				return;
 			}
 			}
 
 
 			float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
 			float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
 			worldX = pa * x + pb * y + parent.worldX;
 			worldX = pa * x + pb * y + parent.worldX;
 			worldY = pc * x + pd * y + parent.worldY;
 			worldY = pc * x + pd * y + parent.worldY;
-			worldSignX = parent.worldSignX * Math.Sign(scaleX);
-			worldSignY = parent.worldSignY * Math.Sign(scaleY);
-
-			if (data.inheritRotation && data.inheritScale) {
-				a = pa * la + pb * lc;
-				b = pa * lb + pb * ld;
-				c = pc * la + pd * lc;
-				d = pc * lb + pd * ld;
-			} else {
-				if (data.inheritRotation) { // No scale inheritance.
-					pa = 1;
-					pb = 0;
-					pc = 0;
-					pd = 1;
-					do {
-						float cos = MathUtils.CosDeg(parent.appliedRotation), sin = MathUtils.SinDeg(parent.appliedRotation);
-						float temp = pa * cos + pb * sin;
-						pb = pb * cos - pa * sin;
-						pa = temp;
-						temp = pc * cos + pd * sin;
-						pd = pd * cos - pc * sin;
-						pc = temp;
+//			worldSignX = parent.worldSignX * Math.Sign(scaleX);
+//			worldSignY = parent.worldSignY * Math.Sign(scaleY);
 
 
-						if (!parent.data.inheritRotation) break;
-						parent = parent.parent;
-					} while (parent != null);
+			switch (data.transformMode) {
+			case TransformMode.Normal: {
+					float rotationY = rotation + 90 + shearY;
+					float la = MathUtils.CosDeg(rotation + shearX) * scaleX;
+					float lb = MathUtils.CosDeg(rotationY) * scaleY;
+					float lc = MathUtils.SinDeg(rotation + shearX) * scaleX;
+					float ld = MathUtils.SinDeg(rotationY) * scaleY;
 					a = pa * la + pb * lc;
 					a = pa * la + pb * lc;
 					b = pa * lb + pb * ld;
 					b = pa * lb + pb * ld;
 					c = pc * la + pd * lc;
 					c = pc * la + pd * lc;
 					d = pc * lb + pd * ld;
 					d = pc * lb + pd * ld;
-				} else if (data.inheritScale) { // No rotation inheritance.
-					pa = 1;
-					pb = 0;
-					pc = 0;
-					pd = 1;
-					do {
-						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 = pb * zd - pa * zb;
-						pa = temp;
-						temp = pc * za + pd * zc;
-						pd = pd * zd - pc * zb;
-						pc = temp;
-
-						if (psx >= 0) sin = -sin;
-						temp = pa * cos + pb * sin;
-						pb = pb * cos - pa * sin;
-						pa = temp;
-						temp = pc * cos + pd * sin;
-						pd = pd * cos - pc * sin;
-						pc = temp;
-
-						if (!parent.data.inheritScale) break;
-						parent = parent.parent;
-					} while (parent != null);
-					a = pa * la + pb * lc;
-					b = pa * lb + pb * ld;
+					return;
+				}
+			case TransformMode.OnlyTranslation: {
+					float rotationY = rotation + 90 + shearY;
+					a = MathUtils.CosDeg(rotation + shearX) * scaleX;
+					b = MathUtils.CosDeg(rotationY) * scaleY;
+					c = MathUtils.SinDeg(rotation + shearX) * scaleX;
+					d = MathUtils.SinDeg(rotationY) * scaleY;
+					break;
+				}
+			case TransformMode.NoRotationOrReflection: {
+					float s = pa * pa + pc * pc, prx;
+					if (s > 0.0001f) {
+						s = Math.Abs(pa * pd - pb * pc) / s;
+						pb = pc * s;
+						pd = pa * s;
+						prx = MathUtils.Atan2(pc, pa) * MathUtils.RadDeg;
+					} else {
+						pa = 0;
+						pc = 0;
+						prx = 90 - MathUtils.Atan2(pd, pb) * MathUtils.RadDeg;
+					}
+					float rx = rotation + shearX - prx;
+					float ry = rotation + shearY - prx + 90;
+					float la = MathUtils.CosDeg(rx) * scaleX;
+					float lb = MathUtils.CosDeg(ry) * scaleY;
+					float lc = MathUtils.SinDeg(rx) * scaleX;
+					float ld = MathUtils.SinDeg(ry) * scaleY;
+					a = pa * la - pb * lc;
+					b = pa * lb - pb * ld;
 					c = pc * la + pd * lc;
 					c = pc * la + pd * lc;
 					d = pc * lb + pd * ld;
 					d = pc * lb + pd * ld;
-				} else {
-					a = la;
-					b = lb;
-					c = lc;
-					d = ld;
-				}
-				if (skeleton.flipX) {
-					a = -a;
-					b = -b;
+					break;
 				}
 				}
-				if (skeleton.flipY != yDown) {
-					c = -c;
-					d = -d;
+			case TransformMode.NoScale:
+			case TransformMode.NoScaleOrReflection: {
+					float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation);
+					float za = pa * cos + pb * sin;
+					float zc = pc * cos + pd * sin;
+					float s = (float)Math.Sqrt(za * za + zc * zc);
+					if (s > 0.00001f) s = 1 / s;
+					za *= s;
+					zc *= s;
+					s = (float)Math.Sqrt(za * za + zc * zc);
+					float r = MathUtils.PI / 2 + MathUtils.Atan2(zc, za);
+					float zb = MathUtils.Cos(r) * s;
+					float zd = MathUtils.Sin(r) * s;
+					float la = MathUtils.CosDeg(shearX) * scaleX;
+					float lb = MathUtils.CosDeg(90 + shearY) * scaleY;
+					float lc = MathUtils.SinDeg(shearX) * scaleX;
+					float ld = MathUtils.SinDeg(90 + shearY) * scaleY;
+					a = za * la + zb * lc;
+					b = za * lb + zb * ld;
+					c = zc * la + zd * lc;
+					d = zc * lb + zd * ld;
+					if (data.transformMode != TransformMode.NoScaleOrReflection ? pa * pd - pb * pc < 0 : skeleton.flipX != skeleton.flipY) {
+						b = -b;
+						d = -d;
+					}
+					return;
 				}
 				}
 			}
 			}
+
+			if (skeleton.flipX) {
+				a = -a;
+				b = -b;
+			}
+			if (skeleton.flipY) {
+				c = -c;
+				d = -d;
+			}
 		}
 		}
 
 
 		public void SetToSetupPose () {
 		public void SetToSetupPose () {
@@ -221,18 +243,18 @@ namespace Spine {
 		public float WorldToLocalRotationX {
 		public float WorldToLocalRotationX {
 			get {
 			get {
 				Bone parent = this.parent;
 				Bone parent = this.parent;
-				if (parent == null) return rotation;
+				if (parent == null) return arotation;
 				float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c;
 				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;
+				return MathUtils.Atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.RadDeg;
 			}
 			}
 		}
 		}
 
 
 		public float WorldToLocalRotationY {
 		public float WorldToLocalRotationY {
 			get {
 			get {
 				Bone parent = this.parent;
 				Bone parent = this.parent;
-				if (parent == null) return rotation;
+				if (parent == null) return arotation;
 				float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d;
 				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;
+				return MathUtils.Atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.RadDeg;
 			}
 			}
 		}
 		}
 
 
@@ -243,33 +265,33 @@ namespace Spine {
 			this.b = cos * b - sin * d;
 			this.b = cos * b - sin * d;
 			this.c = sin * a + cos * c;
 			this.c = sin * a + cos * c;
 			this.d = sin * b + cos * d;
 			this.d = sin * b + cos * d;
+			appliedValid = false;
 		}
 		}
 
 
 		/// <summary>
 		/// <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).
+		/// Computes the individual applied transform values from the world transform. This can be useful to perform processing using
+		/// the applied 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.
+		/// Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation.
 		/// </summary>
 		/// </summary>
-		public void UpdateLocalTransform () {
+		internal void UpdateAppliedTransform () {
+			appliedValid = true;
 			Bone parent = this.parent;
 			Bone parent = this.parent;
 			if (parent == null) {
 			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;
+				ax = worldX;
+				ay = worldY;
+				arotation = MathUtils.Atan2(c, a) * MathUtils.RadDeg;
+				ascaleX = (float)Math.Sqrt(a * a + c * c);
+				ascaleY = (float)Math.Sqrt(b * b + d * d);
+				ashearX = 0;
+				ashearY = MathUtils.Atan2(a * b + c * d, a * d - b * c) * MathUtils.RadDeg;
 				return;
 				return;
 			}
 			}
 			float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
 			float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
 			float pid = 1 / (pa * pd - pb * pc);
 			float pid = 1 / (pa * pd - pb * pc);
 			float dx = worldX - parent.worldX, dy = worldY - parent.worldY;
 			float dx = worldX - parent.worldX, dy = worldY - parent.worldY;
-			x = (dx * pd * pid - dy * pb * pid);
-			y = (dy * pa * pid - dx * pc * pid);
+			ax = (dx * pd * pid - dy * pb * pid);
+			ay = (dy * pa * pid - dx * pc * pid);
 			float ia = pid * pd;
 			float ia = pid * pd;
 			float id = pid * pa;
 			float id = pid * pa;
 			float ib = pid * pb;
 			float ib = pid * pb;
@@ -278,23 +300,22 @@ namespace Spine {
 			float rb = ia * b - ib * d;
 			float rb = ia * b - ib * d;
 			float rc = id * c - ic * a;
 			float rc = id * c - ic * a;
 			float rd = id * d - ic * b;
 			float rd = id * d - ic * b;
-			shearX = 0;
-			scaleX = (float)Math.Sqrt(ra * ra + rc * rc);
-			if (scaleX > 0.0001f) {
+			ashearX = 0;
+			ascaleX = (float)Math.Sqrt(ra * ra + rc * rc);
+			if (ascaleX > 0.0001f) {
 				float det = ra * rd - rb * rc;
 				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;
+				ascaleY = det / ascaleX;
+				ashearY = MathUtils.Atan2(ra * rb + rc * rd, det) * MathUtils.RadDeg;
+				arotation = MathUtils.Atan2(rc, ra) * MathUtils.RadDeg;
 			} else {
 			} else {
-				scaleX = 0;
-				scaleY = (float)Math.Sqrt(rb * rb + rd * rd);
-				shearY = 0;
-				rotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.radDeg;
+				ascaleX = 0;
+				ascaleY = (float)Math.Sqrt(rb * rb + rd * rd);
+				ashearY = 0;
+				arotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.RadDeg;
 			}
 			}
-			appliedRotation = rotation;
 		}
 		}
 
 
-		public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) {
+		public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) {			
 			float a = this.a, b = this.b, c = this.c, d = this.d;
 			float a = this.a, b = this.b, c = this.c, d = this.d;
 			float invDet = 1 / (a * d - b * c);
 			float invDet = 1 / (a * d - b * c);
 			float x = worldX - this.worldX, y = worldY - this.worldY;
 			float x = worldX - this.worldX, y = worldY - this.worldY;

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

@@ -37,7 +37,8 @@ namespace Spine {
 		internal BoneData parent;
 		internal BoneData parent;
 		internal float length;
 		internal float length;
 		internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY;
 		internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY;
-		internal bool inheritRotation = true, inheritScale = true;
+		internal TransformMode transformMode = TransformMode.Normal;
+		//internal bool inheritRotation = true, inheritScale = true;
 
 
 		/// <summary>May be null.</summary>
 		/// <summary>May be null.</summary>
 		public int Index { get { return index; } }
 		public int Index { get { return index; } }
@@ -51,8 +52,10 @@ namespace Spine {
 		public float ScaleY { get { return scaleY; } set { scaleY = value; } }
 		public float ScaleY { get { return scaleY; } set { scaleY = value; } }
 		public float ShearX { get { return shearX; } set { shearX = value; } }
 		public float ShearX { get { return shearX; } set { shearX = value; } }
 		public float ShearY { get { return shearY; } set { shearY = value; } }
 		public float ShearY { get { return shearY; } set { shearY = value; } }
-		public bool InheritRotation { get { return inheritRotation; } set { inheritRotation = value; } }
-		public bool InheritScale { get { return inheritScale; } set { inheritScale = value; } }
+		public TransformMode TransformMode { get { return transformMode; } set { transformMode = 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>
 		/// <param name="parent">May be null.</param>
 		public BoneData (int index, String name, BoneData parent) {
 		public BoneData (int index, String name, BoneData parent) {
@@ -67,4 +70,14 @@ namespace Spine {
 			return name;
 			return name;
 		}
 		}
 	}
 	}
+
+	[Flags]
+	public enum TransformMode {
+		//0000 0FSR
+		Normal = 0, // 0000
+		OnlyTranslation = 7, // 0111
+		NoRotationOrReflection = 1, // 0001
+		NoScale = 2, // 0010
+		NoScaleOrReflection = 6, // 0110
+	}
 }
 }

+ 15 - 9
spine-csharp/src/Event.cs

@@ -32,20 +32,26 @@ using System;
 
 
 namespace Spine {
 namespace Spine {
 	public class Event {
 	public class Event {
-		public EventData Data { get; private set; }
-		public int Int { get; set; }
-		public float Float { get; set; }
-		public String String { get; set; }
-		public float Time { get; private set; }
+		internal readonly EventData data;
+		internal readonly float time;
+		internal int intValue;
+		internal float floatValue;
+		internal string stringValue;
+
+		public EventData Data { get { return data; } }
+		public float Time { get { return time; } }
+		public int Int { get { return intValue; } set { intValue = value; } }
+		public float Float { get { return floatValue; } set { floatValue = value; } }
+		public String String { get { return stringValue; } set { stringValue = value; } }
 
 
 		public Event (float time, EventData data) {
 		public Event (float time, EventData data) {
 			if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
 			if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
-			Time = time;
-			Data = data;
+			this.time = time;
+			this.data = data;
 		}
 		}
 
 
-		override public String ToString () {
-			return Data.Name;
+		override public string ToString () {
+			return this.data.Name;
 		}
 		}
 	}
 	}
 }
 }

+ 11 - 0
spine-csharp/src/ExposedList.cs

@@ -95,6 +95,15 @@ namespace Spine {
 			return this;
 			return this;
 		}
 		}
 
 
+		public void EnsureCapacity (int min) {
+			if (Items.Length < min) {
+				int newCapacity = Items.Length == 0 ? DefaultCapacity : Items.Length * 2;
+				//if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength;
+				if (newCapacity < min) newCapacity = min;
+				Capacity = newCapacity;
+			}
+		}
+
 		private void CheckRange (int idx, int count) {
 		private void CheckRange (int idx, int count) {
 			if (idx < 0)
 			if (idx < 0)
 				throw new ArgumentOutOfRangeException("index");
 				throw new ArgumentOutOfRangeException("index");
@@ -182,6 +191,8 @@ namespace Spine {
 			Array.Copy(Items, index, array, arrayIndex, count);
 			Array.Copy(Items, index, array, arrayIndex, count);
 		}
 		}
 
 
+
+
 		public bool Exists (Predicate<T> match) {
 		public bool Exists (Predicate<T> match) {
 			CheckMatch(match);
 			CheckMatch(match);
 			return GetIndex(0, Count, match) != -1;
 			return GetIndex(0, Count, match) != -1;

+ 5 - 0
spine-csharp/src/IConstraint.cs

@@ -0,0 +1,5 @@
+namespace Spine {
+	public interface IConstraint : IUpdatable {
+		int Order { get; }
+	}
+}

+ 23 - 20
spine-csharp/src/IkConstraint.cs

@@ -31,16 +31,15 @@
 using System;
 using System;
 
 
 namespace Spine {
 namespace Spine {
-	public class IkConstraint : IUpdatable {
+	public class IkConstraint : IConstraint {
 		internal IkConstraintData data;
 		internal IkConstraintData data;
 		internal ExposedList<Bone> bones = new ExposedList<Bone>();
 		internal ExposedList<Bone> bones = new ExposedList<Bone>();
 		internal Bone target;
 		internal Bone target;
 		internal float mix;
 		internal float mix;
 		internal int bendDirection;
 		internal int bendDirection;
 
 
-		internal int level;
-
 		public IkConstraintData Data { get { return data; } }
 		public IkConstraintData Data { get { return data; } }
+		public int Order { get { return data.order; } }
 		public ExposedList<Bone> Bones { get { return bones; } }
 		public ExposedList<Bone> Bones { get { return bones; } }
 		public Bone Target { get { return target; } set { target = value; } }
 		public Bone Target { get { return target; } set { target = value; } }
 		public int BendDirection { get { return bendDirection; } set { bendDirection = value; } }
 		public int BendDirection { get { return bendDirection; } set { bendDirection = value; } }
@@ -83,17 +82,18 @@ namespace Spine {
 		/// <summary>Adjusts the bone rotation so the tip is as close to the target position as possible. The target is specified
 		/// <summary>Adjusts the bone rotation so the tip is as close to the target position as possible. The target is specified
 		/// in the world coordinate system.</summary>
 		/// in the world coordinate system.</summary>
 		static public void Apply (Bone bone, float targetX, float targetY, float alpha) {
 		static public void Apply (Bone bone, float targetX, float targetY, float alpha) {
-			Bone pp = bone.parent;
-			float id = 1 / (pp.a * pp.d - pp.b * pp.c);
-			float x = targetX - pp.worldX, y = targetY - pp.worldY;
-			float tx = (x * pp.d - y * pp.b) * id - bone.x, ty = (y * pp.a - x * pp.c) * id - bone.y;
-			float rotationIK = MathUtils.Atan2(ty, tx) * MathUtils.radDeg - bone.shearX - bone.rotation;
-			if (bone.scaleX < 0) rotationIK += 180;
+			if (!bone.appliedValid) bone.UpdateAppliedTransform();
+			Bone p = bone.parent;
+			float id = 1 / (p.a * p.d - p.b * p.c);
+			float x = targetX - p.worldX, y = targetY - p.worldY;
+			float tx = (x * p.d - y * p.b) * id - bone.ax, ty = (y * p.a - x * p.c) * id - bone.ay;
+			float rotationIK = MathUtils.Atan2(ty, tx) * MathUtils.RadDeg - bone.ashearX - bone.arotation;
+			if (bone.ascaleX < 0) rotationIK += 180;
 			if (rotationIK > 180)
 			if (rotationIK > 180)
 				rotationIK -= 360;
 				rotationIK -= 360;
 			else if (rotationIK < -180) rotationIK += 360;
 			else if (rotationIK < -180) rotationIK += 360;
-			bone.UpdateWorldTransform(bone.x, bone.y, bone.rotation + rotationIK * alpha, bone.scaleX, bone.scaleY,
-				bone.shearX, bone.shearY);
+			bone.UpdateWorldTransform(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, bone.ascaleX, bone.ascaleY, bone.ashearX, 
+				bone.ashearY);
 		}
 		}
 
 
 		/// <summary>Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as
 		/// <summary>Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as
@@ -104,7 +104,10 @@ namespace Spine {
 				child.UpdateWorldTransform ();
 				child.UpdateWorldTransform ();
 				return;
 				return;
 			}
 			}
-			float px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX;
+			//float px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX;
+			if (!parent.appliedValid) parent.UpdateAppliedTransform();
+			if (!child.appliedValid) child.UpdateAppliedTransform();
+			float px = parent.ax, py = parent.ay, psx = parent.ascaleX, psy = parent.ascaleY, csx = child.ascaleX;
 			int os1, os2, s2;
 			int os1, os2, s2;
 			if (psx < 0) {
 			if (psx < 0) {
 				psx = -psx;
 				psx = -psx;
@@ -123,14 +126,14 @@ namespace Spine {
 				os2 = 180;
 				os2 = 180;
 			} else
 			} else
 				os2 = 0;
 				os2 = 0;
-			float cx = child.x, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d;
+			float cx = child.ax, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d;
 			bool u = Math.Abs(psx - psy) <= 0.0001f;
 			bool u = Math.Abs(psx - psy) <= 0.0001f;
 			if (!u) {
 			if (!u) {
 				cy = 0;
 				cy = 0;
 				cwx = a * cx + parent.worldX;
 				cwx = a * cx + parent.worldX;
 				cwy = c * cx + parent.worldY;
 				cwy = c * cx + parent.worldY;
 			} else {
 			} else {
-				cy = child.y;
+				cy = child.ay;
 				cwx = a * cx + b * cy + parent.worldX;
 				cwx = a * cx + b * cy + parent.worldX;
 				cwy = c * cx + d * cy + parent.worldY;
 				cwy = c * cx + d * cy + parent.worldY;
 			}
 			}
@@ -217,18 +220,18 @@ namespace Spine {
 			}
 			}
 			outer:
 			outer:
 			float os = MathUtils.Atan2(cy, cx) * s2;
 			float os = MathUtils.Atan2(cy, cx) * s2;
-			float rotation = parent.rotation;
-			a1 = (a1 - os) * MathUtils.radDeg + os1 - rotation;
+			float rotation = parent.arotation;
+			a1 = (a1 - os) * MathUtils.RadDeg + os1 - rotation;
 			if (a1 > 180)
 			if (a1 > 180)
 				a1 -= 360;
 				a1 -= 360;
 			else 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;
-			a2 = ((a2 + os) * MathUtils.radDeg - child.shearX) * s2 + os2 - rotation;
+			parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, parent.scaleX, parent.ascaleY, 0, 0);
+			rotation = child.arotation;
+			a2 = ((a2 + os) * MathUtils.RadDeg - child.ashearX) * s2 + os2 - rotation;
 			if (a2 > 180)
 			if (a2 > 180)
 				a2 -= 360;
 				a2 -= 360;
 			else 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);
+			child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY);
 		}
 		}
 	}
 	}
 }
 }

+ 2 - 0
spine-csharp/src/IkConstraintData.cs

@@ -34,12 +34,14 @@ using System.Collections.Generic;
 namespace Spine {
 namespace Spine {
 	public class IkConstraintData {
 	public class IkConstraintData {
 		internal String name;
 		internal String name;
+		internal int order;
 		internal List<BoneData> bones = new List<BoneData>();
 		internal List<BoneData> bones = new List<BoneData>();
 		internal BoneData target;
 		internal BoneData target;
 		internal int bendDirection = 1;
 		internal int bendDirection = 1;
 		internal float mix = 1;
 		internal float mix = 1;
 
 
 		public String Name { get { return name; } }
 		public String Name { get { return name; } }
+		public int Order { get { return order; } set { order = value; } }
 		public List<BoneData> Bones { get { return bones; } }
 		public List<BoneData> Bones { get { return bones; } }
 		public BoneData Target { get { return target; } set { target = value; } }
 		public BoneData Target { get { return target; } set { target = value; } }
 		public int BendDirection { get { return bendDirection; } set { bendDirection = value; } }
 		public int BendDirection { get { return bendDirection; } set { bendDirection = value; } }

+ 49 - 51
spine-csharp/src/Json.cs

@@ -54,7 +54,7 @@ namespace Spine {
  *
  *
  * Changes made:
  * Changes made:
  * 
  * 
- *  - Optimized parser speed (deserialize roughly near 3x faster than original)
+ * 	- Optimized parser speed (deserialize roughly near 3x faster than original)
  *  - Added support to handle lexer/parser error messages with line numbers
  *  - Added support to handle lexer/parser error messages with line numbers
  *  - Added more fine grained control over type conversions during the parsing
  *  - Added more fine grained control over type conversions during the parsing
  *  - Refactory API (Separate Lexer code from Parser code and the Encoder from Decoder)
  *  - Refactory API (Separate Lexer code from Parser code and the Encoder from Decoder)
@@ -133,19 +133,19 @@ namespace SharpJson
 		{
 		{
 			int idx = 0;
 			int idx = 0;
 			StringBuilder builder = null;
 			StringBuilder builder = null;
-
+			
 			SkipWhiteSpaces();
 			SkipWhiteSpaces();
-
+			
 			// "
 			// "
 			char c = json[index++];
 			char c = json[index++];
-
+			
 			bool failed = false;
 			bool failed = false;
 			bool complete = false;
 			bool complete = false;
-
+			
 			while (!complete && !failed) {
 			while (!complete && !failed) {
 				if (index == json.Length)
 				if (index == json.Length)
 					break;
 					break;
-
+				
 				c = json[index++];
 				c = json[index++];
 				if (c == '"') {
 				if (c == '"') {
 					complete = true;
 					complete = true;
@@ -153,9 +153,9 @@ namespace SharpJson
 				} else if (c == '\\') {
 				} else if (c == '\\') {
 					if (index == json.Length)
 					if (index == json.Length)
 						break;
 						break;
-
+					
 					c = json[index++];
 					c = json[index++];
-
+					
 					switch (c) {
 					switch (c) {
 					case '"':
 					case '"':
 						stringBuffer[idx++] = '"';
 						stringBuffer[idx++] = '"';
@@ -185,10 +185,10 @@ namespace SharpJson
 						int remainingLength = json.Length - index;
 						int remainingLength = json.Length - index;
 						if (remainingLength >= 4) {
 						if (remainingLength >= 4) {
 							var hex = new string(json, index, 4);
 							var hex = new string(json, index, 4);
-
+							
 							// XXX: handle UTF
 							// XXX: handle UTF
 							stringBuffer[idx++] = (char) Convert.ToInt32(hex, 16);
 							stringBuffer[idx++] = (char) Convert.ToInt32(hex, 16);
-
+							
 							// skip 4 chars
 							// skip 4 chars
 							index += 4;
 							index += 4;
 						} else {
 						} else {
@@ -199,38 +199,38 @@ namespace SharpJson
 				} else {
 				} else {
 					stringBuffer[idx++] = c;
 					stringBuffer[idx++] = c;
 				}
 				}
-
+				
 				if (idx >= stringBuffer.Length) {
 				if (idx >= stringBuffer.Length) {
 					if (builder == null)
 					if (builder == null)
 						builder = new StringBuilder();
 						builder = new StringBuilder();
-
+					
 					builder.Append(stringBuffer, 0, idx);
 					builder.Append(stringBuffer, 0, idx);
 					idx = 0;
 					idx = 0;
 				}
 				}
 			}
 			}
-
+			
 			if (!complete) {
 			if (!complete) {
 				success = false;
 				success = false;
 				return null;
 				return null;
 			}
 			}
-
+			
 			if (builder != null)
 			if (builder != null)
 				return builder.ToString ();
 				return builder.ToString ();
 			else
 			else
 				return new string (stringBuffer, 0, idx);
 				return new string (stringBuffer, 0, idx);
 		}
 		}
-
+		
 		string GetNumberString()
 		string GetNumberString()
 		{
 		{
 			SkipWhiteSpaces();
 			SkipWhiteSpaces();
 
 
 			int lastIndex = GetLastIndexOfNumber(index);
 			int lastIndex = GetLastIndexOfNumber(index);
 			int charLength = (lastIndex - index) + 1;
 			int charLength = (lastIndex - index) + 1;
-
+			
 			var result = new string (json, index, charLength);
 			var result = new string (json, index, charLength);
-
+			
 			index = lastIndex + 1;
 			index = lastIndex + 1;
-
+			
 			return result;
 			return result;
 		}
 		}
 
 
@@ -238,10 +238,10 @@ namespace SharpJson
 		{
 		{
 			float number;
 			float number;
 			var str = GetNumberString ();
 			var str = GetNumberString ();
-
+			
 			if (!float.TryParse (str, NumberStyles.Float, CultureInfo.InvariantCulture, out number))
 			if (!float.TryParse (str, NumberStyles.Float, CultureInfo.InvariantCulture, out number))
 				return 0;
 				return 0;
-
+			
 			return number;
 			return number;
 		}
 		}
 
 
@@ -249,24 +249,25 @@ namespace SharpJson
 		{
 		{
 			double number;
 			double number;
 			var str = GetNumberString ();
 			var str = GetNumberString ();
-
+			
 			if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number))
 			if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number))
 				return 0;
 				return 0;
-
+			
 			return number;
 			return number;
 		}
 		}
-
+		
 		int GetLastIndexOfNumber(int index)
 		int GetLastIndexOfNumber(int index)
 		{
 		{
 			int lastIndex;
 			int lastIndex;
-
+			
 			for (lastIndex = index; lastIndex < json.Length; lastIndex++) {
 			for (lastIndex = index; lastIndex < json.Length; lastIndex++) {
 				char ch = json[lastIndex];
 				char ch = json[lastIndex];
-
-				if ((ch < '0' || ch > '9') && ch != '+' && ch != '-' && ch != '.' && ch != 'e' && ch != 'E')
+				
+				if ((ch < '0' || ch > '9') && ch != '+' && ch != '-'
+				    && ch != '.' && ch != 'e' && ch != 'E')
 					break;
 					break;
 			}
 			}
-
+			
 			return lastIndex - 1;
 			return lastIndex - 1;
 		}
 		}
 
 
@@ -301,9 +302,9 @@ namespace SharpJson
 		{
 		{
 			if (index == json.Length)
 			if (index == json.Length)
 				return Token.None;
 				return Token.None;
-
+			
 			char c = json[index++];
 			char c = json[index++];
-
+			
 			switch (c) {
 			switch (c) {
 			case '{':
 			case '{':
 				return Token.CurlyOpen;
 				return Token.CurlyOpen;
@@ -326,41 +327,38 @@ namespace SharpJson
 			}
 			}
 
 
 			index--;
 			index--;
-
+			
 			int remainingLength = json.Length - index;
 			int remainingLength = json.Length - index;
-
+			
 			// false
 			// false
 			if (remainingLength >= 5) {
 			if (remainingLength >= 5) {
 				if (json[index] == 'f' &&
 				if (json[index] == 'f' &&
-					json[index + 1] == 'a' &&
-					json[index + 2] == 'l' &&
-					json[index + 3] == 's' &&
-					json[index + 4] == 'e'
-				) {
+				    json[index + 1] == 'a' &&
+				    json[index + 2] == 'l' &&
+				    json[index + 3] == 's' &&
+				    json[index + 4] == 'e') {
 					index += 5;
 					index += 5;
 					return Token.False;
 					return Token.False;
 				}
 				}
 			}
 			}
-
+			
 			// true
 			// true
 			if (remainingLength >= 4) {
 			if (remainingLength >= 4) {
 				if (json[index] == 't' &&
 				if (json[index] == 't' &&
-					json[index + 1] == 'r' &&
-					json[index + 2] == 'u' &&
-					json[index + 3] == 'e'
-				) {
+				    json[index + 1] == 'r' &&
+				    json[index + 2] == 'u' &&
+				    json[index + 3] == 'e') {
 					index += 4;
 					index += 4;
 					return Token.True;
 					return Token.True;
 				}
 				}
 			}
 			}
-
+			
 			// null
 			// null
 			if (remainingLength >= 4) {
 			if (remainingLength >= 4) {
 				if (json[index] == 'n' &&
 				if (json[index] == 'n' &&
-					json[index + 1] == 'u' &&
-					json[index + 2] == 'l' &&
-					json[index + 3] == 'l'
-				) {
+				    json[index + 1] == 'u' &&
+				    json[index + 2] == 'l' &&
+				    json[index + 3] == 'l') {
 					index += 4;
 					index += 4;
 					return Token.Null;
 					return Token.Null;
 				}
 				}
@@ -440,25 +438,25 @@ namespace SharpJson
 						TriggerError("Invalid token; expected ':'");
 						TriggerError("Invalid token; expected ':'");
 						return null;
 						return null;
 					}
 					}
-
+					
 					// value
 					// value
 					object value = ParseValue();
 					object value = ParseValue();
 
 
 					if (errorMessage != null)
 					if (errorMessage != null)
 						return null;
 						return null;
-
+					
 					table[name] = value;
 					table[name] = value;
 					break;
 					break;
 				}
 				}
 			}
 			}
-
+			
 			//return null; // Unreachable code
 			//return null; // Unreachable code
 		}
 		}
 
 
 		IList<object> ParseArray()
 		IList<object> ParseArray()
 		{
 		{
 			var array = new List<object>();
 			var array = new List<object>();
-
+			
 			// [
 			// [
 			lexer.NextToken();
 			lexer.NextToken();
 
 
@@ -485,7 +483,7 @@ namespace SharpJson
 					break;
 					break;
 				}
 				}
 			}
 			}
-
+			
 			//return null; // Unreachable code
 			//return null; // Unreachable code
 		}
 		}
 
 

+ 14 - 14
spine-csharp/src/MathUtils.cs

@@ -34,43 +34,43 @@ namespace Spine {
 	public static class MathUtils {
 	public static class MathUtils {
 		public const float PI = 3.1415927f;
 		public const float PI = 3.1415927f;
 		public const float PI2 = PI * 2;
 		public const float PI2 = PI * 2;
-		public const float radDeg = 180f / PI;
-		public const float degRad = PI / 180;
+		public const float RadDeg = 180f / PI;
+		public const float DegRad = PI / 180;
 
 
 		const int SIN_BITS = 14; // 16KB. Adjust for accuracy.
 		const int SIN_BITS = 14; // 16KB. Adjust for accuracy.
 		const int SIN_MASK = ~(-1 << SIN_BITS);
 		const int SIN_MASK = ~(-1 << SIN_BITS);
 		const int SIN_COUNT = SIN_MASK + 1;
 		const int SIN_COUNT = SIN_MASK + 1;
-		const float radFull = PI * 2;
-		const float degFull = 360;
-		const float radToIndex = SIN_COUNT / radFull;
-		const float degToIndex = SIN_COUNT / degFull;
+		const float RadFull = PI * 2;
+		const float DegFull = 360;
+		const float RadToIndex = SIN_COUNT / RadFull;
+		const float DegToIndex = SIN_COUNT / DegFull;
 		static float[] sin = new float[SIN_COUNT];
 		static float[] sin = new float[SIN_COUNT];
 
 
 		static MathUtils () {
 		static MathUtils () {
 			for (int i = 0; i < SIN_COUNT; i++)
 			for (int i = 0; i < SIN_COUNT; i++)
-				sin[i] = (float)Math.Sin((i + 0.5f) / SIN_COUNT * radFull);
+				sin[i] = (float)Math.Sin((i + 0.5f) / SIN_COUNT * RadFull);
 			for (int i = 0; i < 360; i += 90)
 			for (int i = 0; i < 360; i += 90)
-				sin[(int)(i * degToIndex) & SIN_MASK] = (float)Math.Sin(i * degRad);
+				sin[(int)(i * DegToIndex) & SIN_MASK] = (float)Math.Sin(i * DegRad);
 		}
 		}
 
 
 		/// <summary>Returns the sine in radians from a lookup table.</summary>
 		/// <summary>Returns the sine in radians from a lookup table.</summary>
 		static public float Sin (float radians) {
 		static public float Sin (float radians) {
-			return sin[(int)(radians * radToIndex) & SIN_MASK];
+			return sin[(int)(radians * RadToIndex) & SIN_MASK];
 		}
 		}
 
 
 		/// <summary>Returns the cosine in radians from a lookup table.</summary>
 		/// <summary>Returns the cosine in radians from a lookup table.</summary>
 		static public float Cos (float radians) {
 		static public float Cos (float radians) {
-			return sin[(int)((radians + PI / 2) * radToIndex) & SIN_MASK];
+			return sin[(int)((radians + PI / 2) * RadToIndex) & SIN_MASK];
 		}
 		}
-
+			
 		/// <summary>Returns the sine in radians from a lookup table.</summary>
 		/// <summary>Returns the sine in radians from a lookup table.</summary>
 		static public float SinDeg (float degrees) {
 		static public float SinDeg (float degrees) {
-			return sin[(int)(degrees * degToIndex) & SIN_MASK];
+			return sin[(int)(degrees * DegToIndex) & SIN_MASK];
 		}
 		}
-
+			
 		/// <summary>Returns the cosine in radians from a lookup table.</summary>
 		/// <summary>Returns the cosine in radians from a lookup table.</summary>
 		static public float CosDeg (float degrees) {
 		static public float CosDeg (float degrees) {
-			return sin[(int)((degrees + 90) * degToIndex) & SIN_MASK];
+			return sin[(int)((degrees + 90) * DegToIndex) & SIN_MASK];
 		}
 		}
 
 
 		/// <summary>Returns atan2 in radians, faster but less accurate than Math.Atan2. Average error of 0.00231 radians (0.1323
 		/// <summary>Returns atan2 in radians, faster but less accurate than Math.Atan2. Average error of 0.00231 radians (0.1323

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

@@ -31,7 +31,7 @@
 using System;
 using System;
 
 
 namespace Spine {
 namespace Spine {
-	public class PathConstraint : IUpdatable {
+	public class PathConstraint : IConstraint {
 		private const int NONE = -1, BEFORE = -2, AFTER = -3;
 		private const int NONE = -1, BEFORE = -2, AFTER = -3;
 
 
 		internal PathConstraintData data;
 		internal PathConstraintData data;
@@ -43,6 +43,7 @@ namespace Spine {
 		internal ExposedList<float> world = new ExposedList<float>(), curves = new ExposedList<float>(), lengths = new ExposedList<float>();
 		internal ExposedList<float> world = new ExposedList<float>(), curves = new ExposedList<float>(), lengths = new ExposedList<float>();
 		internal float[] segments = new float[10];
 		internal float[] segments = new float[10];
 
 
+		public int Order { get { return data.order; } }
 		public float Position { get { return position; } set { position = value; } }
 		public float Position { get { return position; } set { position = value; } }
 		public float Spacing { get { return spacing; } set { spacing = value; } }
 		public float Spacing { get { return spacing; } set { spacing = value; } }
 		public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
 		public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
@@ -68,7 +69,7 @@ namespace Spine {
 		public void Apply () {
 		public void Apply () {
 			Update();
 			Update();
 		}
 		}
-
+			
 		public void Update () {
 		public void Update () {
 			PathAttachment attachment = target.Attachment as PathAttachment;
 			PathAttachment attachment = target.Attachment as PathAttachment;
 			if (attachment == null) return;
 			if (attachment == null) return;
@@ -102,14 +103,12 @@ namespace Spine {
 
 
 			float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents,
 			float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents,
 				data.positionMode == PositionMode.Percent, spacingMode == SpacingMode.Percent);
 				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;
 			float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation;
 			bool tip = rotateMode == RotateMode.Chain && offsetRotation == 0;
 			bool tip = rotateMode == RotateMode.Chain && offsetRotation == 0;
 			for (int i = 0, p = 3; i < boneCount; i++, p += 3) {
 			for (int i = 0, p = 3; i < boneCount; i++, p += 3) {
 				Bone bone = (Bone)bones[i];
 				Bone bone = (Bone)bones[i];
-				bone.worldX += (boneX - skeletonX - bone.worldX) * translateMix;
-				bone.worldY += (boneY - skeletonY - bone.worldY) * translateMix;
+				bone.worldX += (boneX - bone.worldX) * translateMix;
+				bone.worldY += (boneY - bone.worldY) * translateMix;
 				float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY;
 				float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY;
 				if (scale) {
 				if (scale) {
 					float length = lengths.Items[i];
 					float length = lengths.Items[i];
@@ -129,7 +128,7 @@ namespace Spine {
 						r = positions[p + 2];
 						r = positions[p + 2];
 					else
 					else
 						r = MathUtils.Atan2(dy, dx);
 						r = MathUtils.Atan2(dy, dx);
-					r -= MathUtils.Atan2(c, a) - offsetRotation * MathUtils.degRad;
+					r -= MathUtils.Atan2(c, a) - offsetRotation * MathUtils.DegRad;
 					if (tip) {
 					if (tip) {
 						cos = MathUtils.Cos(r);
 						cos = MathUtils.Cos(r);
 						sin = MathUtils.Sin(r);
 						sin = MathUtils.Sin(r);
@@ -149,6 +148,7 @@ namespace Spine {
 					bone.c = sin * a + cos * c;
 					bone.c = sin * a + cos * c;
 					bone.d = sin * b + cos * d;
 					bone.d = sin * b + cos * d;
 				}
 				}
+				bone.appliedValid = false;
 			}
 			}
 		}
 		}
 
 
@@ -387,7 +387,7 @@ namespace Spine {
 
 
 		private void AddCurvePosition (float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2,
 		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) {
 			float[] output, int o, bool tangents) {
-			if (p == 0) p = 0.0001f;
+			if (p == 0 || float.IsNaN(p)) p = 0.0001f;
 			float tt = p * p, ttt = tt * p, u = 1 - p, uu = u * u, uuu = uu * u;
 			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 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;
 			float x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt;

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

@@ -32,7 +32,8 @@ using System;
 
 
 namespace Spine {
 namespace Spine {
 	public class PathConstraintData {
 	public class PathConstraintData {
-		internal String name;
+		internal string name;
+		internal int order;
 		internal ExposedList<BoneData> bones = new ExposedList<BoneData>();
 		internal ExposedList<BoneData> bones = new ExposedList<BoneData>();
 		internal SlotData target;
 		internal SlotData target;
 		internal PositionMode positionMode;
 		internal PositionMode positionMode;
@@ -41,8 +42,10 @@ namespace Spine {
 		internal float offsetRotation;
 		internal float offsetRotation;
 		internal float position, spacing, rotateMix, translateMix;
 		internal float position, spacing, rotateMix, translateMix;
 
 
+		public string Name { get { return name; } }
+		public int Order { get { return order; } set { order = value; } }
 		public ExposedList<BoneData> Bones { get { return bones; } }
 		public ExposedList<BoneData> Bones { get { return bones; } }
-		public SlotData Target { get { return target; } set { target = value; } }
+		public SlotData Target { get { return target; } set { target = value; } }			
 		public PositionMode PositionMode { get { return positionMode; } set { positionMode = value; } }
 		public PositionMode PositionMode { get { return positionMode; } set { positionMode = value; } }
 		public SpacingMode SpacingMode { get { return spacingMode; } set { spacingMode = value; } }
 		public SpacingMode SpacingMode { get { return spacingMode; } set { spacingMode = value; } }
 		public RotateMode RotateMode { get { return rotateMode; } set { rotateMode = value; } }
 		public RotateMode RotateMode { get { return rotateMode; } set { rotateMode = value; } }
@@ -51,16 +54,19 @@ namespace Spine {
 		public float Spacing { get { return spacing; } set { spacing = value; } }
 		public float Spacing { get { return spacing; } set { spacing = value; } }
 		public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
 		public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
 		public float TranslateMix { get { return translateMix; } set { translateMix = value; } }
 		public float TranslateMix { get { return translateMix; } set { translateMix = value; } }
-		public String Name { get { return name; } }
 
 
 		public PathConstraintData (String name) {
 		public PathConstraintData (String name) {
 			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
 			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
 			this.name = name;
 			this.name = name;
 		}
 		}
-	}
 
 
+		public override string ToString () {
+			return name;
+		}
+	}
+	
 	public enum PositionMode {
 	public enum PositionMode {
-		Fixed, Percent
+		Fixed, Percent        
 	}
 	}
 
 
 	public enum SpacingMode {
 	public enum SpacingMode {

+ 103 - 82
spine-csharp/src/Skeleton.cs

@@ -37,10 +37,11 @@ namespace Spine {
 		internal ExposedList<Bone> bones;
 		internal ExposedList<Bone> bones;
 		internal ExposedList<Slot> slots;
 		internal ExposedList<Slot> slots;
 		internal ExposedList<Slot> drawOrder;
 		internal ExposedList<Slot> drawOrder;
-		internal ExposedList<IkConstraint> ikConstraints, ikConstraintsSorted;
+		internal ExposedList<IkConstraint> ikConstraints;
 		internal ExposedList<TransformConstraint> transformConstraints;
 		internal ExposedList<TransformConstraint> transformConstraints;
 		internal ExposedList<PathConstraint> pathConstraints;
 		internal ExposedList<PathConstraint> pathConstraints;
 		internal ExposedList<IUpdatable> updateCache = new ExposedList<IUpdatable>();
 		internal ExposedList<IUpdatable> updateCache = new ExposedList<IUpdatable>();
+		internal ExposedList<Bone> updateCacheReset = new ExposedList<Bone>();
 		internal Skin skin;
 		internal Skin skin;
 		internal float r = 1, g = 1, b = 1, a = 1;
 		internal float r = 1, g = 1, b = 1, a = 1;
 		internal float time;
 		internal float time;
@@ -78,7 +79,7 @@ namespace Spine {
 			foreach (BoneData boneData in data.bones) {
 			foreach (BoneData boneData in data.bones) {
 				Bone bone;
 				Bone bone;
 				if (boneData.parent == null) {
 				if (boneData.parent == null) {
-					bone = new Bone(boneData, this, null);
+					bone = new Bone(boneData, this, null);				
 				} else {
 				} else {
 					Bone parent = bones.Items[boneData.parent.index];
 					Bone parent = bones.Items[boneData.parent.index];
 					bone = new Bone(boneData, this, parent);
 					bone = new Bone(boneData, this, parent);
@@ -97,7 +98,6 @@ namespace Spine {
 			}
 			}
 
 
 			ikConstraints = new ExposedList<IkConstraint>(data.ikConstraints.Count);
 			ikConstraints = new ExposedList<IkConstraint>(data.ikConstraints.Count);
-			ikConstraintsSorted = new ExposedList<IkConstraint>(data.ikConstraints.Count);
 			foreach (IkConstraintData ikConstraintData in data.ikConstraints)
 			foreach (IkConstraintData ikConstraintData in data.ikConstraints)
 				ikConstraints.Add(new IkConstraint(ikConstraintData, this));
 				ikConstraints.Add(new IkConstraint(ikConstraintData, this));
 
 
@@ -123,92 +123,101 @@ namespace Spine {
 			for (int i = 0, n = bones.Count; i < n; i++)
 			for (int i = 0, n = bones.Count; i < n; i++)
 				bones.Items[i].sorted = false;
 				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;
+			ExposedList<IkConstraint> ikConstraints = this.ikConstraints;
+			var transformConstraints = this.transformConstraints;
+			var pathConstraints = this.pathConstraints;
+			int ikCount = IkConstraints.Count, transformCount = transformConstraints.Count, pathCount = pathConstraints.Count;
+			int constraintCount = ikCount + transformCount + pathCount;
+			//outer:
+			for (int i = 0; i < constraintCount; i++) {
+				for (int ii = 0; ii < ikCount; ii++) {
+					IkConstraint constraint = ikConstraints.Items[ii];
+					if (constraint.data.order == i) {
+						SortIkConstraint(constraint);
+						goto outer; //continue outer;
+					}
+				}
+				for (int ii = 0; ii < transformCount; ii++) {
+					TransformConstraint constraint = transformConstraints.Items[ii];
+					if (constraint.data.order == i) {
+						SortTransformConstraint(constraint);
+						goto outer; //continue outer;
+					}
+				}
+				for (int ii = 0; ii < pathCount; ii++) {
+					PathConstraint constraint = pathConstraints.Items[ii];
+					if (constraint.data.order == i) {
+						SortPathConstraint(constraint);
+						goto outer; //continue outer;
+					}
 				}
 				}
-				ikConstraints.Items[ii + 1] = ik;
+				outer: {}
 			}
 			}
-			for (int i = 0, n = ikConstraints.Count; i < n; i++) {
-				IkConstraint constraint = ikConstraints.Items[i];
-				Bone target = constraint.target;
-				SortBone(target);
-
-				ExposedList<Bone> constrained = constraint.bones;
-				Bone parent = constrained.Items[0];
-				SortBone(parent);
 
 
-				updateCache.Add(constraint);
+			for (int i = 0, n = bones.Count; i < n; i++)
+				SortBone(bones.Items[i]);
+		}
 
 
-				SortReset(parent.children);
-				constrained.Items[constrained.Count - 1].sorted = true;
-			}
+		private void SortIkConstraint (IkConstraint constraint) {
+			Bone target = constraint.target;
+			SortBone(target);
 
 
-			ExposedList<PathConstraint> pathConstraints = this.pathConstraints;
-			for (int i = 0, n = pathConstraints.Count; i < n; i++) {
-				PathConstraint constraint = pathConstraints.Items[i];
+			var constrained = constraint.bones;
+			Bone parent = constrained.Items[0];
+			SortBone(parent);
 
 
-				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;
+			if (constrained.Count > 1) {
+				Bone child = constrained.Items[constrained.Count - 1];
+				if (!updateCache.Contains(child))
+					updateCacheReset.Add(child);
 			}
 			}
 
 
-			ExposedList<TransformConstraint> transformConstraints = this.transformConstraints;
-			for (int i = 0, n = transformConstraints.Count; i < n; i++) {
-				TransformConstraint constraint = transformConstraints.Items[i];
+			updateCache.Add(constraint);
 
 
-				SortBone(constraint.target);
+			SortReset(parent.children);
+			constrained.Items[constrained.Count - 1].sorted = true;
+		}
 
 
-				ExposedList<Bone> constrained = constraint.bones;
-				int boneCount = constrained.Count;
-				for (int ii = 0; ii < boneCount; ii++)
-					SortBone(constrained.Items[ii]);
+		private void SortPathConstraint (PathConstraint constraint) {
+			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);
+
+			Attachment attachment = slot.attachment;
+			if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone);
+
+			var 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;
+		}
 
 
-				updateCache.Add(constraint);
+		private void SortTransformConstraint (TransformConstraint constraint) {
+			SortBone(constraint.target);
 
 
-				for (int ii = 0; ii < boneCount; ii++)
-					SortReset(constrained.Items[ii].children);
-				for (int ii = 0; ii < boneCount; ii++)
-					constrained.Items[ii].sorted = true;
-			}
+			var constrained = constraint.bones;
+			int boneCount = constrained.Count;
+			for (int ii = 0; ii < boneCount; ii++)
+				SortBone(constrained.Items[ii]);
 
 
-			for (int i = 0, n = bones.Count; i < n; i++)
-				SortBone(bones.Items[i]);
+			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;
 		}
 		}
 
 
 		private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) {
 		private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) {
@@ -217,18 +226,17 @@ namespace Spine {
 		}
 		}
 
 
 		private void SortPathConstraintAttachment (Attachment attachment, Bone slotBone) {
 		private void SortPathConstraintAttachment (Attachment attachment, Bone slotBone) {
-			var pathAttachment = attachment as PathAttachment;
-			if (pathAttachment == null) return;
-			int[] pathBones = pathAttachment.bones;
+			if (!(attachment is PathAttachment)) return;
+			int[] pathBones = ((PathAttachment)attachment).bones;
 			if (pathBones == null)
 			if (pathBones == null)
 				SortBone(slotBone);
 				SortBone(slotBone);
 			else {
 			else {
-				var bonesItems = this.bones.Items;
+				var bones = this.bones;
 				for (int i = 0, n = pathBones.Length; i < n;) {
 				for (int i = 0, n = pathBones.Length; i < n;) {
 					int nn = pathBones[i++];
 					int nn = pathBones[i++];
 					nn += i;
 					nn += i;
 					while (i < nn)
 					while (i < nn)
-						SortBone(bonesItems[pathBones[i++]]);
+						SortBone(bones.Items[pathBones[i++]]);
 				}
 				}
 			}
 			}
 		}
 		}
@@ -252,6 +260,19 @@ namespace Spine {
 
 
 		/// <summary>Updates the world transform for each bone and applies constraints.</summary>
 		/// <summary>Updates the world transform for each bone and applies constraints.</summary>
 		public void UpdateWorldTransform () {
 		public void UpdateWorldTransform () {
+			var updateCacheReset = this.updateCacheReset;
+			var updateCacheResetItems = updateCacheReset.Items;
+			for (int i = 0, n = updateCacheReset.Count; i < n; i++) {
+				Bone bone = updateCacheResetItems[i];
+				bone.ax = bone.x;
+				bone.ay = bone.y;
+				bone.arotation = bone.rotation;
+				bone.ascaleX = bone.scaleX;
+				bone.ascaleY = bone.scaleY;
+				bone.ashearX = bone.shearX;
+				bone.ashearY = bone.shearY;
+				bone.appliedValid = true;
+			}
 			var updateItems = this.updateCache.Items;
 			var updateItems = this.updateCache.Items;
 			for (int i = 0, n = updateCache.Count; i < n; i++)
 			for (int i = 0, n = updateCache.Count; i < n; i++)
 				updateItems[i].Update();
 				updateItems[i].Update();
@@ -416,7 +437,7 @@ namespace Spine {
 			}
 			}
 			throw new Exception("Slot not found: " + slotName);
 			throw new Exception("Slot not found: " + slotName);
 		}
 		}
-
+			
 		/// <returns>May be null.</returns>
 		/// <returns>May be null.</returns>
 		public IkConstraint FindIkConstraint (String constraintName) {
 		public IkConstraint FindIkConstraint (String constraintName) {
 			if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
 			if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");

+ 27 - 15
spine-csharp/src/SkeletonBinary.cs

@@ -74,7 +74,7 @@ namespace Spine {
 			this.attachmentLoader = attachmentLoader;
 			this.attachmentLoader = attachmentLoader;
 			Scale = 1;
 			Scale = 1;
 		}
 		}
-
+			
 		#if !ISUNITY && WINDOWS_STOREAPP
 		#if !ISUNITY && WINDOWS_STOREAPP
 		private async Task<SkeletonData> ReadFile(string path) {
 		private async Task<SkeletonData> ReadFile(string path) {
 			var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
 			var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
@@ -103,6 +103,14 @@ namespace Spine {
 
 
 		#endif // WINDOWS_STOREAPP
 		#endif // WINDOWS_STOREAPP
 
 
+		public static readonly TransformMode[] TransformModeValues = {
+			TransformMode.Normal,
+			TransformMode.OnlyTranslation,
+			TransformMode.NoRotationOrReflection,
+			TransformMode.NoScale,
+			TransformMode.NoScaleOrReflection
+		};
+
 		public SkeletonData ReadSkeletonData (Stream input) {
 		public SkeletonData ReadSkeletonData (Stream input) {
 			if (input == null) throw new ArgumentNullException("input");
 			if (input == null) throw new ArgumentNullException("input");
 			float scale = Scale;
 			float scale = Scale;
@@ -118,6 +126,7 @@ namespace Spine {
 			bool nonessential = ReadBoolean(input);
 			bool nonessential = ReadBoolean(input);
 
 
 			if (nonessential) {
 			if (nonessential) {
+				skeletonData.fps = ReadFloat(input);
 				skeletonData.imagesPath = ReadString(input);
 				skeletonData.imagesPath = ReadString(input);
 				if (skeletonData.imagesPath.Length == 0) skeletonData.imagesPath = null;
 				if (skeletonData.imagesPath.Length == 0) skeletonData.imagesPath = null;
 			}
 			}
@@ -127,7 +136,7 @@ namespace Spine {
 				String name = ReadString(input);
 				String name = ReadString(input);
 				BoneData parent = i == 0 ? null : skeletonData.bones.Items[ReadVarint(input, true)];
 				BoneData parent = i == 0 ? null : skeletonData.bones.Items[ReadVarint(input, true)];
 				BoneData data = new BoneData(i, name, parent);
 				BoneData data = new BoneData(i, name, parent);
-				data.rotation = ReadFloat(input);
+				data.rotation = ReadFloat(input);		
 				data.x = ReadFloat(input) * scale;
 				data.x = ReadFloat(input) * scale;
 				data.y = ReadFloat(input) * scale;
 				data.y = ReadFloat(input) * scale;
 				data.scaleX = ReadFloat(input);
 				data.scaleX = ReadFloat(input);
@@ -135,8 +144,7 @@ namespace Spine {
 				data.shearX = ReadFloat(input);
 				data.shearX = ReadFloat(input);
 				data.shearY = ReadFloat(input);
 				data.shearY = ReadFloat(input);
 				data.length = ReadFloat(input) * scale;
 				data.length = ReadFloat(input) * scale;
-				data.inheritRotation = ReadBoolean(input);
-				data.inheritScale = ReadBoolean(input);
+				data.transformMode = TransformModeValues[ReadVarint(input, true)];
 				if (nonessential) ReadInt(input); // Skip bone color.
 				if (nonessential) ReadInt(input); // Skip bone color.
 				skeletonData.bones.Add(data);
 				skeletonData.bones.Add(data);
 			}
 			}
@@ -159,6 +167,7 @@ namespace Spine {
 			// IK constraints.
 			// IK constraints.
 			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
 			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
 				IkConstraintData data = new IkConstraintData(ReadString(input));
 				IkConstraintData data = new IkConstraintData(ReadString(input));
+				data.order = ReadVarint(input, true);
 				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++)
 				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++)
 					data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]);
 					data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]);
 				data.target = skeletonData.bones.Items[ReadVarint(input, true)];
 				data.target = skeletonData.bones.Items[ReadVarint(input, true)];
@@ -170,8 +179,9 @@ namespace Spine {
 			// Transform constraints.
 			// Transform constraints.
 			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
 			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
 				TransformConstraintData data = new TransformConstraintData(ReadString(input));
 				TransformConstraintData data = new TransformConstraintData(ReadString(input));
+				data.order = ReadVarint(input, true);
 				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++)
 				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++)
-					data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]);
+				    data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]);
 				data.target = skeletonData.bones.Items[ReadVarint(input, true)];
 				data.target = skeletonData.bones.Items[ReadVarint(input, true)];
 				data.offsetRotation = ReadFloat(input);
 				data.offsetRotation = ReadFloat(input);
 				data.offsetX = ReadFloat(input) * scale;
 				data.offsetX = ReadFloat(input) * scale;
@@ -189,6 +199,7 @@ namespace Spine {
 			// Path constraints
 			// Path constraints
 			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
 			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
 				PathConstraintData data = new PathConstraintData(ReadString(input));
 				PathConstraintData data = new PathConstraintData(ReadString(input));
+				data.order = ReadVarint(input, true);
 				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++)
 				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++)
 					data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]);
 					data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]);
 				data.target = skeletonData.slots.Items[ReadVarint(input, true)];
 				data.target = skeletonData.slots.Items[ReadVarint(input, true)];
@@ -261,7 +272,8 @@ namespace Spine {
 				int slotIndex = ReadVarint(input, true);
 				int slotIndex = ReadVarint(input, true);
 				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) {
 				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) {
 					String name = ReadString(input);
 					String name = ReadString(input);
-					skin.AddAttachment(slotIndex, name, ReadAttachment(input, skin, slotIndex, name, nonessential));
+					Attachment attachment = ReadAttachment(input, skin, slotIndex, name, nonessential);
+					if (attachment != null) skin.AddAttachment(slotIndex, name, attachment);
 				}
 				}
 			}
 			}
 			return skin;
 			return skin;
@@ -277,7 +289,7 @@ namespace Spine {
 			switch (type) {
 			switch (type) {
 			case AttachmentType.Region: {
 			case AttachmentType.Region: {
 					String path = ReadString(input);
 					String path = ReadString(input);
-					float rotation = ReadFloat(input);
+					float rotation = ReadFloat(input);		
 					float x = ReadFloat(input);
 					float x = ReadFloat(input);
 					float y = ReadFloat(input);
 					float y = ReadFloat(input);
 					float scaleX = ReadFloat(input);
 					float scaleX = ReadFloat(input);
@@ -308,18 +320,18 @@ namespace Spine {
 					int vertexCount = ReadVarint(input, true);
 					int vertexCount = ReadVarint(input, true);
 					Vertices vertices = ReadVertices(input, vertexCount);
 					Vertices vertices = ReadVertices(input, vertexCount);
 					if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; // Avoid unused local warning.
 					if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; // Avoid unused local warning.
-
+					
 					BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name);
 					BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name);
 					if (box == null) return null;
 					if (box == null) return null;
 					box.worldVerticesLength = vertexCount << 1;
 					box.worldVerticesLength = vertexCount << 1;
 					box.vertices = vertices.vertices;
 					box.vertices = vertices.vertices;
-					box.bones = vertices.bones;
+					box.bones = vertices.bones;                    
 					return box;
 					return box;
 				}
 				}
 			case AttachmentType.Mesh: {
 			case AttachmentType.Mesh: {
 					String path = ReadString(input);
 					String path = ReadString(input);
 					int color = ReadInt(input);
 					int color = ReadInt(input);
-					int vertexCount = ReadVarint(input, true);
+					int vertexCount = ReadVarint(input, true);					
 					float[] uvs = ReadFloatArray(input, vertexCount << 1, 1);
 					float[] uvs = ReadFloatArray(input, vertexCount << 1, 1);
 					int[] triangles = ReadShortArray(input);
 					int[] triangles = ReadShortArray(input);
 					Vertices vertices = ReadVertices(input, vertexCount);
 					Vertices vertices = ReadVertices(input, vertexCount);
@@ -400,8 +412,8 @@ namespace Spine {
 					path.vertices = vertices.vertices;
 					path.vertices = vertices.vertices;
 					path.bones = vertices.bones;
 					path.bones = vertices.bones;
 					path.lengths = lengths;
 					path.lengths = lengths;
-					return path;
-				}
+					return path;                    
+				}			
 			}
 			}
 			return null;
 			return null;
 		}
 		}
@@ -540,7 +552,7 @@ namespace Spine {
 			}
 			}
 
 
 			// IK timelines.
 			// IK timelines.
-			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {				
 				int index = ReadVarint(input, true);
 				int index = ReadVarint(input, true);
 				int frameCount = ReadVarint(input, true);
 				int frameCount = ReadVarint(input, true);
 				IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount);
 				IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount);
@@ -587,7 +599,7 @@ namespace Spine {
 									if (data.positionMode == PositionMode.Fixed) timelineScale = scale;
 									if (data.positionMode == PositionMode.Fixed) timelineScale = scale;
 								}
 								}
 								timeline.pathConstraintIndex = index;
 								timeline.pathConstraintIndex = index;
-								for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
+								for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {                                    
 									timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale);
 									timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale);
 									if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
 									if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
 								}
 								}
@@ -651,7 +663,7 @@ namespace Spine {
 
 
 							timeline.SetFrame(frameIndex, time, deform);
 							timeline.SetFrame(frameIndex, time, deform);
 							if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
 							if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
-						}
+						}							
 						timelines.Add(timeline);
 						timelines.Add(timeline);
 						duration = Math.Max(duration, timeline.frames[frameCount - 1]);
 						duration = Math.Max(duration, timeline.frames[frameCount - 1]);
 					}
 					}

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

@@ -81,7 +81,14 @@ namespace Spine {
 				boundingBox.ComputeWorldVertices(slot, polygon.Vertices);
 				boundingBox.ComputeWorldVertices(slot, polygon.Vertices);
 			}
 			}
 
 
-			if (updateAabb) aabbCompute();
+			if (updateAabb) {
+				aabbCompute();
+			} else {
+				minX = int.MinValue;
+				minY = int.MinValue;
+				maxX = int.MaxValue;
+				maxY = int.MaxValue;
+			}
 		}
 		}
 
 
 		private void aabbCompute () {
 		private void aabbCompute () {

+ 12 - 5
spine-csharp/src/SkeletonData.cs

@@ -32,7 +32,7 @@ using System;
 
 
 namespace Spine {
 namespace Spine {
 	public class SkeletonData {
 	public class SkeletonData {
-		internal String name;
+		internal string name;
 		internal ExposedList<BoneData> bones = new ExposedList<BoneData>();
 		internal ExposedList<BoneData> bones = new ExposedList<BoneData>();
 		internal ExposedList<SlotData> slots = new ExposedList<SlotData>();
 		internal ExposedList<SlotData> slots = new ExposedList<SlotData>();
 		internal ExposedList<Skin> skins = new ExposedList<Skin>();
 		internal ExposedList<Skin> skins = new ExposedList<Skin>();
@@ -43,7 +43,11 @@ namespace Spine {
 		internal ExposedList<TransformConstraintData> transformConstraints = new ExposedList<TransformConstraintData>();
 		internal ExposedList<TransformConstraintData> transformConstraints = new ExposedList<TransformConstraintData>();
 		internal ExposedList<PathConstraintData> pathConstraints = new ExposedList<PathConstraintData>();
 		internal ExposedList<PathConstraintData> pathConstraints = new ExposedList<PathConstraintData>();
 		internal float width, height;
 		internal float width, height;
-		internal String version, hash, imagesPath;
+		internal string version, hash;
+
+		// Nonessential.
+		internal float fps;
+		internal string imagesPath;
 
 
 		public String Name { get { return name; } set { name = value; } }
 		public String Name { get { return name; } set { name = value; } }
 		public ExposedList<BoneData> Bones { get { return bones; } } // Ordered parents first.
 		public ExposedList<BoneData> Bones { get { return bones; } } // Ordered parents first.
@@ -56,11 +60,14 @@ namespace Spine {
 		public ExposedList<IkConstraintData> IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } }
 		public ExposedList<IkConstraintData> IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } }
 		public ExposedList<TransformConstraintData> TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } }
 		public ExposedList<TransformConstraintData> TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } }
 		public ExposedList<PathConstraintData> PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } }
 		public ExposedList<PathConstraintData> PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } }
+
 		public float Width { get { return width; } set { width = value; } }
 		public float Width { get { return width; } set { width = value; } }
 		public float Height { get { return height; } set { height = value; } }
 		public float Height { get { return height; } set { height = value; } }
-		/// <summary>The Spine version used to export this data.</summary>
-		public String Version { get { return version; } set { version = value; } }
-		public String Hash { get { return hash; } set { hash = value; } }
+		/// <summary>The Spine version used to export this data, or null.</summary>
+		public string Version { get { return version; } set { version = value; } }
+		public string Hash { get { return hash; } set { hash = value; } }
+		public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } }
+		public float Fps { get { return fps; } set { fps = value; } }
 
 
 		// --- Bones.
 		// --- Bones.
 
 

+ 13 - 7
spine-csharp/src/SkeletonJson.cs

@@ -103,6 +103,8 @@ namespace Spine {
 				skeletonData.version = (String)skeletonMap["spine"];
 				skeletonData.version = (String)skeletonMap["spine"];
 				skeletonData.width = GetFloat(skeletonMap, "width", 0);
 				skeletonData.width = GetFloat(skeletonMap, "width", 0);
 				skeletonData.height = GetFloat(skeletonMap, "height", 0);
 				skeletonData.height = GetFloat(skeletonMap, "height", 0);
+				skeletonData.fps = GetFloat(skeletonMap, "fps", 0);
+				skeletonData.imagesPath = GetString(skeletonMap, "images", null);
 			}
 			}
 
 
 			// Bones.
 			// Bones.
@@ -122,8 +124,9 @@ namespace Spine {
 				data.scaleY = GetFloat(boneMap, "scaleY", 1);
 				data.scaleY = GetFloat(boneMap, "scaleY", 1);
 				data.shearX = GetFloat(boneMap, "shearX", 0);
 				data.shearX = GetFloat(boneMap, "shearX", 0);
 				data.shearY = GetFloat(boneMap, "shearY", 0);
 				data.shearY = GetFloat(boneMap, "shearY", 0);
-				data.inheritRotation = GetBoolean(boneMap, "inheritRotation", true);
-				data.inheritScale = GetBoolean(boneMap, "inheritScale", true);
+
+				string tm = GetString(boneMap, "transform", TransformMode.Normal.ToString());
+				data.transformMode = (TransformMode)Enum.Parse(typeof(TransformMode), tm, true);
 
 
 				skeletonData.bones.Add(data);
 				skeletonData.bones.Add(data);
 			}
 			}
@@ -144,7 +147,7 @@ namespace Spine {
 						data.b = ToColor(color, 2);
 						data.b = ToColor(color, 2);
 						data.a = ToColor(color, 3);
 						data.a = ToColor(color, 3);
 					}
 					}
-
+						
 					data.attachmentName = GetString(slotMap, "attachment", null);
 					data.attachmentName = GetString(slotMap, "attachment", null);
 					if (slotMap.ContainsKey("blend"))
 					if (slotMap.ContainsKey("blend"))
 						data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (String)slotMap["blend"], false);
 						data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (String)slotMap["blend"], false);
@@ -158,13 +161,14 @@ namespace Spine {
 			if (root.ContainsKey("ik")) {
 			if (root.ContainsKey("ik")) {
 				foreach (Dictionary<String, Object> constraintMap in (List<Object>)root["ik"]) {
 				foreach (Dictionary<String, Object> constraintMap in (List<Object>)root["ik"]) {
 					IkConstraintData data = new IkConstraintData((String)constraintMap["name"]);
 					IkConstraintData data = new IkConstraintData((String)constraintMap["name"]);
+					data.order = GetInt(constraintMap, "order", 0);
 
 
 					foreach (String boneName in (List<Object>)constraintMap["bones"]) {
 					foreach (String boneName in (List<Object>)constraintMap["bones"]) {
 						BoneData bone = skeletonData.FindBone(boneName);
 						BoneData bone = skeletonData.FindBone(boneName);
 						if (bone == null) throw new Exception("IK constraint bone not found: " + boneName);
 						if (bone == null) throw new Exception("IK constraint bone not found: " + boneName);
 						data.bones.Add(bone);
 						data.bones.Add(bone);
 					}
 					}
-
+					
 					String targetName = (String)constraintMap["target"];
 					String targetName = (String)constraintMap["target"];
 					data.target = skeletonData.FindBone(targetName);
 					data.target = skeletonData.FindBone(targetName);
 					if (data.target == null) throw new Exception("Target bone not found: " + targetName);
 					if (data.target == null) throw new Exception("Target bone not found: " + targetName);
@@ -180,6 +184,7 @@ namespace Spine {
 			if (root.ContainsKey("transform")) {
 			if (root.ContainsKey("transform")) {
 				foreach (Dictionary<String, Object> constraintMap in (List<Object>)root["transform"]) {
 				foreach (Dictionary<String, Object> constraintMap in (List<Object>)root["transform"]) {
 					TransformConstraintData data = new TransformConstraintData((String)constraintMap["name"]);
 					TransformConstraintData data = new TransformConstraintData((String)constraintMap["name"]);
+					data.order = GetInt(constraintMap, "order", 0);
 
 
 					foreach (String boneName in (List<Object>)constraintMap["bones"]) {
 					foreach (String boneName in (List<Object>)constraintMap["bones"]) {
 						BoneData bone = skeletonData.FindBone(boneName);
 						BoneData bone = skeletonData.FindBone(boneName);
@@ -211,6 +216,7 @@ namespace Spine {
 			if(root.ContainsKey("path")) {
 			if(root.ContainsKey("path")) {
 				foreach (Dictionary<String, Object> constraintMap in (List<Object>)root["path"]) {
 				foreach (Dictionary<String, Object> constraintMap in (List<Object>)root["path"]) {
 					PathConstraintData data = new PathConstraintData((String)constraintMap["name"]);
 					PathConstraintData data = new PathConstraintData((String)constraintMap["name"]);
+					data.order = GetInt(constraintMap, "order", 0);
 
 
 					foreach (String boneName in (List<Object>)constraintMap["bones"]) {
 					foreach (String boneName in (List<Object>)constraintMap["bones"]) {
 						BoneData bone = skeletonData.FindBone(boneName);
 						BoneData bone = skeletonData.FindBone(boneName);
@@ -276,7 +282,7 @@ namespace Spine {
 					var data = new EventData(entry.Key);
 					var data = new EventData(entry.Key);
 					data.Int = GetInt(entryMap, "int", 0);
 					data.Int = GetInt(entryMap, "int", 0);
 					data.Float = GetFloat(entryMap, "float", 0);
 					data.Float = GetFloat(entryMap, "float", 0);
-					data.String = GetString(entryMap, "string", null);
+					data.String = GetString(entryMap, "string", string.Empty);
 					skeletonData.events.Add(data);
 					skeletonData.events.Add(data);
 				}
 				}
 			}
 			}
@@ -289,7 +295,7 @@ namespace Spine {
 					} catch (Exception e) {
 					} catch (Exception e) {
 						throw new Exception("Error reading animation: " + entry.Key, e);
 						throw new Exception("Error reading animation: " + entry.Key, e);
 					}
 					}
-				}
+				}   
 			}
 			}
 
 
 			skeletonData.bones.TrimExcess();
 			skeletonData.bones.TrimExcess();
@@ -632,7 +638,7 @@ namespace Spine {
 							var timeline = new DeformTimeline(values.Count);
 							var timeline = new DeformTimeline(values.Count);
 							timeline.slotIndex = slotIndex;
 							timeline.slotIndex = slotIndex;
 							timeline.attachment = attachment;
 							timeline.attachment = attachment;
-
+							
 							int frameIndex = 0;
 							int frameIndex = 0;
 							foreach (Dictionary<String, Object> valueMap in values) {
 							foreach (Dictionary<String, Object> valueMap in values) {
 								float[] deform;
 								float[] deform;

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

@@ -79,7 +79,7 @@ namespace Spine {
 			foreach (KeyValuePair<AttachmentKeyTuple, Attachment> entry in oldSkin.attachments) {
 			foreach (KeyValuePair<AttachmentKeyTuple, Attachment> entry in oldSkin.attachments) {
 				int slotIndex = entry.Key.slotIndex;
 				int slotIndex = entry.Key.slotIndex;
 				Slot slot = skeleton.slots.Items[slotIndex];
 				Slot slot = skeleton.slots.Items[slotIndex];
-				if (slot.attachment == entry.Value) {
+				if (slot.Attachment == entry.Value) {
 					Attachment attachment = GetAttachment(slotIndex, entry.Key.name);
 					Attachment attachment = GetAttachment(slotIndex, entry.Key.name);
 					if (attachment != null) slot.Attachment = attachment;
 					if (attachment != null) slot.Attachment = attachment;
 				}
 				}

+ 21 - 12
spine-csharp/src/TransformConstraint.cs

@@ -31,13 +31,14 @@
 using System;
 using System;
 
 
 namespace Spine {
 namespace Spine {
-	public class TransformConstraint : IUpdatable {
+	public class TransformConstraint : IConstraint {
 		internal TransformConstraintData data;
 		internal TransformConstraintData data;
 		internal ExposedList<Bone> bones;
 		internal ExposedList<Bone> bones;
 		internal Bone target;
 		internal Bone target;
 		internal float rotateMix, translateMix, scaleMix, shearMix;
 		internal float rotateMix, translateMix, scaleMix, shearMix;
 
 
 		public TransformConstraintData Data { get { return data; } }
 		public TransformConstraintData Data { get { return data; } }
+		public int Order { get { return data.order; } }
 		public ExposedList<Bone> Bones { get { return bones; } }
 		public ExposedList<Bone> Bones { get { return bones; } }
 		public Bone Target { get { return target; } set { target = value; } }
 		public Bone Target { get { return target; } set { target = value; } }
 		public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
 		public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
@@ -57,7 +58,7 @@ namespace Spine {
 			bones = new ExposedList<Bone>();
 			bones = new ExposedList<Bone>();
 			foreach (BoneData boneData in data.bones)
 			foreach (BoneData boneData in data.bones)
 				bones.Add (skeleton.FindBone (boneData.name));
 				bones.Add (skeleton.FindBone (boneData.name));
-
+			
 			target = skeleton.FindBone(data.target.name);
 			target = skeleton.FindBone(data.target.name);
 		}
 		}
 
 
@@ -69,13 +70,15 @@ namespace Spine {
 			float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix;
 			float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix;
 			Bone target = this.target;
 			Bone target = this.target;
 			float ta = target.a, tb = target.b, tc = target.c, td = target.d;
 			float ta = target.a, tb = target.b, tc = target.c, td = target.d;
-			ExposedList<Bone> bones = this.bones;
+			var bones = this.bones;
+			var bonesItems = bones.Items;
 			for (int i = 0, n = bones.Count; i < n; i++) {
 			for (int i = 0, n = bones.Count; i < n; i++) {
-				Bone bone = bones.Items[i];
+				Bone bone = bonesItems[i];
+				bool modified = false;
 
 
-				if (rotateMix > 0) {
+				if (rotateMix != 0) {
 					float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
 					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;
+					float r = MathUtils.Atan2(tc, ta) - MathUtils.Atan2(c, a) + data.offsetRotation * MathUtils.DegRad;
 					if (r > MathUtils.PI)
 					if (r > MathUtils.PI)
 						r -= MathUtils.PI2;
 						r -= MathUtils.PI2;
 					else if (r < -MathUtils.PI) r += MathUtils.PI2;
 					else if (r < -MathUtils.PI) r += MathUtils.PI2;
@@ -85,26 +88,29 @@ namespace Spine {
 					bone.b = cos * b - sin * d;
 					bone.b = cos * b - sin * d;
 					bone.c = sin * a + cos * c;
 					bone.c = sin * a + cos * c;
 					bone.d = sin * b + cos * d;
 					bone.d = sin * b + cos * d;
+					modified = true;
 				}
 				}
 
 
-				if (translateMix > 0) {
+				if (translateMix != 0) {
 					float tempx, tempy;
 					float tempx, tempy;
 					target.LocalToWorld(data.offsetX, data.offsetY, out tempx, out tempy);
 					target.LocalToWorld(data.offsetX, data.offsetY, out tempx, out tempy);
 					bone.worldX += (tempx - bone.worldX) * translateMix;
 					bone.worldX += (tempx - bone.worldX) * translateMix;
 					bone.worldY += (tempy - bone.worldY) * translateMix;
 					bone.worldY += (tempy - bone.worldY) * translateMix;
+					modified = true;
 				}
 				}
 
 
 				if (scaleMix > 0) {
 				if (scaleMix > 0) {
-					float bs = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c);
+					float s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c);
 					float ts = (float)Math.Sqrt(ta * ta + tc * tc);
 					float ts = (float)Math.Sqrt(ta * ta + tc * tc);
-					float s = bs > 0.00001f ? (bs + (ts - bs + data.offsetScaleX) * scaleMix) / bs : 0;
+					if (s > 0.00001f) s = (s + (ts - s + data.offsetScaleX) * scaleMix) / s;
 					bone.a *= s;
 					bone.a *= s;
 					bone.c *= s;
 					bone.c *= s;
-					bs = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d);
+					s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d);
 					ts = (float)Math.Sqrt(tb * tb + td * td);
 					ts = (float)Math.Sqrt(tb * tb + td * td);
-					s = bs > 0.00001f ? (bs + (ts - bs + data.offsetScaleY) * scaleMix) / bs : 0;
+					if (s > 0.00001f) s = (s + (ts - s + data.offsetScaleY) * scaleMix) / s;
 					bone.b *= s;
 					bone.b *= s;
 					bone.d *= s;
 					bone.d *= s;
+					modified = true;
 				}
 				}
 
 
 				if (shearMix > 0) {
 				if (shearMix > 0) {
@@ -114,11 +120,14 @@ namespace Spine {
 					if (r > MathUtils.PI)
 					if (r > MathUtils.PI)
 						r -= MathUtils.PI2;
 						r -= MathUtils.PI2;
 					else if (r < -MathUtils.PI) r += MathUtils.PI2;
 					else if (r < -MathUtils.PI) r += MathUtils.PI2;
-					r = by + (r + data.offsetShearY * MathUtils.degRad) * shearMix;
+					r = by + (r + data.offsetShearY * MathUtils.DegRad) * shearMix;
 					float s = (float)Math.Sqrt(b * b + d * d);
 					float s = (float)Math.Sqrt(b * b + d * d);
 					bone.b = MathUtils.Cos(r) * s;
 					bone.b = MathUtils.Cos(r) * s;
 					bone.d = MathUtils.Sin(r) * s;
 					bone.d = MathUtils.Sin(r) * s;
+					modified = true;
 				}
 				}
+
+				if (modified) bone.appliedValid = false;
 			}
 			}
 		}
 		}
 
 

+ 2 - 0
spine-csharp/src/TransformConstraintData.cs

@@ -33,12 +33,14 @@ using System;
 namespace Spine {
 namespace Spine {
 	public class TransformConstraintData {
 	public class TransformConstraintData {
 		internal String name;
 		internal String name;
+		internal int order;
 		internal ExposedList<BoneData> bones = new ExposedList<BoneData>();
 		internal ExposedList<BoneData> bones = new ExposedList<BoneData>();
 		internal BoneData target;
 		internal BoneData target;
 		internal float rotateMix, translateMix, scaleMix, shearMix;
 		internal float rotateMix, translateMix, scaleMix, shearMix;
 		internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY;
 		internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY;
 
 
 		public String Name { get { return name; } }
 		public String Name { get { return name; } }
+		public int Order { get { return order; } set { order = value; } }
 		public ExposedList<BoneData> Bones { get { return bones; } }
 		public ExposedList<BoneData> Bones { get { return bones; } }
 		public BoneData Target { get { return target; } set { target = value; } }
 		public BoneData Target { get { return target; } set { target = value; } }
 		public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
 		public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }

+ 16 - 15
spine-monogame/LICENSE

@@ -1,16 +1,17 @@
-Spine Runtimes Software License v2.5
+Spine Runtimes Software License
+Version 2.3
 
 
-Copyright (c) 2013-2016, Esoteric Software
+Copyright (c) 2013-2015, Esoteric Software
 All rights reserved.
 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 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 develop new applications using the Spine Runtimes or otherwise
-create derivative works or improvements of the Spine Runtimes or (b) remove,
-delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+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
 or other intellectual property or proprietary rights notices on or in the
 Software, including any copy thereof. Redistributions in binary or source
 Software, including any copy thereof. Redistributions in binary or source
 form must include this license and terms.
 form must include this license and terms.
@@ -20,8 +21,8 @@ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF
-USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
-IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGE.
+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.

+ 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 version
 
 
-spine-monogame works with data exported from Spine 3.4.02.
+spine-monogame works with data exported from Spine 3.5.x.
 
 
 spine-monogame supports all Spine features.
 spine-monogame supports all Spine features.
 
 

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

@@ -104,12 +104,12 @@ public class BasicPlatformerController : MonoBehaviour {
 	}
 	}
 
 
 	void Start () {
 	void Start () {
-		//register a callback for Spine Events (in this case, Footstep)
+		// Register a callback for Spine Events (in this case, Footstep)
 		skeletonAnimation.state.Event += HandleEvent;
 		skeletonAnimation.state.Event += HandleEvent;
 	}
 	}
 
 
-	void HandleEvent (Spine.AnimationState state, int trackIndex, Spine.Event e) {
-		//play some sound if footstep event fired
+	void HandleEvent (Spine.TrackEntry trackEntry, Spine.Event e) {
+		// Play some sound if footstep event fired
 		if (e.Data.Name == footstepEventName) {
 		if (e.Data.Name == footstepEventName) {
 			footstepAudioSource.Stop();
 			footstepAudioSource.Stop();
 			footstepAudioSource.pitch = GetRandomPitch(0.2f);
 			footstepAudioSource.pitch = GetRandomPitch(0.2f);

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

@@ -58,7 +58,7 @@ public class Raptor : MonoBehaviour {
 		StartCoroutine(GunGrabRoutine());
 		StartCoroutine(GunGrabRoutine());
 	}
 	}
 
 
-	void HandleEvent (Spine.AnimationState state, int trackIndex, Spine.Event e) {
+	void HandleEvent (Spine.TrackEntry trackEntry, Spine.Event e) {
 		if (e.Data.Name == footstepEvent) {
 		if (e.Data.Name == footstepEvent) {
 			footstepAudioSource.pitch = 0.5f + Random.Range(-0.2f, 0.2f);
 			footstepAudioSource.pitch = 0.5f + Random.Range(-0.2f, 0.2f);
 			footstepAudioSource.Play();
 			footstepAudioSource.Play();

+ 11 - 3
spine-unity/Assets/Examples/Getting Started/Scripts/SpineboyBeginnerView.cs

@@ -60,10 +60,9 @@ public class SpineboyBeginnerView : MonoBehaviour {
 		skeletonAnimation.state.Event += HandleEvent;
 		skeletonAnimation.state.Event += HandleEvent;
 	}
 	}
 
 
-	void HandleEvent (Spine.AnimationState state, int trackIndex, Spine.Event e) {
-		if (e.Data.Name == footstepEventName) {
+	void HandleEvent (Spine.TrackEntry trackEntry, Spine.Event e) {
+		if (e.Data.Name == footstepEventName)
 			PlayFootstepSound();
 			PlayFootstepSound();
-		}
 	}
 	}
 
 
 	void Update () {
 	void Update () {
@@ -113,12 +112,21 @@ public class SpineboyBeginnerView : MonoBehaviour {
 		footstepSource.pitch = GetRandomPitch(footstepPitchOffset);
 		footstepSource.pitch = GetRandomPitch(footstepPitchOffset);
 	}
 	}
 
 
+	[ContextMenu("Check Tracks")]
+	void CheckTracks () {
+		var state = skeletonAnimation.state;
+		Debug.Log(state.GetCurrent(0));
+		Debug.Log(state.GetCurrent(1));
+	}
+
 	#region Transient Actions
 	#region Transient Actions
 	public void PlayShoot () {
 	public void PlayShoot () {
 		// Play the shoot animation on track 1.
 		// Play the shoot animation on track 1.
 		skeletonAnimation.state.SetAnimation(1, shoot, false);
 		skeletonAnimation.state.SetAnimation(1, shoot, false);
+		//skeletonAnimation.state.AddEmptyAnimation(1, 0.1f, 0f);
 		gunSource.pitch = GetRandomPitch(gunsoundPitchOffset);
 		gunSource.pitch = GetRandomPitch(gunsoundPitchOffset);
 		gunSource.Play();
 		gunSource.Play();
+		gunParticles.randomSeed = (uint)Random.Range(0, 100);
 		gunParticles.Play();
 		gunParticles.Play();
 	}
 	}
 
 

+ 1 - 1
spine-unity/Assets/Examples/Scripts/SpineGauge.cs

@@ -66,7 +66,7 @@ public class SpineGauge : MonoBehaviour {
 			if (fillAnimation == null) return;
 			if (fillAnimation == null) return;
 		}
 		}
 			
 			
-		fillAnimation.Apply(skeleton, 0, x, false, null);
+		fillAnimation.Apply(skeleton, 0, x, false, null, 1f, true, false);
 
 
 		skeleton.Update(Time.deltaTime);
 		skeleton.Update(Time.deltaTime);
 		skeleton.UpdateWorldTransform();
 		skeleton.UpdateWorldTransform();

+ 13 - 21
spine-unity/Assets/Examples/Scripts/Spineboy.cs

@@ -29,37 +29,29 @@
  *****************************************************************************/
  *****************************************************************************/
 
 
 using UnityEngine;
 using UnityEngine;
-using System.Collections;
+
 using Spine;
 using Spine;
-using System;
 using Spine.Unity;
 using Spine.Unity;
 
 
 public class Spineboy : MonoBehaviour {
 public class Spineboy : MonoBehaviour {
 	SkeletonAnimation skeletonAnimation;
 	SkeletonAnimation skeletonAnimation;
 
 
 	public void Start () {
 	public void Start () {
-		// Get the SkeletonAnimation component for the GameObject this script is attached to.
-		skeletonAnimation = GetComponent<SkeletonAnimation>();
-		// Call our method any time an animation fires an event.
-		skeletonAnimation.state.Event += Event;
-		// A lambda can be used for the callback instead of a method.
-		skeletonAnimation.state.End += (state, trackIndex) => {
-			Debug.Log("start: " + state.GetCurrent(trackIndex));
-		};
-		// Queue jump to be played on track 0 two seconds after the starting animation.
-		skeletonAnimation.state.AddAnimation(0, "jump", false, 2);
-		// Queue walk to be looped on track 0 after the jump animation.
-		skeletonAnimation.state.AddAnimation(0, "run", true, 0);
+		skeletonAnimation = GetComponent<SkeletonAnimation>(); // Get the SkeletonAnimation component for the GameObject this script is attached to.
+
+		skeletonAnimation.state.Event += HandleEvent;; // Call our method any time an animation fires an event.
+		skeletonAnimation.state.End += (entry) => Debug.Log("start: " + entry.trackIndex); // A lambda can be used for the callback instead of a method.
+
+		skeletonAnimation.state.AddAnimation(0, "jump", false, 2);	// Queue jump to be played on track 0 two seconds after the starting animation.
+		skeletonAnimation.state.AddAnimation(0, "run", true, 0); // Queue walk to be looped on track 0 after the jump animation.
 	}
 	}
-	
-	public void Event (Spine.AnimationState state, int trackIndex, Spine.Event e) {
-		Debug.Log(trackIndex + " " + state.GetCurrent(trackIndex) + ": event " + e + ", " + e.Int);
+
+	void HandleEvent (TrackEntry trackEntry, Spine.Event e) {
+		Debug.Log(trackEntry.trackIndex + " " + trackEntry.animation.name + ": event " + e + ", " + e.Int);
 	}
 	}
 
 
 	public void OnMouseDown () {
 	public void OnMouseDown () {
-		// Set jump to be played on track 0 immediately.
-		skeletonAnimation.state.SetAnimation(0, "jump", false);
-		// Queue walk to be looped on track 0 after the jump animation.
-		skeletonAnimation.state.AddAnimation(0, "run", true, 0);
+		skeletonAnimation.state.SetAnimation(0, "jump", false); // Set jump to be played on track 0 immediately.
+		skeletonAnimation.state.AddAnimation(0, "run", true, 0); // Queue walk to be looped on track 0 after the jump animation.
 	}
 	}
 }
 }

+ 4 - 4
spine-unity/Assets/Examples/Spine/Dragon/dragon.json

@@ -1,5 +1,5 @@
 {
 {
-"skeleton": { "hash": "9MKo2cmJTDc3IPV4B3LRJxWbl04", "spine": "3.3.07", "width": 897, "height": 716.31, "images": "./images/" },
+"skeleton": { "hash": "Mc0suer5LoUZv7DvFA3mLFATKFU", "spine": "3.5.03-beta", "width": 897, "height": 716.36, "fps": 30, "images": "./images/" },
 "bones": [
 "bones": [
 	{ "name": "root", "y": -176.12 },
 	{ "name": "root", "y": -176.12 },
 	{ "name": "COG", "parent": "root", "y": 176.12 },
 	{ "name": "COG", "parent": "root", "y": 176.12 },
@@ -200,7 +200,7 @@
 					{ "time": 0.8, "name": "L_wing03" },
 					{ "time": 0.8, "name": "L_wing03" },
 					{ "time": 0.8333, "name": "L_wing04" },
 					{ "time": 0.8333, "name": "L_wing04" },
 					{ "time": 0.8666, "name": "L_wing05" },
 					{ "time": 0.8666, "name": "L_wing05" },
-					{ "time": 0.9, "name": "L_wing06" },
+					{ "time": 0.8999, "name": "L_wing06" },
 					{ "time": 0.9333, "name": "L_wing07" },
 					{ "time": 0.9333, "name": "L_wing07" },
 					{ "time": 0.9666, "name": "L_wing08" },
 					{ "time": 0.9666, "name": "L_wing08" },
 					{ "time": 1, "name": "L_wing01" }
 					{ "time": 1, "name": "L_wing01" }
@@ -223,7 +223,7 @@
 					{ "time": 0.8, "name": "R_wing03" },
 					{ "time": 0.8, "name": "R_wing03" },
 					{ "time": 0.8333, "name": "R_wing04" },
 					{ "time": 0.8333, "name": "R_wing04" },
 					{ "time": 0.8666, "name": "R_wing05" },
 					{ "time": 0.8666, "name": "R_wing05" },
-					{ "time": 0.9, "name": "R_wing06" },
+					{ "time": 0.8999, "name": "R_wing06" },
 					{ "time": 0.9333, "name": "R_wing07" },
 					{ "time": 0.9333, "name": "R_wing07" },
 					{ "time": 0.9666, "name": "R_wing08" },
 					{ "time": 0.9666, "name": "R_wing08" },
 					{ "time": 1, "name": "R_wing01" }
 					{ "time": 1, "name": "R_wing01" }
@@ -757,7 +757,7 @@
 					{ "time": 0.3333, "angle": 23.93 },
 					{ "time": 0.3333, "angle": 23.93 },
 					{
 					{
 						"time": 0.6666,
 						"time": 0.6666,
-						"angle": 337.8,
+						"angle": 337.79,
 						"curve": [ 0.41, 0, 0.887, 0.75 ]
 						"curve": [ 0.41, 0, 0.887, 0.75 ]
 					},
 					},
 					{ "time": 1, "angle": 0 }
 					{ "time": 1, "angle": 0 }

+ 4 - 10
spine-unity/Assets/Examples/Spine/Eyes/eyes.json

@@ -1,14 +1,8 @@
 {
 {
-"skeleton": {
-	"hash": "4JK7uGWbzO7qeQSyyuOyKLXQ5oI",
-	"spine": "3.3.07",
-	"width": 868,
-	"height": 322,
-	"images": "C:\\Users\\John Eric\\Desktop\\old exports"
-},
+"skeleton": { "hash": "+wD3CxYNDCLBTldmA/MNGhrRDHE", "spine": "3.5.03-beta", "width": 0, "height": 0, "fps": 1, "images": "" },
 "bones": [
 "bones": [
 	{ "name": "root" },
 	{ "name": "root" },
-	{ "name": "L_Eye", "parent": "root", "x": -223.18, "y": 2.99 },
+	{ "name": "L_Eye", "parent": "root", "x": -223.17, "y": 2.99 },
 	{ "name": "R_Eye", "parent": "root", "x": 237.32, "y": 6.4 }
 	{ "name": "R_Eye", "parent": "root", "x": 237.32, "y": 6.4 }
 ],
 ],
 "slots": [
 "slots": [
@@ -26,10 +20,10 @@
 			"EyeWhite": { "x": 3, "y": 2, "width": 700, "height": 148 }
 			"EyeWhite": { "x": 3, "y": 2, "width": 700, "height": 148 }
 		},
 		},
 		"L_Eye": {
 		"L_Eye": {
-			"L_Eye": { "x": -0.82, "y": 2, "width": 148, "height": 148 }
+			"L_Eye": { "x": -0.81, "y": 2, "width": 148, "height": 148 }
 		},
 		},
 		"R_Eye": {
 		"R_Eye": {
-			"R_Eye": { "x": 0.67, "y": -1.4, "width": 148, "height": 148 }
+			"R_Eye": { "x": 0.67, "y": -1.39, "width": 148, "height": 148 }
 		}
 		}
 	}
 	}
 }
 }

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 193 - 193
spine-unity/Assets/Examples/Spine/FootSoldier/FootSoldier.json


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

@@ -1,5 +1,5 @@
 {
 {
-"skeleton": { "hash": "C69Pg+RG3DYyHmt9bOPYVrESJBQ", "spine": "3.3.07", "width": 250, "height": 60, "images": "./images/" },
+"skeleton": { "hash": "l6rgF0e8V0iyd44P62tS+NdEpPk", "spine": "3.5.03-beta", "width": 0, "height": 0, "fps": 1, "images": "./images/" },
 "bones": [
 "bones": [
 	{ "name": "root" },
 	{ "name": "root" },
 	{ "name": "Bar", "parent": "root", "x": -112.29 }
 	{ "name": "Bar", "parent": "root", "x": -112.29 }

+ 35 - 34
spine-unity/Assets/Examples/Spine/Goblins/goblins.json

@@ -1,9 +1,10 @@
 {
 {
 "skeleton": {
 "skeleton": {
-	"hash": "JkhrYH1FWGINlDaz60K/E43JLYM",
-	"spine": "3.3.07",
+	"hash": "23+jIweu3SIB20tkotitVaswslk",
+	"spine": "3.5.03-beta",
 	"width": 266.99,
 	"width": 266.99,
 	"height": 349.62,
 	"height": 349.62,
+	"fps": 30,
 	"images": "C:/Program Files (x86)/Spine/examples/goblins/images/"
 	"images": "C:/Program Files (x86)/Spine/examples/goblins/images/"
 },
 },
 "bones": [
 "bones": [
@@ -59,7 +60,7 @@
 				"type": "mesh",
 				"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 ],
 				"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 ],
 				"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 ],
+				"vertices": [ -26.02, 180.65, -29.23999, 172.19, 41.7, -138.95, 32.41999, -141.1, -41.49, 169.49, -47.22, 174.66998, -47.08, 218.22, -44.27, 218.87 ],
 				"hull": 8,
 				"hull": 8,
 				"edges": [ 12, 10, 10, 8, 8, 6, 4, 6, 4, 2, 2, 0, 12, 14, 0, 14 ],
 				"edges": [ 12, 10, 10, 8, 8, 6, 4, 6, 4, 2, 2, 0, 12, 14, 0, 14 ],
 				"width": 22,
 				"width": 22,
@@ -71,7 +72,7 @@
 				"type": "mesh",
 				"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 ],
 				"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 ],
 				"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 ],
+				"vertices": [ 15.48999, -12.81999, 21.12999, -13.56999, 20.15999, -20.48999, 13.14999, -21.67, 8.13, -58.56, -5.13, -77.04, -9.92, -76.36, -7.78999, -53.59999, -0.02999, -20.36, -5.59999, -17.04, -4.63, -10.17, 1.12, -10.93, 7.46, 30.23999, 26.93, 27.48999 ],
 				"hull": 14,
 				"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 ],
 				"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,
 				"width": 26,
@@ -92,7 +93,7 @@
 				"type": "mesh",
 				"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 ],
 				"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 ],
 				"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 ],
+				"vertices": [ 14.56, 50.41999, 23.12, 35.47, 17.45999, 26.36, 11.56999, 16.86, 3.74, 11.71, -5.88999, -3.91, -11.82999, -37.22999, -8.31, -45.63, 7.75, -44.24, 10.39, -51.33, 19.52, -51.81999, 25.20999, -43.15, 26.12, -47.43, 27.35, -53.15999, 34.84, -53.45999, 35.95999, -47.33, 37.11, -41.08, 43.75, -33.97, 53.58, -19.87, 54.5, 0.02999, 43.31, 19.15999, 35.59999, 23.40999, 35.88999, 40.16999, 28.38999, 49.86999, 10.25, 5.98999, 24.2, 2, 35.54999, 12.47999, 9.39, -25.1, 16.79999, -24.30999, 17.2, -40.65, 20.68, -33.02 ],
 				"hull": 24,
 				"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 ],
 				"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,
 				"width": 103,
@@ -105,7 +106,7 @@
 				"type": "mesh",
 				"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 ],
 				"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 ],
 				"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 ],
+				"vertices": [ 18.6, 8.81, 32.18999, 10.31, 38.02, 1.62, 38.08, -9.63, 32.31, -13.48999, 14.36999, -9.61999, -0.75, -10.77999, -9.84, 2.76999, 1.28999, 10.25 ],
 				"hull": 9,
 				"hull": 9,
 				"edges": [ 14, 16, 16, 0, 0, 2, 2, 4, 6, 4, 6, 8, 8, 10, 12, 14, 10, 12 ],
 				"edges": [ 14, 16, 16, 0, 0, 2, 2, 4, 6, 4, 6, 8, 8, 10, 12, 14, 10, 12 ],
 				"width": 37,
 				"width": 37,
@@ -118,7 +119,7 @@
 				"type": "mesh",
 				"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 ],
 				"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 ],
 				"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 ],
+				"vertices": [ 2.27999, 13.06999, -1.75999, -1.63999, 3.58999, -7.8, 20.25, -6.03999, 37.90999, -5.26999, 58.11999, -3.71, 57.31, 3.33999, 47.77999, 9.51, 31.95, 15.05, 21.98999, 24.11, 24.03, 0.75, 42.20999, 1.15999 ],
 				"hull": 10,
 				"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 ],
 				"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,
 				"width": 65,
@@ -131,7 +132,7 @@
 				"type": "mesh",
 				"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 ],
 				"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 ],
 				"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 ],
+				"vertices": [ -3.10999, 15.42, 10.82999, 22.27, 15.5, 14.55, 18.35, -8.96, 9.47999, -14.31999, -4.57999, -14.3, -11.63, -2.63, -14.89, 13.68, -7.75, 17.98999 ],
 				"hull": 9,
 				"hull": 9,
 				"edges": [ 16, 0, 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10, 12, 14, 16, 12, 14 ],
 				"edges": [ 16, 0, 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10, 12, 14, 16, 12, 14 ],
 				"width": 36,
 				"width": 36,
@@ -142,9 +143,9 @@
 			"left lower leg": {
 			"left lower leg": {
 				"name": "goblin/left-lower-leg",
 				"name": "goblin/left-lower-leg",
 				"type": "mesh",
 				"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 ],
+				"uvs": [ 0.95508, 0.20749, 0.81927, 0.65213, 0.94754, 0.77307, 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 ],
 				"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 ],
+				"vertices": [ -0.18999, 6.82, 30.96999, 10.96, 37.97, 17.32999, 53.88, 12.6, 57.58, 6.30999, 59.34, 0.07999, 55.04, -8.63, 32.99, -9.32999, 20.79, -17.43, -7.26999, -21.55999, -8.18999, -18.29 ],
 				"hull": 11,
 				"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 ],
 				"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,
 				"width": 33,
@@ -157,7 +158,7 @@
 				"type": "mesh",
 				"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 ],
 				"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 ],
 				"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 ],
+				"vertices": [ 15.18, 5.73999, 32.16999, 5.32, 41.79, 0.20999, 36.63, -9.5, 14.88, -9.72, 0.89999, -10.89, -10.65999, -4.73999, -4.65999, 6.53999 ],
 				"hull": 8,
 				"hull": 8,
 				"edges": [ 12, 14, 14, 0, 4, 2, 0, 2, 4, 6, 6, 8, 10, 12, 8, 10 ],
 				"edges": [ 12, 14, 14, 0, 4, 2, 0, 2, 4, 6, 6, 8, 10, 12, 8, 10 ],
 				"width": 29,
 				"width": 29,
@@ -170,7 +171,7 @@
 				"type": "mesh",
 				"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 ],
 				"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 ],
 				"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 ],
+				"vertices": [ 2.32999, 13.06, 33.5, 12.56999, 51, 9.34, 66.31999, 4.30999, 63, -10.71, 43.13, -20.57999, 8.90999, -20.04, -6.78999, -2.64, -6.61, 9.1 ],
 				"hull": 9,
 				"hull": 9,
 				"edges": [ 10, 8, 8, 6, 6, 4, 4, 2, 10, 12, 12, 14, 14, 16, 2, 0, 16, 0 ],
 				"edges": [ 10, 8, 8, 6, 6, 4, 4, 2, 10, 12, 12, 14, 14, 16, 2, 0, 16, 0 ],
 				"width": 33,
 				"width": 33,
@@ -183,7 +184,7 @@
 				"type": "mesh",
 				"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 ],
 				"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 ],
 				"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 ],
+				"vertices": [ 18.62, -11.64999, -3.98, -13.85, -10.27999, 2.75999, -6.90999, 13.89, 0.8, 19.04999, 10.06, 11.51, 16.73999, 12.44999, 22.70999, 17.63999, 31.39999, 12.18999, 30.12, -7.67, 8.05, -6.71 ],
 				"hull": 10,
 				"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 ],
 				"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,
 				"width": 36,
@@ -196,7 +197,7 @@
 				"type": "mesh",
 				"type": "mesh",
 				"uvs": [ 1, 1, 0, 1, 0, 0, 1, 0 ],
 				"uvs": [ 1, 1, 0, 1, 0, 0, 1, 0 ],
 				"triangles": [ 1, 2, 3, 1, 3, 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 ],
+				"vertices": [ 25.37999, -20.72999, -36.61, -20.72999, -36.61, 22.26, 25.37999, 22.26 ],
 				"hull": 4,
 				"hull": 4,
 				"edges": [ 0, 2, 2, 4, 4, 6, 0, 6 ],
 				"edges": [ 0, 2, 2, 4, 4, 6, 0, 6 ],
 				"width": 62,
 				"width": 62,
@@ -209,7 +210,7 @@
 				"type": "mesh",
 				"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 ],
 				"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 ],
 				"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 ],
+				"vertices": [ -4.75, 8.89, 33.02999, 11.73999, 40.99, 5.88999, 41.81, -5.03, 35.52999, -11.13, -2.52999, -9.19999, -8.5, -2.71, -9.09, 5.17999 ],
 				"hull": 8,
 				"hull": 8,
 				"edges": [ 8, 6, 4, 6, 4, 2, 12, 14, 2, 0, 14, 0, 10, 12, 8, 10 ],
 				"edges": [ 8, 6, 4, 6, 4, 2, 12, 14, 2, 0, 14, 0, 10, 12, 8, 10 ],
 				"width": 23,
 				"width": 23,
@@ -222,7 +223,7 @@
 				"type": "mesh",
 				"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 ],
 				"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 ],
 				"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 ],
+				"vertices": [ 17.36, 25.98999, 29.12999, 15.43999, 39.88999, 10.8, 48.13999, 7.23999, 53.84, -2.38, 49.43, -6, 34.84, -6.38999, 16.84, -6.86999, -2.4, -7.38, -7.57999, -3.85999, -7.78, 3.7, -7.94999, 10.1, -2.56999, 22.36, 10.84, 25.96999, 22.13999, 2.75, 37.31, 2.02999 ],
 				"hull": 14,
 				"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 ],
 				"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,
 				"width": 63,
@@ -235,7 +236,7 @@
 				"type": "mesh",
 				"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 ],
 				"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 ],
 				"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 ],
+				"vertices": [ -10.81999, -9.44999, 5.94999, -15.34, 18.87999, -14.89999, 24, -7.5, 25.69, 5.15999, 25.30999, 16.06999, 18.61, 17.44, 15.84, 14.73999, 6.84, 15.02, 0.81, 19.18, -11.40999, 7.82999 ],
 				"hull": 11,
 				"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 ],
 				"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,
 				"width": 36,
@@ -248,7 +249,7 @@
 				"type": "mesh",
 				"type": "mesh",
 				"uvs": [ 0.88538, 0.22262, 0.76167, 0.3594, 0.75088, 0.78308, 0.95326, 0.84981, 1, 0.60302 ],
 				"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 ],
 				"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 ],
+				"vertices": [ -2.81999, 15.97, 2.4, 11.71, 18.07999, 11.89999, 20.27, 19.27, 11.09, 20.62 ],
 				"hull": 5,
 				"hull": 5,
 				"edges": [ 2, 4, 4, 6, 6, 8, 2, 0, 0, 8 ],
 				"edges": [ 2, 4, 4, 6, 6, 8, 2, 0, 0, 8 ],
 				"width": 36,
 				"width": 36,
@@ -261,7 +262,7 @@
 				"type": "mesh",
 				"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 ],
 				"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 ],
 				"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 ],
+				"vertices": [ 6.26, 8.46, 23.31999, 8.03999, 37.09999, 12.89, 41.45, 20.81999, 53.06999, 21.45999, 61.33, 10.06, 65.76999, -1.02999, 58.99, -9.18999, 43.02, -9.81, 16.32999, -20, -12.78999, -9.26 ],
 				"hull": 11,
 				"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 ],
 				"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,
 				"width": 36,
@@ -274,7 +275,7 @@
 				"type": "mesh",
 				"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 ],
 				"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 ],
 				"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 ],
+				"vertices": [ -3.17, -11.05, -9, -0.56999, -1.00999, 10.32999, 16.69, 11.17, 37.40999, 8.19999, 45.45, -1.15999, 36.95, -8.46, 21.2, -7.46999 ],
 				"hull": 8,
 				"hull": 8,
 				"edges": [ 10, 12, 12, 14, 14, 0, 0, 2, 2, 4, 4, 6, 8, 10, 6, 8 ],
 				"edges": [ 10, 12, 12, 14, 14, 0, 0, 2, 2, 4, 4, 6, 8, 10, 6, 8 ],
 				"width": 39,
 				"width": 39,
@@ -287,7 +288,7 @@
 				"type": "mesh",
 				"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 ],
 				"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 ],
 				"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 ],
+				"vertices": [ -9.85, -10.36999, 2.17, -14.06999, 35.49, -13.65999, 47.29, -12.10999, 52.61, -2.25999, 51.63, 5.15999, 40.50999, 10.18, 19.12999, 18.46999, 2.84999, 16.31999, -8.39999, 6.13999 ],
 				"hull": 10,
 				"hull": 10,
 				"edges": [ 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10, 12, 12, 14, 14, 16, 16, 18, 0, 18 ],
 				"edges": [ 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10, 12, 12, 14, 14, 16, 16, 18, 0, 18 ],
 				"width": 34,
 				"width": 34,
@@ -300,7 +301,7 @@
 				"type": "mesh",
 				"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 ],
 				"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 ],
 				"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 ],
+				"vertices": [ 56.93, 27.95, 43.36999, 18.22999, 30.15999, 19.5, 11.52999, 21.28, -2.54999, 10.68999, -10.89, -13.11999, -11.59, -21.22999, 8.53999, -36.11999, 19.64999, -37.08, 28.68, -37.86, 37.68, -34, 45.97999, -30.44, 56.4, -29.06999, 84.77999, -20.92, 87.9, 15.14999, 81.87999, 25.79, 1.66999, -21.01, 10.02999, 2.18, 25.22999, -21.69, 29.97999, 0, 48.54, -8.39, 13.97999, -21.36, 35.9, -15.6 ],
 				"hull": 16,
 				"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 ],
 				"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,
 				"width": 68,
@@ -313,7 +314,7 @@
 				"type": "mesh",
 				"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 ],
 				"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 ],
 				"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 ],
+				"vertices": [ -10.56, 12.86999, 6.53, 9.89999, 25.62, 17.70999, 25.62, 10.56, 11.97, 2.41, -9.09, 2.41, -31, 16.38999, -31, 21.40999, -23.92, 21.40999 ],
 				"hull": 9,
 				"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 ],
 				"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,
 				"width": 55,
@@ -326,7 +327,7 @@
 				"type": "mesh",
 				"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 ],
 				"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 ],
 				"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 ],
+				"vertices": [ -13.22, 5.55999, -8, -2.47, -5.48999, -14.27, -0.63999, -14.35999, 4.78, -14.44999, 15.27, -2.58999, 22.21999, 6.11, 22.92, 14.05, 3.75, 9.43999, -13.07999, 13.71, 4.21, -2.58999, 4.03, 2.04999 ],
 				"hull": 10,
 				"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 ],
 				"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,
 				"width": 36,
@@ -418,7 +419,7 @@
 		"slots": {
 		"slots": {
 			"eyes": {
 			"eyes": {
 				"attachment": [
 				"attachment": [
-					{ "time": 0.7, "name": "eyes closed" },
+					{ "time": 0.6999, "name": "eyes closed" },
 					{ "time": 0.8, "name": null }
 					{ "time": 0.8, "name": null }
 				]
 				]
 			}
 			}
@@ -783,7 +784,7 @@
 							"curve": [ 0.621, 0, 0.75, 1 ]
 							"curve": [ 0.621, 0, 0.75, 1 ]
 						},
 						},
 						{
 						{
-							"time": 0.7,
+							"time": 0.6999,
 							"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 ],
 							"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 ]
 							"curve": [ 0.25, 0, 0.75, 1 ]
 						},
 						},
@@ -818,7 +819,7 @@
 						{
 						{
 							"time": 0.7333,
 							"time": 0.7333,
 							"offset": 8,
 							"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 ]
+							"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.06772, 10.70101 ]
 						},
 						},
 						{
 						{
 							"time": 0.8333,
 							"time": 0.8333,
@@ -846,7 +847,7 @@
 							"vertices": [ -1.04945, -3.10476 ]
 							"vertices": [ -1.04945, -3.10476 ]
 						},
 						},
 						{
 						{
-							"time": 0.7,
+							"time": 0.6999,
 							"offset": 6,
 							"offset": 6,
 							"vertices": [ -1.4245, -6.30616 ]
 							"vertices": [ -1.4245, -6.30616 ]
 						},
 						},
@@ -874,7 +875,7 @@
 						{
 						{
 							"time": 0.3,
 							"time": 0.3,
 							"offset": 2,
 							"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 ]
+							"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.01887 ]
 						},
 						},
 						{
 						{
 							"time": 0.3666,
 							"time": 0.3666,
@@ -890,12 +891,12 @@
 						{
 						{
 							"time": 0.7333,
 							"time": 0.7333,
 							"offset": 4,
 							"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 ]
+							"vertices": [ 1.31462, -6.84099, -0.87905, -12.54479, -5.98509, -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,
 							"time": 0.8,
 							"offset": 4,
 							"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 ]
+							"vertices": [ 0.65731, -3.42049, -0.43952, -6.27239, -2.99254, -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 }
 						{ "time": 0.8666 }
 					]
 					]
@@ -1001,7 +1002,7 @@
 							"vertices": [ -2.37974, -0.05431, -0.49433, 0.19436, -0.90861, 1.16519, -1.60956, 2.70798, 0.96186, 0.80615 ]
 							"vertices": [ -2.37974, -0.05431, -0.49433, 0.19436, -0.90861, 1.16519, -1.60956, 2.70798, 0.96186, 0.80615 ]
 						},
 						},
 						{
 						{
-							"time": 0.7,
+							"time": 0.6999,
 							"offset": 2,
 							"offset": 2,
 							"vertices": [ -0.91714, -2.76567, -0.62214, -3.63489, -0.8494, -2.26772, -2.56076, 0.5297 ]
 							"vertices": [ -0.91714, -2.76567, -0.62214, -3.63489, -0.8494, -2.26772, -2.56076, 0.5297 ]
 						},
 						},
@@ -1036,7 +1037,7 @@
 							"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 ]
 							"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,
+							"time": 0.6999,
 							"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 ]
 							"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 ]
 						},
 						},
 						{
 						{
@@ -1045,7 +1046,7 @@
 						},
 						},
 						{
 						{
 							"time": 0.8666,
 							"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 ]
+							"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.2398, 0.45919, 0, 0.11759, -0.09759, 3.2839, -0.09025, 5.13115, 0.19388 ]
 						},
 						},
 						{
 						{
 							"time": 1,
 							"time": 1,

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 30 - 6
spine-unity/Assets/Examples/Spine/Hero/hero-mesh.json


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 20 - 22
spine-unity/Assets/Examples/Spine/Raggedy Spineboy/Raggedy Spineboy.json


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 20 - 10
spine-unity/Assets/Examples/Spine/Raptor/raptor.json


+ 177 - 67
spine-unity/Assets/Examples/Spine/Spineboy/spineboy.json

@@ -1,9 +1,10 @@
 {
 {
 "skeleton": {
 "skeleton": {
-	"hash": "nRAMrmcWEKPxacYiF9d5DFza2MU",
-	"spine": "3.3.07",
+	"hash": "ndYeXIOZqYovffwEsZjYOgxtFY4",
+	"spine": "3.5.03-beta",
 	"width": 470.7,
 	"width": 470.7,
 	"height": 731.57,
 	"height": 731.57,
+	"fps": 30,
 	"images": "C:/Program Files (x86)/Spine/examples/spineboy/images/"
 	"images": "C:/Program Files (x86)/Spine/examples/spineboy/images/"
 },
 },
 "bones": [
 "bones": [
@@ -33,16 +34,20 @@
 		"rotation": -2.21,
 		"rotation": -2.21,
 		"x": 78.69,
 		"x": 78.69,
 		"y": 1.6,
 		"y": 1.6,
-		"inheritScale": false,
+		"transform": "noScale",
 		"color": "00ff04ff"
 		"color": "00ff04ff"
 	},
 	},
 	{ "name": "front_foot", "parent": "front_shin", "length": 91.34, "rotation": 77.9, "x": 128.75, "y": -0.33, "color": "00ff04ff" },
 	{ "name": "front_foot", "parent": "front_shin", "length": 91.34, "rotation": 77.9, "x": 128.75, "y": -0.33, "color": "00ff04ff" },
 	{ "name": "rear_upper_arm", "parent": "torso", "length": 51.93, "rotation": -169.55, "x": 92.35, "y": -19.22, "color": "ff000dff" },
 	{ "name": "rear_upper_arm", "parent": "torso", "length": 51.93, "rotation": -169.55, "x": 92.35, "y": -19.22, "color": "ff000dff" },
 	{ "name": "rear_bracer", "parent": "rear_upper_arm", "length": 34.55, "rotation": 23.15, "x": 51.35, "color": "ff000dff" },
 	{ "name": "rear_bracer", "parent": "rear_upper_arm", "length": 34.55, "rotation": 23.15, "x": 51.35, "color": "ff000dff" },
 	{ "name": "gun", "parent": "rear_bracer", "length": 43.1, "rotation": 5.34, "x": 34.42, "y": -0.45, "color": "ff000dff" },
 	{ "name": "gun", "parent": "rear_bracer", "length": 43.1, "rotation": 5.34, "x": 34.42, "y": -0.45, "color": "ff000dff" },
+	{ "name": "gun2", "parent": "root", "x": 390.36, "y": 534.38 },
+	{ "name": "gun3", "parent": "root", "x": 227.39, "y": 464.27 },
+	{ "name": "gun4", "parent": "root", "x": 250.13, "y": 672.72 },
 	{ "name": "gunTip", "parent": "gun", "rotation": 6.83, "x": 201.03, "y": 52.12, "color": "ff000dff" },
 	{ "name": "gunTip", "parent": "gun", "rotation": 6.83, "x": 201.03, "y": 52.12, "color": "ff000dff" },
 	{ "name": "neck", "parent": "torso", "length": 25.45, "rotation": -31.53, "x": 127.49, "y": -0.3, "color": "e0da19ff" },
 	{ "name": "neck", "parent": "torso", "length": 25.45, "rotation": -31.53, "x": 127.49, "y": -0.3, "color": "e0da19ff" },
 	{ "name": "head", "parent": "neck", "length": 263.57, "rotation": 23.18, "x": 27.66, "y": -0.25, "color": "e0da19ff" },
 	{ "name": "head", "parent": "neck", "length": 263.57, "rotation": 23.18, "x": 27.66, "y": -0.25, "color": "e0da19ff" },
+	{ "name": "pathbone", "parent": "root", "x": 71.66, "y": 368.82 },
 	{ "name": "rear_thigh", "parent": "hip", "length": 85.71, "rotation": -72.54, "x": 8.91, "y": -5.62, "color": "ff000dff" },
 	{ "name": "rear_thigh", "parent": "hip", "length": 85.71, "rotation": -72.54, "x": 8.91, "y": -5.62, "color": "ff000dff" },
 	{ "name": "rear_shin", "parent": "rear_thigh", "length": 121.87, "rotation": -19.83, "x": 86.1, "y": -1.32, "color": "ff000dff" },
 	{ "name": "rear_shin", "parent": "rear_thigh", "length": 121.87, "rotation": -19.83, "x": 86.1, "y": -1.32, "color": "ff000dff" },
 	{ "name": "rear_foot", "parent": "rear_shin", "length": 82.57, "rotation": 69.3, "x": 121.45, "y": -0.75, "color": "ff000dff" }
 	{ "name": "rear_foot", "parent": "rear_shin", "length": 82.57, "rotation": 69.3, "x": 121.45, "y": -0.75, "color": "ff000dff" }
@@ -67,17 +72,23 @@
 	{ "name": "front_bracer", "bone": "front_bracer", "attachment": "front_bracer" },
 	{ "name": "front_bracer", "bone": "front_bracer", "attachment": "front_bracer" },
 	{ "name": "front_fist", "bone": "front_fist", "attachment": "front_fist_closed" },
 	{ "name": "front_fist", "bone": "front_fist", "attachment": "front_fist_closed" },
 	{ "name": "muzzle", "bone": "gunTip", "blend": "additive" },
 	{ "name": "muzzle", "bone": "gunTip", "blend": "additive" },
-	{ "name": "head-bb", "bone": "head" }
+	{ "name": "head-bb", "bone": "head" },
+	{ "name": "gun2", "bone": "gun2" },
+	{ "name": "gun3", "bone": "gun3" },
+	{ "name": "gun4", "bone": "gun4" },
+	{ "name": "gunspath", "bone": "pathbone", "attachment": "gunspath" }
 ],
 ],
 "ik": [
 "ik": [
 	{
 	{
 		"name": "aiming constraint",
 		"name": "aiming constraint",
+		"order": 0,
 		"bones": [ "aimer" ],
 		"bones": [ "aimer" ],
 		"target": "shoot target",
 		"target": "shoot target",
 		"mix": 0
 		"mix": 0
 	},
 	},
 	{
 	{
 		"name": "aiming gun constraint",
 		"name": "aiming gun constraint",
+		"order": 1,
 		"bones": [ "rear_upper_arm", "rear_bracer" ],
 		"bones": [ "rear_upper_arm", "rear_bracer" ],
 		"target": "aiming gun target",
 		"target": "aiming gun target",
 		"mix": 0
 		"mix": 0
@@ -86,6 +97,7 @@
 "transform": [
 "transform": [
 	{
 	{
 		"name": "gun flying",
 		"name": "gun flying",
+		"order": 3,
 		"bones": [ "gun" ],
 		"bones": [ "gun" ],
 		"target": "free gun bone",
 		"target": "free gun bone",
 		"rotateMix": 0,
 		"rotateMix": 0,
@@ -94,6 +106,18 @@
 		"shearMix": 0
 		"shearMix": 0
 	}
 	}
 ],
 ],
+"path": [
+	{
+		"name": "spinning guns",
+		"order": 2,
+		"bones": [ "gun4", "gun3", "gun2" ],
+		"target": "gunspath",
+		"spacingMode": "percent",
+		"spacing": 0.335,
+		"rotateMix": 0,
+		"translateMix": 0
+	}
+],
 "skins": {
 "skins": {
 	"default": {
 	"default": {
 		"eye": {
 		"eye": {
@@ -127,6 +151,25 @@
 		"gun": {
 		"gun": {
 			"gun": { "x": 77.29, "y": 16.39, "rotation": 60.82, "width": 210, "height": 203 }
 			"gun": { "x": 77.29, "y": 16.39, "rotation": 60.82, "width": 210, "height": 203 }
 		},
 		},
+		"gun2": {
+			"gun": { "x": 43.58, "y": -39.79, "width": 210, "height": 203 }
+		},
+		"gun3": {
+			"gun": { "x": 39.79, "y": -39.79, "width": 210, "height": 203 }
+		},
+		"gun4": {
+			"gun": { "x": 41.68, "y": -36, "width": 210, "height": 203 }
+		},
+		"gunspath": {
+			"gunspath": {
+				"type": "path",
+				"closed": true,
+				"lengths": [ 534.72, 1020.72, 1497.05, 1846.08 ],
+				"vertexCount": 12,
+				"vertices": [ -175.35, 204.79, -71.33, 203.77998, 121.94999, 201.88, 376.27, 145.38, 369.87, -13.61999, 363.35, -174.04, 157.88998, -197.95, -31.54, -205.52998, -224.8, -211.55, -421.27, -97.37999, -436.33, -4.17999, -453.88, 104.41 ],
+				"color": "ffffffff"
+			}
+		},
 		"head": {
 		"head": {
 			"head": { "x": 128.95, "y": 0.29, "rotation": -70.63, "width": 271, "height": 298 }
 			"head": { "x": 128.95, "y": 0.29, "rotation": -70.63, "width": 271, "height": 298 }
 		},
 		},
@@ -134,7 +177,7 @@
 			"head": {
 			"head": {
 				"type": "boundingbox",
 				"type": "boundingbox",
 				"vertexCount": 6,
 				"vertexCount": 6,
-				"vertices": [ -19.14, -70.3, 40.8, -118.07, 257.77, -115.61, 285.16, 57.18, 120.77, 164.95, -5.06, 76.94 ]
+				"vertices": [ -19.13999, -70.3, 40.79999, -118.06999, 257.77, -115.61, 285.16, 57.18, 120.76999, 164.95, -5.05999, 76.94 ]
 			}
 			}
 		},
 		},
 		"mouth": {
 		"mouth": {
@@ -257,7 +300,7 @@
 					{ "time": 0.4, "angle": -292.35 },
 					{ "time": 0.4, "angle": -292.35 },
 					{ "time": 0.4333, "angle": -315.84 },
 					{ "time": 0.4333, "angle": -315.84 },
 					{ "time": 0.5, "angle": -347.94 },
 					{ "time": 0.5, "angle": -347.94 },
-					{ "time": 0.7, "angle": -347.33, "curve": "stepped" },
+					{ "time": 0.6999, "angle": -347.33, "curve": "stepped" },
 					{ "time": 2.2333, "angle": -347.33 },
 					{ "time": 2.2333, "angle": -347.33 },
 					{ "time": 2.7, "angle": -290.68 },
 					{ "time": 2.7, "angle": -290.68 },
 					{ "time": 2.7666, "angle": -285.1 },
 					{ "time": 2.7666, "angle": -285.1 },
@@ -286,7 +329,7 @@
 					{ "time": 0.4, "angle": -18.92 },
 					{ "time": 0.4, "angle": -18.92 },
 					{ "time": 0.4333, "angle": -18.28 },
 					{ "time": 0.4333, "angle": -18.28 },
 					{ "time": 0.5, "angle": 60.61 },
 					{ "time": 0.5, "angle": 60.61 },
-					{ "time": 0.7, "angle": -18.87, "curve": "stepped" },
+					{ "time": 0.6999, "angle": -18.87, "curve": "stepped" },
 					{ "time": 2.2333, "angle": -18.87 },
 					{ "time": 2.2333, "angle": -18.87 },
 					{ "time": 2.7, "angle": -1.95, "curve": "stepped" },
 					{ "time": 2.7, "angle": -1.95, "curve": "stepped" },
 					{ "time": 4.6666, "angle": -1.95 },
 					{ "time": 4.6666, "angle": -1.95 },
@@ -298,7 +341,7 @@
 				"rotate": [
 				"rotate": [
 					{ "time": 0, "angle": -2.33 },
 					{ "time": 0, "angle": -2.33 },
 					{ "time": 0.2666, "angle": 26.34 },
 					{ "time": 0.2666, "angle": 26.34 },
-					{ "time": 0.7, "angle": -6.07, "curve": "stepped" },
+					{ "time": 0.6999, "angle": -6.07, "curve": "stepped" },
 					{ "time": 2.2333, "angle": -6.07 },
 					{ "time": 2.2333, "angle": -6.07 },
 					{ "time": 2.7, "angle": 5.72, "curve": "stepped" },
 					{ "time": 2.7, "angle": 5.72, "curve": "stepped" },
 					{ "time": 4.6666, "angle": 5.72 },
 					{ "time": 4.6666, "angle": 5.72 },
@@ -357,7 +400,7 @@
 					{ "time": 0.4, "angle": 214.31, "curve": "stepped" },
 					{ "time": 0.4, "angle": 214.31, "curve": "stepped" },
 					{ "time": 0.4333, "angle": 214.31 },
 					{ "time": 0.4333, "angle": 214.31 },
 					{ "time": 0.5, "angle": 169.67 },
 					{ "time": 0.5, "angle": 169.67 },
-					{ "time": 0.7, "angle": 83.27 }
+					{ "time": 0.6999, "angle": 83.27 }
 				]
 				]
 			},
 			},
 			"rear_shin": {
 			"rear_shin": {
@@ -697,6 +740,24 @@
 					{ "time": 2.5333, "name": "front_fist_open" }
 					{ "time": 2.5333, "name": "front_fist_open" }
 				]
 				]
 			},
 			},
+			"gun2": {
+				"attachment": [
+					{ "time": 0.6333, "name": "gun" },
+					{ "time": 1.6666, "name": null }
+				]
+			},
+			"gun3": {
+				"attachment": [
+					{ "time": 0.6333, "name": "gun" },
+					{ "time": 1.6666, "name": null }
+				]
+			},
+			"gun4": {
+				"attachment": [
+					{ "time": 0.6333, "name": "gun" },
+					{ "time": 1.6666, "name": null }
+				]
+			},
 			"mouth": {
 			"mouth": {
 				"attachment": [
 				"attachment": [
 					{ "time": 0, "name": "mouth_smile" },
 					{ "time": 0, "name": "mouth_smile" },
@@ -711,7 +772,7 @@
 					{ "time": 0.8, "color": "ffffffff", "curve": "stepped" },
 					{ "time": 0.8, "color": "ffffffff", "curve": "stepped" },
 					{ "time": 0.8333, "color": "ffffff00", "curve": "stepped" },
 					{ "time": 0.8333, "color": "ffffff00", "curve": "stepped" },
 					{ "time": 0.8666, "color": "ffffffff", "curve": "stepped" },
 					{ "time": 0.8666, "color": "ffffffff", "curve": "stepped" },
-					{ "time": 0.9, "color": "ffffff00", "curve": "stepped" },
+					{ "time": 0.8999, "color": "ffffff00", "curve": "stepped" },
 					{ "time": 0.9333, "color": "ffffffff", "curve": "stepped" },
 					{ "time": 0.9333, "color": "ffffffff", "curve": "stepped" },
 					{ "time": 0.9666, "color": "ffffff00", "curve": "stepped" },
 					{ "time": 0.9666, "color": "ffffff00", "curve": "stepped" },
 					{ "time": 1.1, "color": "ffffffff", "curve": "stepped" },
 					{ "time": 1.1, "color": "ffffffff", "curve": "stepped" },
@@ -751,7 +812,7 @@
 				"translate": [
 				"translate": [
 					{ "time": 0, "x": -6.63, "y": -23.01, "curve": "stepped" },
 					{ "time": 0, "x": -6.63, "y": -23.01, "curve": "stepped" },
 					{
 					{
-						"time": 0.9,
+						"time": 0.8999,
 						"x": -6.63,
 						"x": -6.63,
 						"y": -23.01,
 						"y": -23.01,
 						"curve": [ 0.328, 0.07, 0.662, 0.41 ]
 						"curve": [ 0.328, 0.07, 0.662, 0.41 ]
@@ -782,7 +843,7 @@
 						"curve": [ 0.331, 0.32, 0.665, 0.65 ]
 						"curve": [ 0.331, 0.32, 0.665, 0.65 ]
 					},
 					},
 					{
 					{
-						"time": 1.9,
+						"time": 1.8999,
 						"x": -0.92,
 						"x": -0.92,
 						"y": 15.74,
 						"y": 15.74,
 						"curve": [ 0.329, 0.31, 0.662, 0.64 ]
 						"curve": [ 0.329, 0.31, 0.662, 0.64 ]
@@ -794,7 +855,7 @@
 						"curve": [ 0.329, 0.08, 0.663, 0.42 ]
 						"curve": [ 0.329, 0.08, 0.663, 0.42 ]
 					},
 					},
 					{
 					{
-						"time": 2.1,
+						"time": 2.0999,
 						"x": -6.63,
 						"x": -6.63,
 						"y": -34.66,
 						"y": -34.66,
 						"curve": [ 0.324, 0.16, 0.658, 0.51 ]
 						"curve": [ 0.324, 0.16, 0.658, 0.51 ]
@@ -804,15 +865,15 @@
 				],
 				],
 				"scale": [
 				"scale": [
 					{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
 					{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
-					{ "time": 0.9, "x": 1, "y": 1 },
+					{ "time": 0.8999, "x": 1, "y": 1 },
 					{ "time": 0.9666, "x": 1.098, "y": 0.921 },
 					{ "time": 0.9666, "x": 1.098, "y": 0.921 },
 					{ "time": 1.0666, "x": 1, "y": 1.1 },
 					{ "time": 1.0666, "x": 1, "y": 1.1 },
-					{ "time": 1.3, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 1.2999, "x": 1, "y": 1, "curve": "stepped" },
 					{ "time": 2.5333, "x": 1, "y": 1 }
 					{ "time": 2.5333, "x": 1, "y": 1 }
 				],
 				],
 				"shear": [
 				"shear": [
 					{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
 					{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
-					{ "time": 0.9, "x": 0, "y": 0 },
+					{ "time": 0.8999, "x": 0, "y": 0 },
 					{ "time": 0.9666, "x": 0, "y": -1 },
 					{ "time": 0.9666, "x": 0, "y": -1 },
 					{ "time": 1.1666, "x": 0, "y": 0, "curve": "stepped" },
 					{ "time": 1.1666, "x": 0, "y": 0, "curve": "stepped" },
 					{ "time": 2.5333, "x": 0, "y": 0 }
 					{ "time": 2.5333, "x": 0, "y": 0 }
@@ -822,7 +883,7 @@
 				"rotate": [
 				"rotate": [
 					{ "time": 0, "angle": 0.64, "curve": "stepped" },
 					{ "time": 0, "angle": 0.64, "curve": "stepped" },
 					{
 					{
-						"time": 0.9,
+						"time": 0.8999,
 						"angle": 0.64,
 						"angle": 0.64,
 						"curve": [ 0.328, 0.07, 0.662, 0.41 ]
 						"curve": [ 0.328, 0.07, 0.662, 0.41 ]
 					},
 					},
@@ -858,7 +919,7 @@
 						"curve": [ 0.329, 0.08, 0.663, 0.42 ]
 						"curve": [ 0.329, 0.08, 0.663, 0.42 ]
 					},
 					},
 					{
 					{
-						"time": 2.1,
+						"time": 2.0999,
 						"angle": 14.29,
 						"angle": 14.29,
 						"curve": [ 0.324, 0.16, 0.658, 0.51 ]
 						"curve": [ 0.324, 0.16, 0.658, 0.51 ]
 					},
 					},
@@ -868,7 +929,7 @@
 				"translate": [
 				"translate": [
 					{ "time": 0, "x": -13.39, "y": 6.69, "curve": "stepped" },
 					{ "time": 0, "x": -13.39, "y": 6.69, "curve": "stepped" },
 					{
 					{
-						"time": 0.9,
+						"time": 0.8999,
 						"x": -13.39,
 						"x": -13.39,
 						"y": 6.69,
 						"y": 6.69,
 						"curve": [ 0.315, 0.02, 0.648, 0.39 ]
 						"curve": [ 0.315, 0.02, 0.648, 0.39 ]
@@ -903,7 +964,7 @@
 				"rotate": [
 				"rotate": [
 					{ "time": 0, "angle": -19.28, "curve": "stepped" },
 					{ "time": 0, "angle": -19.28, "curve": "stepped" },
 					{
 					{
-						"time": 0.9,
+						"time": 0.8999,
 						"angle": -19.28,
 						"angle": -19.28,
 						"curve": [ 0.328, 0.07, 0.662, 0.41 ]
 						"curve": [ 0.328, 0.07, 0.662, 0.41 ]
 					},
 					},
@@ -944,7 +1005,7 @@
 						"curve": [ 0.329, 0.08, 0.663, 0.42 ]
 						"curve": [ 0.329, 0.08, 0.663, 0.42 ]
 					},
 					},
 					{
 					{
-						"time": 2.1,
+						"time": 2.0999,
 						"angle": -41.71,
 						"angle": -41.71,
 						"curve": [ 0.324, 0.16, 0.658, 0.51 ]
 						"curve": [ 0.324, 0.16, 0.658, 0.51 ]
 					},
 					},
@@ -955,13 +1016,13 @@
 					{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
 					{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
 					{ "time": 1.0666, "x": 1, "y": 1 },
 					{ "time": 1.0666, "x": 1, "y": 1 },
 					{ "time": 1.1333, "x": 1, "y": 2.131 },
 					{ "time": 1.1333, "x": 1, "y": 2.131 },
-					{ "time": 1.3, "x": 1, "y": 1, "curve": "stepped" },
+					{ "time": 1.2999, "x": 1, "y": 1, "curve": "stepped" },
 					{ "time": 2.5333, "x": 1, "y": 1 }
 					{ "time": 2.5333, "x": 1, "y": 1 }
 				],
 				],
 				"shear": [
 				"shear": [
 					{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
 					{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
 					{
 					{
-						"time": 0.9,
+						"time": 0.8999,
 						"x": 0,
 						"x": 0,
 						"y": 0,
 						"y": 0,
 						"curve": [ 0.263, 0.47, 0.75, 1 ]
 						"curve": [ 0.263, 0.47, 0.75, 1 ]
@@ -996,7 +1057,7 @@
 				"rotate": [
 				"rotate": [
 					{ "time": 0, "angle": 30.5, "curve": "stepped" },
 					{ "time": 0, "angle": 30.5, "curve": "stepped" },
 					{
 					{
-						"time": 0.9,
+						"time": 0.8999,
 						"angle": 30.5,
 						"angle": 30.5,
 						"curve": [ 0.326, 0.05, 0.66, 0.39 ]
 						"curve": [ 0.326, 0.05, 0.66, 0.39 ]
 					},
 					},
@@ -1018,14 +1079,14 @@
 					{ "time": 1.4333, "angle": 56.48, "curve": "stepped" },
 					{ "time": 1.4333, "angle": 56.48, "curve": "stepped" },
 					{ "time": 1.7, "angle": 56.48 },
 					{ "time": 1.7, "angle": 56.48 },
 					{ "time": 1.7666, "angle": 73.88 },
 					{ "time": 1.7666, "angle": 73.88 },
-					{ "time": 1.9, "angle": 9.72 },
+					{ "time": 1.8999, "angle": 9.72 },
 					{
 					{
 						"time": 2.0333,
 						"time": 2.0333,
 						"angle": 30.5,
 						"angle": 30.5,
 						"curve": [ 0.329, 0.08, 0.663, 0.42 ]
 						"curve": [ 0.329, 0.08, 0.663, 0.42 ]
 					},
 					},
 					{
 					{
-						"time": 2.1,
+						"time": 2.0999,
 						"angle": 40.03,
 						"angle": 40.03,
 						"curve": [ 0.324, 0.16, 0.658, 0.51 ]
 						"curve": [ 0.324, 0.16, 0.658, 0.51 ]
 					},
 					},
@@ -1037,7 +1098,7 @@
 				"rotate": [
 				"rotate": [
 					{ "time": 0, "angle": -23.83, "curve": "stepped" },
 					{ "time": 0, "angle": -23.83, "curve": "stepped" },
 					{
 					{
-						"time": 0.9,
+						"time": 0.8999,
 						"angle": -23.83,
 						"angle": -23.83,
 						"curve": [ 0.326, 0.05, 0.66, 0.39 ]
 						"curve": [ 0.326, 0.05, 0.66, 0.39 ]
 					},
 					},
@@ -1059,14 +1120,14 @@
 					{ "time": 1.4666, "angle": -49.1, "curve": "stepped" },
 					{ "time": 1.4666, "angle": -49.1, "curve": "stepped" },
 					{ "time": 1.7, "angle": -49.1 },
 					{ "time": 1.7, "angle": -49.1 },
 					{ "time": 1.7666, "angle": -67.91 },
 					{ "time": 1.7666, "angle": -67.91 },
-					{ "time": 1.9, "angle": 12.32 },
+					{ "time": 1.8999, "angle": 12.32 },
 					{
 					{
 						"time": 2.0333,
 						"time": 2.0333,
 						"angle": -23.83,
 						"angle": -23.83,
 						"curve": [ 0.329, 0.08, 0.663, 0.42 ]
 						"curve": [ 0.329, 0.08, 0.663, 0.42 ]
 					},
 					},
 					{
 					{
-						"time": 2.1,
+						"time": 2.0999,
 						"angle": -37.67,
 						"angle": -37.67,
 						"curve": [ 0.324, 0.16, 0.658, 0.51 ]
 						"curve": [ 0.324, 0.16, 0.658, 0.51 ]
 					},
 					},
@@ -1076,7 +1137,7 @@
 				"scale": [
 				"scale": [
 					{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
 					{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
 					{ "time": 1.1666, "x": 1, "y": 1 },
 					{ "time": 1.1666, "x": 1, "y": 1 },
-					{ "time": 1.3, "x": 1, "y": 1.803 },
+					{ "time": 1.2999, "x": 1, "y": 1.803 },
 					{ "time": 1.4666, "x": 1, "y": 1, "curve": "stepped" },
 					{ "time": 1.4666, "x": 1, "y": 1, "curve": "stepped" },
 					{ "time": 2.5333, "x": 1, "y": 1 }
 					{ "time": 2.5333, "x": 1, "y": 1 }
 				],
 				],
@@ -1103,7 +1164,7 @@
 				"rotate": [
 				"rotate": [
 					{ "time": 0, "angle": 5.13, "curve": "stepped" },
 					{ "time": 0, "angle": 5.13, "curve": "stepped" },
 					{
 					{
-						"time": 0.9,
+						"time": 0.8999,
 						"angle": 5.13,
 						"angle": 5.13,
 						"curve": [ 0.551, 0.05, 0.837, 0.21 ]
 						"curve": [ 0.551, 0.05, 0.837, 0.21 ]
 					},
 					},
@@ -1150,7 +1211,7 @@
 						"curve": [ 0.332, 0.32, 0.665, 0.66 ]
 						"curve": [ 0.332, 0.32, 0.665, 0.66 ]
 					},
 					},
 					{
 					{
-						"time": 2.1,
+						"time": 2.0999,
 						"angle": 15.51,
 						"angle": 15.51,
 						"curve": [ 0.324, 0.12, 0.657, 0.46 ]
 						"curve": [ 0.324, 0.12, 0.657, 0.46 ]
 					},
 					},
@@ -1162,7 +1223,7 @@
 					{ "time": 1.1, "x": 0.755, "y": 1.309 },
 					{ "time": 1.1, "x": 0.755, "y": 1.309 },
 					{ "time": 1.2, "x": 1.271, "y": 1.309, "curve": "stepped" },
 					{ "time": 1.2, "x": 1.271, "y": 1.309, "curve": "stepped" },
 					{ "time": 1.2666, "x": 1.271, "y": 1.309 },
 					{ "time": 1.2666, "x": 1.271, "y": 1.309 },
-					{ "time": 1.4, "x": 0.755, "y": 1.309, "curve": "stepped" },
+					{ "time": 1.3999, "x": 0.755, "y": 1.309, "curve": "stepped" },
 					{ "time": 2.5333, "x": 0.755, "y": 1.309 }
 					{ "time": 2.5333, "x": 0.755, "y": 1.309 }
 				]
 				]
 			},
 			},
@@ -1175,9 +1236,9 @@
 					},
 					},
 					{ "time": 0.1666, "angle": -54.91 },
 					{ "time": 0.1666, "angle": -54.91 },
 					{ "time": 0.5333, "angle": 299.38 },
 					{ "time": 0.5333, "angle": 299.38 },
-					{ "time": 0.7, "angle": 103.94 },
+					{ "time": 0.6999, "angle": 103.94 },
 					{ "time": 0.7666, "angle": 149.61 },
 					{ "time": 0.7666, "angle": 149.61 },
-					{ "time": 1.3, "angle": 12.09 },
+					{ "time": 1.2999, "angle": 12.09 },
 					{
 					{
 						"time": 1.4666,
 						"time": 1.4666,
 						"angle": 269.27,
 						"angle": 269.27,
@@ -1196,7 +1257,7 @@
 					{ "time": 0, "x": -1.83, "y": -16.78 },
 					{ "time": 0, "x": -1.83, "y": -16.78 },
 					{ "time": 0.1666, "x": -3.74, "y": 56 },
 					{ "time": 0.1666, "x": -3.74, "y": 56 },
 					{ "time": 0.5333, "x": 8.84, "y": 49.52 },
 					{ "time": 0.5333, "x": 8.84, "y": 49.52 },
-					{ "time": 0.7, "x": 4.59, "y": -0.69, "curve": "stepped" },
+					{ "time": 0.6999, "x": 4.59, "y": -0.69, "curve": "stepped" },
 					{ "time": 0.7666, "x": 4.59, "y": -0.69 },
 					{ "time": 0.7666, "x": 4.59, "y": -0.69 },
 					{ "time": 1.4333, "x": 7.36, "y": 37.32 },
 					{ "time": 1.4333, "x": 7.36, "y": 37.32 },
 					{ "time": 1.5666, "x": 29.37, "y": 77.68, "curve": "stepped" },
 					{ "time": 1.5666, "x": 29.37, "y": 77.68, "curve": "stepped" },
@@ -1208,7 +1269,7 @@
 					{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
 					{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
 					{ "time": 1.2666, "x": 0, "y": 0 },
 					{ "time": 1.2666, "x": 0, "y": 0 },
 					{
 					{
-						"time": 1.4,
+						"time": 1.3999,
 						"x": 16.96,
 						"x": 16.96,
 						"y": 0,
 						"y": 0,
 						"curve": [ 0.25, 0, 0.511, 1 ]
 						"curve": [ 0.25, 0, 0.511, 1 ]
@@ -1238,7 +1299,7 @@
 						"angle": -300.75,
 						"angle": -300.75,
 						"curve": [ 0.25, 0, 0.566, 1.77 ]
 						"curve": [ 0.25, 0, 0.566, 1.77 ]
 					},
 					},
-					{ "time": 0.7, "angle": -41.32 },
+					{ "time": 0.6999, "angle": -41.32 },
 					{
 					{
 						"time": 0.8333,
 						"time": 0.8333,
 						"angle": -47.51,
 						"angle": -47.51,
@@ -1250,7 +1311,7 @@
 						"curve": [ 0.514, 0, 0.803, 0.42 ]
 						"curve": [ 0.514, 0, 0.803, 0.42 ]
 					},
 					},
 					{
 					{
-						"time": 1.3,
+						"time": 1.2999,
 						"angle": -231.77,
 						"angle": -231.77,
 						"curve": [ 0.342, 0.4, 0.364, 1 ]
 						"curve": [ 0.342, 0.4, 0.364, 1 ]
 					},
 					},
@@ -1268,7 +1329,7 @@
 						"y": -22.29,
 						"y": -22.29,
 						"curve": [ 0.25, 0, 0.566, 1.77 ]
 						"curve": [ 0.25, 0, 0.566, 1.77 ]
 					},
 					},
-					{ "time": 0.7, "x": -2.86, "y": 8.61, "curve": "stepped" },
+					{ "time": 0.6999, "x": -2.86, "y": 8.61, "curve": "stepped" },
 					{
 					{
 						"time": 0.8333,
 						"time": 0.8333,
 						"x": -2.86,
 						"x": -2.86,
@@ -1327,7 +1388,7 @@
 						"angle": -22.45,
 						"angle": -22.45,
 						"curve": [ 0.285, 0.17, 0.69, 0.74 ]
 						"curve": [ 0.285, 0.17, 0.69, 0.74 ]
 					},
 					},
-					{ "time": 0.7, "angle": 0.33, "curve": "stepped" },
+					{ "time": 0.6999, "angle": 0.33, "curve": "stepped" },
 					{
 					{
 						"time": 1.0333,
 						"time": 1.0333,
 						"angle": 0.33,
 						"angle": 0.33,
@@ -1352,7 +1413,7 @@
 					{ "time": 0, "x": -6.49, "y": 0 },
 					{ "time": 0, "x": -6.49, "y": 0 },
 					{ "time": 0.1666, "x": -6.49, "y": -5.46, "curve": "stepped" },
 					{ "time": 0.1666, "x": -6.49, "y": -5.46, "curve": "stepped" },
 					{ "time": 0.4333, "x": -6.49, "y": -5.46 },
 					{ "time": 0.4333, "x": -6.49, "y": -5.46 },
-					{ "time": 0.7, "x": -6.49, "y": 0, "curve": "stepped" },
+					{ "time": 0.6999, "x": -6.49, "y": 0, "curve": "stepped" },
 					{ "time": 2.5333, "x": -6.49, "y": 0 }
 					{ "time": 2.5333, "x": -6.49, "y": 0 }
 				],
 				],
 				"shear": [
 				"shear": [
@@ -1386,7 +1447,7 @@
 						"angle": 89.75,
 						"angle": 89.75,
 						"curve": [ 0.179, 0.72, 0.456, 1 ]
 						"curve": [ 0.179, 0.72, 0.456, 1 ]
 					},
 					},
-					{ "time": 0.7, "angle": -20.53, "curve": "stepped" },
+					{ "time": 0.6999, "angle": -20.53, "curve": "stepped" },
 					{
 					{
 						"time": 0.8333,
 						"time": 0.8333,
 						"angle": -20.53,
 						"angle": -20.53,
@@ -1394,7 +1455,7 @@
 					},
 					},
 					{ "time": 1, "angle": 61.24 },
 					{ "time": 1, "angle": 61.24 },
 					{
 					{
-						"time": 1.4,
+						"time": 1.3999,
 						"angle": 93.63,
 						"angle": 93.63,
 						"curve": [ 0.294, 1.37, 0.624, 1 ]
 						"curve": [ 0.294, 1.37, 0.624, 1 ]
 					},
 					},
@@ -1426,7 +1487,7 @@
 				"rotate": [
 				"rotate": [
 					{ "time": 0, "angle": -7.34, "curve": "stepped" },
 					{ "time": 0, "angle": -7.34, "curve": "stepped" },
 					{
 					{
-						"time": 0.9,
+						"time": 0.8999,
 						"angle": -7.34,
 						"angle": -7.34,
 						"curve": [ 0.326, 0.05, 0.66, 0.39 ]
 						"curve": [ 0.326, 0.05, 0.66, 0.39 ]
 					},
 					},
@@ -1457,7 +1518,7 @@
 						"curve": [ 0.33, 0.3, 0.663, 0.63 ]
 						"curve": [ 0.33, 0.3, 0.663, 0.63 ]
 					},
 					},
 					{
 					{
-						"time": 1.9,
+						"time": 1.8999,
 						"angle": -33.94,
 						"angle": -33.94,
 						"curve": [ 0.325, 0.28, 0.659, 0.62 ]
 						"curve": [ 0.325, 0.28, 0.659, 0.62 ]
 					},
 					},
@@ -1467,7 +1528,7 @@
 						"curve": [ 0.324, 0.04, 0.658, 0.39 ]
 						"curve": [ 0.324, 0.04, 0.658, 0.39 ]
 					},
 					},
 					{
 					{
-						"time": 2.1,
+						"time": 2.0999,
 						"angle": -2.67,
 						"angle": -2.67,
 						"curve": [ 0.317, 0.18, 0.65, 0.53 ]
 						"curve": [ 0.317, 0.18, 0.65, 0.53 ]
 					},
 					},
@@ -1486,7 +1547,7 @@
 					{ "time": 0.1666, "angle": -24.07 },
 					{ "time": 0.1666, "angle": -24.07 },
 					{ "time": 0.4333, "angle": -29.77 },
 					{ "time": 0.4333, "angle": -29.77 },
 					{ "time": 0.5666, "angle": -68.84 },
 					{ "time": 0.5666, "angle": -68.84 },
-					{ "time": 0.7, "angle": -22.33 },
+					{ "time": 0.6999, "angle": -22.33 },
 					{ "time": 0.7666, "angle": -20.19, "curve": "stepped" },
 					{ "time": 0.7666, "angle": -20.19, "curve": "stepped" },
 					{
 					{
 						"time": 0.8666,
 						"time": 0.8666,
@@ -1506,7 +1567,7 @@
 					{ "time": 1.6, "angle": -23.72, "curve": "stepped" },
 					{ "time": 1.6, "angle": -23.72, "curve": "stepped" },
 					{ "time": 1.6333, "angle": -23.72 },
 					{ "time": 1.6333, "angle": -23.72 },
 					{
 					{
-						"time": 1.8,
+						"time": 1.7999,
 						"angle": -80.6,
 						"angle": -80.6,
 						"curve": [ 0.465, 0.15, 0.852, 0.45 ]
 						"curve": [ 0.465, 0.15, 0.852, 0.45 ]
 					},
 					},
@@ -1628,9 +1689,9 @@
 					{ "time": 0.3, "x": -11.45, "y": 0 },
 					{ "time": 0.3, "x": -11.45, "y": 0 },
 					{ "time": 0.5333, "x": -14.51, "y": -10.11 },
 					{ "time": 0.5333, "x": -14.51, "y": -10.11 },
 					{ "time": 0.6, "x": 9.49, "y": -7.02 },
 					{ "time": 0.6, "x": 9.49, "y": -7.02 },
-					{ "time": 0.7, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 0.6999, "x": 0, "y": 0, "curve": "stepped" },
 					{
 					{
-						"time": 1.4,
+						"time": 1.3999,
 						"x": 0,
 						"x": 0,
 						"y": 0,
 						"y": 0,
 						"curve": [ 0.25, 0, 0.553, 1 ]
 						"curve": [ 0.25, 0, 0.553, 1 ]
@@ -1642,7 +1703,7 @@
 						"curve": [ 0.25, 0, 0.553, 1 ]
 						"curve": [ 0.25, 0, 0.553, 1 ]
 					},
 					},
 					{
 					{
-						"time": 1.8,
+						"time": 1.7999,
 						"x": 0,
 						"x": 0,
 						"y": 0,
 						"y": 0,
 						"curve": [ 0.25, 0, 0.553, 1 ]
 						"curve": [ 0.25, 0, 0.553, 1 ]
@@ -1674,7 +1735,7 @@
 						"angle": -43.92,
 						"angle": -43.92,
 						"curve": [ 0.348, 0.38, 0.691, 0.75 ]
 						"curve": [ 0.348, 0.38, 0.691, 0.75 ]
 					},
 					},
-					{ "time": 0.7, "angle": 0, "curve": "stepped" },
+					{ "time": 0.6999, "angle": 0, "curve": "stepped" },
 					{
 					{
 						"time": 1.7333,
 						"time": 1.7333,
 						"angle": 0,
 						"angle": 0,
@@ -1808,6 +1869,41 @@
 					{ "time": 1.5, "angle": 0, "curve": "stepped" },
 					{ "time": 1.5, "angle": 0, "curve": "stepped" },
 					{ "time": 2.5333, "angle": 0 }
 					{ "time": 2.5333, "angle": 0 }
 				]
 				]
+			},
+			"pathbone": {
+				"rotate": [
+					{ "time": 0.6666, "angle": 0 },
+					{ "time": 1, "angle": -126.95 },
+					{ "time": 1.3333, "angle": 65.57 },
+					{ "time": 1.6666, "angle": 0 }
+				]
+			},
+			"gun2": {
+				"rotate": [
+					{ "time": 1.6666, "angle": -123.49 }
+				],
+				"translate": [
+					{ "time": 0.6, "x": -271.68, "y": -301.14 },
+					{ "time": 1.6666, "x": -408.03, "y": -266.18 }
+				]
+			},
+			"gun3": {
+				"rotate": [
+					{ "time": 1.6666, "angle": -123.49 }
+				],
+				"translate": [
+					{ "time": 0.6, "x": -111.29, "y": -212.76 },
+					{ "time": 1.6666, "x": -247.63, "y": -177.8 }
+				]
+			},
+			"gun4": {
+				"rotate": [
+					{ "time": 1.6666, "angle": -123.49 }
+				],
+				"translate": [
+					{ "time": 0.6, "x": -144.02, "y": -425.53 },
+					{ "time": 1.6666, "x": -280.37, "y": -390.57 }
+				]
 			}
 			}
 		},
 		},
 		"ik": {
 		"ik": {
@@ -1824,11 +1920,25 @@
 			"gun flying": [
 			"gun flying": [
 				{ "time": 0, "rotateMix": 0, "translateMix": 0, "scaleMix": 0, "shearMix": 0, "curve": "stepped" },
 				{ "time": 0, "rotateMix": 0, "translateMix": 0, "scaleMix": 0, "shearMix": 0, "curve": "stepped" },
 				{ "time": 0.6666, "rotateMix": 0, "translateMix": 0, "scaleMix": 0, "shearMix": 0 },
 				{ "time": 0.6666, "rotateMix": 0, "translateMix": 0, "scaleMix": 0, "shearMix": 0 },
-				{ "time": 0.7, "curve": "stepped" },
+				{ "time": 0.6999, "curve": "stepped" },
 				{ "time": 1.6333 },
 				{ "time": 1.6333 },
 				{ "time": 1.6666, "rotateMix": 0, "translateMix": 0, "scaleMix": 0, "shearMix": 0, "curve": "stepped" },
 				{ "time": 1.6666, "rotateMix": 0, "translateMix": 0, "scaleMix": 0, "shearMix": 0, "curve": "stepped" },
 				{ "time": 2.5333, "rotateMix": 0, "translateMix": 0, "scaleMix": 0, "shearMix": 0 }
 				{ "time": 2.5333, "rotateMix": 0, "translateMix": 0, "scaleMix": 0, "shearMix": 0 }
 			]
 			]
+		},
+		"paths": {
+			"spinning guns": {
+				"position": [
+					{ "time": 0.6666 },
+					{ "time": 1.6666, "position": 2.0339 }
+				],
+				"mix": [
+					{ "time": 0.6666, "rotateMix": 0, "translateMix": 0 },
+					{ "time": 0.7333, "curve": "stepped" },
+					{ "time": 1.5666 },
+					{ "time": 1.6666, "rotateMix": 0, "translateMix": 0 }
+				]
+			}
 		}
 		}
 	},
 	},
 	"hit": {
 	"hit": {
@@ -2505,7 +2615,7 @@
 					{ "time": 0.3666, "angle": -50.76 },
 					{ "time": 0.3666, "angle": -50.76 },
 					{ "time": 0.6666, "angle": 1.89 },
 					{ "time": 0.6666, "angle": 1.89 },
 					{ "time": 0.7666, "angle": 11.58 },
 					{ "time": 0.7666, "angle": 11.58 },
-					{ "time": 0.9, "angle": -1.89 },
+					{ "time": 0.8999, "angle": -1.89 },
 					{ "time": 1.0666, "angle": 11.58 },
 					{ "time": 1.0666, "angle": 11.58 },
 					{ "time": 1.2666, "angle": -42.63 }
 					{ "time": 1.2666, "angle": -42.63 }
 				]
 				]
@@ -2614,7 +2724,7 @@
 						"curve": [ 0.342, 0.36, 0.68, 0.71 ]
 						"curve": [ 0.342, 0.36, 0.68, 0.71 ]
 					},
 					},
 					{
 					{
-						"time": 0.7,
+						"time": 0.6999,
 						"angle": 321.47,
 						"angle": 321.47,
 						"curve": [ 0.333, 0.33, 0.667, 0.66 ]
 						"curve": [ 0.333, 0.33, 0.667, 0.66 ]
 					},
 					},
@@ -2640,7 +2750,7 @@
 					{ "time": 0, "angle": 29.66 },
 					{ "time": 0, "angle": 29.66 },
 					{ "time": 0.1333, "angle": 45.06 },
 					{ "time": 0.1333, "angle": 45.06 },
 					{ "time": 0.3666, "angle": -4.34 },
 					{ "time": 0.3666, "angle": -4.34 },
-					{ "time": 0.7, "angle": 61.68 },
+					{ "time": 0.6999, "angle": 61.68 },
 					{ "time": 0.7333, "angle": 82.59 },
 					{ "time": 0.7333, "angle": 82.59 },
 					{ "time": 0.8, "angle": 80.06 },
 					{ "time": 0.8, "angle": 80.06 },
 					{ "time": 0.9666, "angle": 57.56 },
 					{ "time": 0.9666, "angle": 57.56 },
@@ -2719,7 +2829,7 @@
 					{ "time": 0.1333, "angle": 30.81 },
 					{ "time": 0.1333, "angle": 30.81 },
 					{ "time": 0.3666, "angle": 13.25 },
 					{ "time": 0.3666, "angle": 13.25 },
 					{ "time": 0.6666, "angle": 14.98 },
 					{ "time": 0.6666, "angle": 14.98 },
-					{ "time": 0.7, "angle": 25.64 },
+					{ "time": 0.6999, "angle": 25.64 },
 					{ "time": 0.7333, "angle": 20.62 },
 					{ "time": 0.7333, "angle": 20.62 },
 					{ "time": 0.8, "angle": 64.52 },
 					{ "time": 0.8, "angle": 64.52 },
 					{ "time": 0.9666, "angle": 8.59 },
 					{ "time": 0.9666, "angle": 8.59 },
@@ -3432,7 +3542,7 @@
 				"scale": [
 				"scale": [
 					{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
 					{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
 					{ "time": 0.0666, "x": 1, "y": 1 },
 					{ "time": 0.0666, "x": 1, "y": 1 },
-					{ "time": 0.0864, "x": 0.9, "y": 1.1 },
+					{ "time": 0.0864, "x": 0.899, "y": 1.1 },
 					{
 					{
 						"time": 0.1144,
 						"time": 0.1144,
 						"x": 1.163,
 						"x": 1.163,
@@ -4177,7 +4287,7 @@
 					{ "time": 0.4, "angle": -28.62 },
 					{ "time": 0.4, "angle": -28.62 },
 					{ "time": 0.5, "angle": -19.3 },
 					{ "time": 0.5, "angle": -19.3 },
 					{ "time": 0.6, "angle": -3.08 },
 					{ "time": 0.6, "angle": -3.08 },
-					{ "time": 0.7, "angle": 29.51 },
+					{ "time": 0.6999, "angle": 29.51 },
 					{ "time": 0.8, "angle": 15.79 }
 					{ "time": 0.8, "angle": 15.79 }
 				],
 				],
 				"translate": [
 				"translate": [
@@ -4196,7 +4306,7 @@
 					{ "time": 0.3, "angle": 15.98 },
 					{ "time": 0.3, "angle": 15.98 },
 					{ "time": 0.4, "angle": 5.94 },
 					{ "time": 0.4, "angle": 5.94 },
 					{ "time": 0.5, "angle": -26.76 },
 					{ "time": 0.5, "angle": -26.76 },
-					{ "time": 0.7, "angle": -55.44 },
+					{ "time": 0.6999, "angle": -55.44 },
 					{ "time": 0.8, "angle": 5.12 }
 					{ "time": 0.8, "angle": 5.12 }
 				]
 				]
 			},
 			},
@@ -4209,14 +4319,14 @@
 					{ "time": 0.4, "angle": 8.69 },
 					{ "time": 0.4, "angle": 8.69 },
 					{ "time": 0.5, "angle": 12.16 },
 					{ "time": 0.5, "angle": 12.16 },
 					{ "time": 0.6, "angle": -24.62 },
 					{ "time": 0.6, "angle": -24.62 },
-					{ "time": 0.7, "angle": -27.26 },
+					{ "time": 0.6999, "angle": -27.26 },
 					{ "time": 0.8, "angle": -34.38 }
 					{ "time": 0.8, "angle": -34.38 }
 				],
 				],
 				"translate": [
 				"translate": [
 					{ "time": 0, "x": 0, "y": 0 },
 					{ "time": 0, "x": 0, "y": 0 },
 					{ "time": 0.4, "x": 4.08, "y": -9.53 },
 					{ "time": 0.4, "x": 4.08, "y": -9.53 },
 					{ "time": 0.5, "x": 0, "y": 0 },
 					{ "time": 0.5, "x": 0, "y": 0 },
-					{ "time": 0.7, "x": -21.14, "y": -9.6 },
+					{ "time": 0.6999, "x": -21.14, "y": -9.6 },
 					{ "time": 0.8, "x": 0, "y": 0 }
 					{ "time": 0.8, "x": 0, "y": 0 }
 				]
 				]
 			},
 			},
@@ -4229,7 +4339,7 @@
 					{ "time": 0.4, "angle": 15.95 },
 					{ "time": 0.4, "angle": 15.95 },
 					{ "time": 0.5, "angle": -9 },
 					{ "time": 0.5, "angle": -9 },
 					{ "time": 0.6, "angle": 26.06 },
 					{ "time": 0.6, "angle": 26.06 },
-					{ "time": 0.7, "angle": 21.85 },
+					{ "time": 0.6999, "angle": 21.85 },
 					{ "time": 0.8, "angle": 14.26 }
 					{ "time": 0.8, "angle": 14.26 }
 				],
 				],
 				"scale": [
 				"scale": [
@@ -4248,7 +4358,7 @@
 					{ "time": 0.4, "angle": 13.45 },
 					{ "time": 0.4, "angle": 13.45 },
 					{ "time": 0.5, "angle": -3.57 },
 					{ "time": 0.5, "angle": -3.57 },
 					{ "time": 0.6, "angle": -0.97 },
 					{ "time": 0.6, "angle": -0.97 },
-					{ "time": 0.7, "angle": 2.97 },
+					{ "time": 0.6999, "angle": 2.97 },
 					{ "time": 0.8, "angle": 10.13 }
 					{ "time": 0.8, "angle": 10.13 }
 				]
 				]
 			},
 			},
@@ -4314,7 +4424,7 @@
 					{ "time": 0.4, "angle": 10.22 },
 					{ "time": 0.4, "angle": 10.22 },
 					{ "time": 0.5, "angle": 11.44 },
 					{ "time": 0.5, "angle": 11.44 },
 					{ "time": 0.6, "angle": -0.33 },
 					{ "time": 0.6, "angle": -0.33 },
-					{ "time": 0.7, "angle": 0.15 },
+					{ "time": 0.6999, "angle": 0.15 },
 					{ "time": 0.8, "angle": 12.49 }
 					{ "time": 0.8, "angle": 12.49 }
 				]
 				]
 			},
 			},
@@ -4406,7 +4516,7 @@
 						"curve": [ 0.287, 0.37, 0.718, 0.76 ]
 						"curve": [ 0.287, 0.37, 0.718, 0.76 ]
 					},
 					},
 					{
 					{
-						"time": 0.7,
+						"time": 0.6999,
 						"x": -23.93,
 						"x": -23.93,
 						"y": 10.34,
 						"y": 10.34,
 						"curve": [ 0.615, 0, 0.75, 1 ]
 						"curve": [ 0.615, 0, 0.75, 1 ]

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 13 - 12
spine-unity/Assets/Examples/Spine/Spineunitygirl/Doi.json


+ 60 - 13
spine-unity/Assets/spine-unity/Asset Types/AtlasAsset.cs

@@ -29,6 +29,7 @@
  *****************************************************************************/
  *****************************************************************************/
 
 
 using System;
 using System;
+using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using UnityEngine;
 using UnityEngine;
 using Spine;
 using Spine;
@@ -40,6 +41,65 @@ namespace Spine.Unity {
 		public Material[] materials;
 		public Material[] materials;
 		protected Atlas atlas;
 		protected Atlas atlas;
 
 
+		#region Runtime Instantiation
+		/// <summary>
+		/// Creates a runtime AtlasAsset</summary>
+		public static AtlasAsset CreateRuntimeInstance (TextAsset atlasText, Material[] materials, bool initialize) {
+			AtlasAsset atlasAsset = ScriptableObject.CreateInstance<AtlasAsset>();
+			atlasAsset.Reset();
+			atlasAsset.atlasFile = atlasText;
+			atlasAsset.materials = materials;
+
+			if (initialize)
+				atlasAsset.GetAtlas();
+
+			return atlasAsset;
+		}
+			
+		/// <summary>
+		/// Creates a runtime AtlasAsset. Only providing the textures is slower because it has to search for atlas page matches. <seealso cref="Spine.Unity.AtlasAsset.CreateRuntimeInstance(TextAsset, Material[], bool)"/></summary>
+		public static AtlasAsset CreateRuntimeInstance (TextAsset atlasText, Texture2D[] textures, Shader shader, bool initialize) {
+			if (shader == null)
+				shader = Shader.Find("Spine/Skeleton");
+
+			// Get atlas page names.
+			string atlasString = atlasText.text;
+			atlasString = atlasString.Replace("\r", "");
+			string[] atlasLines = atlasString.Split('\n');
+			var pages = new List<string>();
+			for (int i = 0; i < atlasLines.Length - 1; i++) {
+				if (atlasLines[i].Trim().Length == 0)
+					pages.Add(atlasLines[i + 1].Trim().Replace(".png", ""));
+			}
+
+			// Populate Materials[] by matching texture names with page names.
+			var materials = new Material[pages.Count];
+			for (int i = 0, n = pages.Count; i < n; i++) {
+				Material mat = null;
+
+				// Search for a match.
+				string pageName = pages[i];
+				for (int j = 0, m = textures.Length; j < m; j++) {
+					if (string.Equals(pageName, textures[j].name, System.StringComparison.OrdinalIgnoreCase)) {
+						// Match found.
+						mat = new Material(shader);
+						mat.mainTexture = textures[j];
+						break;
+					}
+				}
+
+				if (mat != null)
+					materials[i] = mat;
+				else
+					throw new ArgumentException("Could not find matching atlas page in the texture array.");
+
+			}
+
+			// Create AtlasAsset normally
+			return CreateRuntimeInstance(atlasText, materials, initialize);
+		}
+		#endregion
+
 		public virtual void Reset () {
 		public virtual void Reset () {
 			atlas = null;
 			atlas = null;
 		}
 		}
@@ -71,19 +131,6 @@ namespace Spine.Unity {
 			}
 			}
 		}
 		}
 
 
-		public Sprite GenerateSprite (string name, out Material material) {
-			AtlasRegion region = atlas.FindRegion(name);
-
-			Sprite sprite = null;
-			material = null;
-
-			if (region != null) {
-				//sprite.rect
-			}
-
-			return sprite;
-		}
-
 		public Mesh GenerateMesh (string name, Mesh mesh, out Material material, float scale = 0.01f) {
 		public Mesh GenerateMesh (string name, Mesh mesh, out Material material, float scale = 0.01f) {
 			AtlasRegion region = atlas.FindRegion(name);
 			AtlasRegion region = atlas.FindRegion(name);
 			material = null;
 			material = null;

+ 74 - 82
spine-unity/Assets/spine-unity/Asset Types/Editor/AtlasAssetInspector.cs

@@ -34,18 +34,26 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Reflection;
 using System.Reflection;
-using System.IO;
 using UnityEditor;
 using UnityEditor;
 using UnityEngine;
 using UnityEngine;
 using Spine;
 using Spine;
 
 
 namespace Spine.Unity.Editor {
 namespace Spine.Unity.Editor {
-	
+	using Event = UnityEngine.Event;
+
 	[CustomEditor(typeof(AtlasAsset))]
 	[CustomEditor(typeof(AtlasAsset))]
 	public class AtlasAssetInspector : UnityEditor.Editor {
 	public class AtlasAssetInspector : UnityEditor.Editor {
 		private SerializedProperty atlasFile, materials;
 		private SerializedProperty atlasFile, materials;
 		private AtlasAsset atlasAsset;
 		private AtlasAsset atlasAsset;
 
 
+		readonly GUIContent SpriteSlicesLabel = new GUIContent(
+			"Apply Regions as Texture Sprite Slices",
+			SpineEditorUtilities.Icons.unityIcon,
+			"Adds Sprite slices to atlas texture(s). " +
+			"Updates existing slices if ones with matching names exist. \n\n" +
+			"If your atlas was exported with Premultiply Alpha, " +
+			"your SpriteRenderer should use the generated Spine _Material asset (or any Material with a PMA shader) instead of Sprites-Default.");
+
 		static List<AtlasRegion> GetRegions (Atlas atlas) {
 		static List<AtlasRegion> GetRegions (Atlas atlas) {
 			FieldInfo regionsField = typeof(Atlas).GetField("regions", BindingFlags.Instance | BindingFlags.NonPublic);
 			FieldInfo regionsField = typeof(Atlas).GetField("regions", BindingFlags.Instance | BindingFlags.NonPublic);
 			return (List<AtlasRegion>)regionsField.GetValue(atlas);
 			return (List<AtlasRegion>)regionsField.GetValue(atlas);
@@ -86,70 +94,11 @@ namespace Spine.Unity.Editor {
 		}
 		}
 		#endif
 		#endif
 
 
-		static public void UpdateSpriteSlices (Texture texture, Atlas atlas) {
-			string texturePath = AssetDatabase.GetAssetPath(texture.GetInstanceID());
-			var t = (TextureImporter)TextureImporter.GetAtPath(texturePath);
-			t.spriteImportMode = SpriteImportMode.Multiple;
-			var spriteSheet = t.spritesheet;
-			var sprites = new List<SpriteMetaData>(spriteSheet);
-
-			var regions = AtlasAssetInspector.GetRegions(atlas);
-			int textureHeight = texture.height;
-			char[] FilenameDelimiter = {'.'};
-			int updatedCount = 0;
-			int addedCount = 0;
-
-			foreach (var r in regions) {
-				int width, height;
-				if (r.rotate) {
-					width = r.height;
-					height = r.width;
-				} else {
-					width = r.width;
-					height = r.height;
-				}
-
-				int x = r.x;
-				int y = textureHeight - height - r.y;
-
-				string pageName = r.page.name.Split(FilenameDelimiter, StringSplitOptions.RemoveEmptyEntries)[0];
-				string textureName = texture.name;
-				bool pageMatch = string.Equals(pageName, textureName,StringComparison.Ordinal);
-				int spriteIndex = pageMatch ? sprites.FindIndex(
-					(s) => string.Equals(s.name, r.name, StringComparison.Ordinal)
-				) : -1;
-				bool matchFound = spriteIndex >= 0;
-
-				if (matchFound) {
-					var s = sprites[spriteIndex];
-					s.rect = new Rect(x, y, width, height);
-					sprites[spriteIndex] = s;
-					updatedCount++;
-				} else {
-					if (pageMatch) {
-						sprites.Add(new SpriteMetaData {
-							name = r.name,
-							pivot = new Vector2(0.5f, 0.5f),
-							rect = new Rect(x, y, width, height)
-						});
-						addedCount++;
-					}
-				}
-			}
-
-			t.spritesheet = sprites.ToArray();
-			EditorUtility.SetDirty(t);
-			AssetDatabase.ImportAsset(texturePath, ImportAssetOptions.ForceUpdate);
-			EditorGUIUtility.PingObject(texture);
-			Debug.Log(string.Format("Applied sprite slices to {2}. {0} added. {1} updated.", addedCount, updatedCount, texture.name));
-		}
-
 		override public void OnInspectorGUI () {
 		override public void OnInspectorGUI () {
 			serializedObject.Update();
 			serializedObject.Update();
 			atlasAsset = atlasAsset ?? (AtlasAsset)target;
 			atlasAsset = atlasAsset ?? (AtlasAsset)target;
 			EditorGUI.BeginChangeCheck();
 			EditorGUI.BeginChangeCheck();
 			EditorGUILayout.PropertyField(atlasFile);
 			EditorGUILayout.PropertyField(atlasFile);
-
 			EditorGUILayout.PropertyField(materials, true);
 			EditorGUILayout.PropertyField(materials, true);
 			if (EditorGUI.EndChangeCheck())
 			if (EditorGUI.EndChangeCheck())
 				serializedObject.ApplyModifiedProperties();
 				serializedObject.ApplyModifiedProperties();
@@ -170,25 +119,12 @@ namespace Spine.Unity.Editor {
 
 
 			EditorGUILayout.Space();
 			EditorGUILayout.Space();
 			if (atlasFile.objectReferenceValue != null) {
 			if (atlasFile.objectReferenceValue != null) {
-				using (new EditorGUILayout.HorizontalScope()) {
-					EditorGUILayout.Space();
-					if (GUILayout.Button(
-						new GUIContent(
-							"Apply Regions as Texture Sprite Slices",
-							SpineEditorUtilities.Icons.unityIcon,
-							"Adds Sprite slices to atlas texture(s). " +
-							"Updates existing slices if ones with matching names exist. \n\n" +
-							"If your atlas was exported with Premultiply Alpha, " +
-							"your SpriteRenderer should use the generated Spine _Material asset (or any Material with a PMA shader) instead of Sprites-Default.")
-						, GUILayout.Height(30f))) {
-						var atlas = atlasAsset.GetAtlas();
-						foreach (var m in atlasAsset.materials)
-							UpdateSpriteSlices(m.mainTexture, atlas);
-					}
-					EditorGUILayout.Space();
+				if (SpineInspectorUtility.LargeCenteredButton(SpriteSlicesLabel)) {
+					var atlas = atlasAsset.GetAtlas();
+					foreach (var m in atlasAsset.materials)
+						UpdateSpriteSlices(m.mainTexture, atlas);
 				}
 				}
 			}
 			}
-			EditorGUILayout.Space();
 
 
 			#if REGION_BAKING_MESH
 			#if REGION_BAKING_MESH
 			if (atlasFile.objectReferenceValue != null) {
 			if (atlasFile.objectReferenceValue != null) {
@@ -322,7 +258,7 @@ namespace Spine.Unity.Editor {
 							GUILayout.EndHorizontal();
 							GUILayout.EndHorizontal();
 
 
 						} else {
 						} else {
-							EditorGUILayout.LabelField(new GUIContent("Page missing material!", SpineEditorUtilities.Icons.warning));
+							EditorGUILayout.HelpBox("Page missing material!", MessageType.Warning);
 						}
 						}
 					}
 					}
 					EditorGUILayout.LabelField(new GUIContent(regions[i].name, SpineEditorUtilities.Icons.image));
 					EditorGUILayout.LabelField(new GUIContent(regions[i].name, SpineEditorUtilities.Icons.image));
@@ -331,12 +267,68 @@ namespace Spine.Unity.Editor {
 			}
 			}
 			#endif
 			#endif
 
 
-			if (serializedObject.ApplyModifiedProperties() ||
-				(UnityEngine.Event.current.type == EventType.ValidateCommand && UnityEngine.Event.current.commandName == "UndoRedoPerformed")
-			) {
+			if (serializedObject.ApplyModifiedProperties() || SpineInspectorUtility.UndoRedoPerformed(Event.current)) {
 				atlasAsset.Reset();
 				atlasAsset.Reset();
 			}
 			}
 		}
 		}
+
+		static public void UpdateSpriteSlices (Texture texture, Atlas atlas) {
+			string texturePath = AssetDatabase.GetAssetPath(texture.GetInstanceID());
+			var t = (TextureImporter)TextureImporter.GetAtPath(texturePath);
+			t.spriteImportMode = SpriteImportMode.Multiple;
+			var spriteSheet = t.spritesheet;
+			var sprites = new List<SpriteMetaData>(spriteSheet);
+
+			var regions = AtlasAssetInspector.GetRegions(atlas);
+			int textureHeight = texture.height;
+			char[] FilenameDelimiter = {'.'};
+			int updatedCount = 0;
+			int addedCount = 0;
+
+			foreach (var r in regions) {
+				int width, height;
+				if (r.rotate) {
+					width = r.height;
+					height = r.width;
+				} else {
+					width = r.width;
+					height = r.height;
+				}
+
+				int x = r.x;
+				int y = textureHeight - height - r.y;
+
+				string pageName = r.page.name.Split(FilenameDelimiter, StringSplitOptions.RemoveEmptyEntries)[0];
+				string textureName = texture.name;
+				bool pageMatch = string.Equals(pageName, textureName,StringComparison.Ordinal);
+				int spriteIndex = pageMatch ? sprites.FindIndex(
+					(s) => string.Equals(s.name, r.name, StringComparison.Ordinal)
+				) : -1;
+				bool matchFound = spriteIndex >= 0;
+
+				if (matchFound) {
+					var s = sprites[spriteIndex];
+					s.rect = new Rect(x, y, width, height);
+					sprites[spriteIndex] = s;
+					updatedCount++;
+				} else {
+					if (pageMatch) {
+						sprites.Add(new SpriteMetaData {
+							name = r.name,
+							pivot = new Vector2(0.5f, 0.5f),
+							rect = new Rect(x, y, width, height)
+						});
+						addedCount++;
+					}
+				}
+			}
+
+			t.spritesheet = sprites.ToArray();
+			EditorUtility.SetDirty(t);
+			AssetDatabase.ImportAsset(texturePath, ImportAssetOptions.ForceUpdate);
+			EditorGUIUtility.PingObject(texture);
+			Debug.Log(string.Format("Applied sprite slices to {2}. {0} added. {1} updated.", addedCount, updatedCount, texture.name));
+		}
 	}
 	}
 
 
 }
 }

+ 118 - 133
spine-unity/Assets/spine-unity/Asset Types/Editor/SkeletonDataAssetInspector.cs

@@ -31,19 +31,17 @@
 // Contributed by: Mitch Thompson
 // Contributed by: Mitch Thompson
 
 
 #define SPINE_SKELETON_ANIMATOR
 #define SPINE_SKELETON_ANIMATOR
-#define SPINE_BAKING
+//#define SPINE_BAKING
 
 
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using UnityEditor;
 using UnityEditor;
-
-#if !UNITY_4_3
-using UnityEditor.AnimatedValues;
-#endif
 using UnityEngine;
 using UnityEngine;
 using Spine;
 using Spine;
 
 
 namespace Spine.Unity.Editor {
 namespace Spine.Unity.Editor {
+	using Event = UnityEngine.Event;
+
 	[CustomEditor(typeof(SkeletonDataAsset))]
 	[CustomEditor(typeof(SkeletonDataAsset))]
 	public class SkeletonDataAssetInspector : UnityEditor.Editor {
 	public class SkeletonDataAssetInspector : UnityEditor.Editor {
 		static bool showAnimationStateData = true;
 		static bool showAnimationStateData = true;
@@ -75,9 +73,10 @@ namespace Spine.Unity.Editor {
 		string m_skeletonDataAssetGUID;
 		string m_skeletonDataAssetGUID;
 		bool needToSerialize;
 		bool needToSerialize;
 
 
-		List<string> warnings = new List<string>();
+		readonly List<string> warnings = new List<string>();
 
 
 		GUIStyle activePlayButtonStyle, idlePlayButtonStyle;
 		GUIStyle activePlayButtonStyle, idlePlayButtonStyle;
+		readonly GUIContent DefaultMixLabel = new GUIContent("Default Mix Duration", "Sets 'SkeletonDataAsset.defaultMix' in the asset and 'AnimationState.data.defaultMix' at runtime load time.");
 
 
 		void OnEnable () {
 		void OnEnable () {
 			SpineEditorUtilities.ConfirmInitialization();
 			SpineEditorUtilities.ConfirmInitialization();
@@ -126,29 +125,51 @@ namespace Spine.Unity.Editor {
 			// Lazy initialization
 			// Lazy initialization
 			{ 
 			{ 
 				// Accessing EditorStyles values in OnEnable during a recompile causes UnityEditor to throw null exceptions. (Unity 5.3.5)
 				// Accessing EditorStyles values in OnEnable during a recompile causes UnityEditor to throw null exceptions. (Unity 5.3.5)
-				idlePlayButtonStyle = idlePlayButtonStyle ?? new GUIStyle(EditorStyles.toolbarButton);
+				idlePlayButtonStyle = idlePlayButtonStyle ?? new GUIStyle(EditorStyles.miniButton);
 				if (activePlayButtonStyle == null) {
 				if (activePlayButtonStyle == null) {
-					activePlayButtonStyle = new GUIStyle(EditorStyles.toolbarButton);
+					activePlayButtonStyle = new GUIStyle(idlePlayButtonStyle);
 					activePlayButtonStyle.normal.textColor = Color.red;
 					activePlayButtonStyle.normal.textColor = Color.red;
 				}
 				}
 			}
 			}
 
 
 			serializedObject.Update();
 			serializedObject.Update();
 
 
+			EditorGUILayout.LabelField(new GUIContent(target.name + " (SkeletonDataAsset)", SpineEditorUtilities.Icons.spine), EditorStyles.whiteLargeLabel);
+
 			EditorGUI.BeginChangeCheck();
 			EditorGUI.BeginChangeCheck();
-			#if !SPINE_TK2D
-			EditorGUILayout.PropertyField(atlasAssets, true);
-			#else
-			using (new EditorGUI.DisabledGroupScope(spriteCollection.objectReferenceValue != null)) {
+
+			// SkeletonData
+			using (new SpineInspectorUtility.BoxScope()) {
+				EditorGUILayout.LabelField("SkeletonData", EditorStyles.boldLabel);
+				EditorGUILayout.PropertyField(skeletonJSON, new GUIContent(skeletonJSON.displayName, SpineEditorUtilities.Icons.spine));
+				EditorGUILayout.PropertyField(scale);
+
+//				if (m_skeletonData != null) {
+//					var sd = m_skeletonData;
+//					using (new GUILayout.HorizontalScope()) {
+//						GUILayout.Space(15f);
+//						GUILayout.Label(
+//							string.Format("{8} - {0} {1}\nBones: {2}\tConstraints: {5} IK + {6} Path + {7} Transform\nSlots: {3}\t\tSkins: {4}\n",
+//								sd.Version, string.IsNullOrEmpty(sd.Version) ? "" : "export", sd.Bones.Count, sd.Slots.Count, sd.Skins.Count, sd.IkConstraints.Count, sd.PathConstraints.Count, sd.TransformConstraints.Count, skeletonJSON.objectReferenceValue.name),
+//							SpineInspectorUtility.GrayMiniLabel);
+//					}
+//				}
+			}
+
+			// Atlas
+			using (new SpineInspectorUtility.BoxScope()) {
+				EditorGUILayout.LabelField("Atlas", EditorStyles.boldLabel);
+				#if !SPINE_TK2D
 				EditorGUILayout.PropertyField(atlasAssets, true);
 				EditorGUILayout.PropertyField(atlasAssets, true);
+				#else
+				using (new EditorGUI.DisabledGroupScope(spriteCollection.objectReferenceValue != null)) {
+					EditorGUILayout.PropertyField(atlasAssets, true);
+				}
+				EditorGUILayout.LabelField("spine-tk2d", EditorStyles.boldLabel);
+				EditorGUILayout.PropertyField(spriteCollection, true);
+				#endif
 			}
 			}
-			EditorGUILayout.LabelField("spine-tk2d", EditorStyles.boldLabel);
-			EditorGUILayout.PropertyField(spriteCollection, true);
-			#endif
-			EditorGUILayout.Space();
-			EditorGUILayout.PropertyField(skeletonJSON);
-			EditorGUILayout.PropertyField(scale);
-			EditorGUILayout.Space();
+
 			if (EditorGUI.EndChangeCheck()) {
 			if (EditorGUI.EndChangeCheck()) {
 				if (serializedObject.ApplyModifiedProperties()) {
 				if (serializedObject.ApplyModifiedProperties()) {
 					if (m_previewUtility != null) {
 					if (m_previewUtility != null) {
@@ -165,9 +186,19 @@ namespace Spine.Unity.Editor {
 			// If m_skeletonAnimation is lazy-instantiated elsewhere, this can cause contents to change between Layout and Repaint events, causing GUILayout control count errors.
 			// If m_skeletonAnimation is lazy-instantiated elsewhere, this can cause contents to change between Layout and Repaint events, causing GUILayout control count errors.
 			InitPreview();
 			InitPreview();
 			if (m_skeletonData != null) {
 			if (m_skeletonData != null) {
-				DrawAnimationStateInfo();
+				
+				using (new SpineInspectorUtility.BoxScope()) {
+					EditorGUILayout.LabelField("Mix Settings", EditorStyles.boldLabel);
+					DrawAnimationStateInfo();
+					EditorGUILayout.Space();
+				}
+
+				EditorGUILayout.LabelField("Preview", EditorStyles.boldLabel);
 				DrawAnimationList();
 				DrawAnimationList();
+				EditorGUILayout.Space();
 				DrawSlotList();
 				DrawSlotList();
+				EditorGUILayout.Space();
+
 				DrawUnityTools();
 				DrawUnityTools();
 			} else {
 			} else {
 				#if !SPINE_TK2D
 				#if !SPINE_TK2D
@@ -185,7 +216,6 @@ namespace Spine.Unity.Editor {
 				// List warnings.
 				// List warnings.
 				foreach (var line in warnings)
 				foreach (var line in warnings)
 					EditorGUILayout.LabelField(new GUIContent(line, SpineEditorUtilities.Icons.warning));
 					EditorGUILayout.LabelField(new GUIContent(line, SpineEditorUtilities.Icons.warning));
-				
 			}
 			}
 
 
 			if (!Application.isPlaying)
 			if (!Application.isPlaying)
@@ -194,31 +224,33 @@ namespace Spine.Unity.Editor {
 
 
 		void DrawUnityTools () {
 		void DrawUnityTools () {
 			#if SPINE_SKELETON_ANIMATOR
 			#if SPINE_SKELETON_ANIMATOR
-			isMecanimExpanded = EditorGUILayout.Foldout(isMecanimExpanded, new GUIContent("SkeletonAnimator", SpineEditorUtilities.Icons.unityIcon));
-			if (isMecanimExpanded) {
-				EditorGUI.indentLevel++;
-				EditorGUILayout.PropertyField(controller, new GUIContent("Controller", SpineEditorUtilities.Icons.controllerIcon));		
-				if (controller.objectReferenceValue == null) {
-					
-					// Generate Mecanim Controller Button
-					using (new GUILayout.HorizontalScope()) {
-						GUILayout.Space(EditorGUIUtility.labelWidth);
-						if (GUILayout.Button(new GUIContent("Generate Mecanim Controller"), GUILayout.Height(20)))
-							SkeletonBaker.GenerateMecanimAnimationClips(m_skeletonDataAsset);						
-					}
-					EditorGUILayout.HelpBox("SkeletonAnimator is the Mecanim alternative to SkeletonAnimation.\nIt is not required.", MessageType.Info);
+			using (new SpineInspectorUtility.BoxScope()) {
+				isMecanimExpanded = EditorGUILayout.Foldout(isMecanimExpanded, new GUIContent("SkeletonAnimator", SpineEditorUtilities.Icons.unityIcon));
+				if (isMecanimExpanded) {
+					EditorGUI.indentLevel++;
+					EditorGUILayout.PropertyField(controller, new GUIContent("Controller", SpineEditorUtilities.Icons.controllerIcon));		
+					if (controller.objectReferenceValue == null) {
+
+						// Generate Mecanim Controller Button
+						using (new GUILayout.HorizontalScope()) {
+							GUILayout.Space(EditorGUIUtility.labelWidth);
+							if (GUILayout.Button(new GUIContent("Generate Mecanim Controller"), GUILayout.Height(20)))
+								SkeletonBaker.GenerateMecanimAnimationClips(m_skeletonDataAsset);						
+						}
+						EditorGUILayout.HelpBox("SkeletonAnimator is the Mecanim alternative to SkeletonAnimation.\nIt is not required.", MessageType.Info);
 
 
-				} else {
-					
-					// Update AnimationClips button.
-					using (new GUILayout.HorizontalScope()) {
-						GUILayout.Space(EditorGUIUtility.labelWidth);
-						if (GUILayout.Button(new GUIContent("Force Update AnimationClips"), GUILayout.Height(20)))
-							SkeletonBaker.GenerateMecanimAnimationClips(m_skeletonDataAsset);				
-					}
+					} else {
+
+						// Update AnimationClips button.
+						using (new GUILayout.HorizontalScope()) {
+							GUILayout.Space(EditorGUIUtility.labelWidth);
+							if (GUILayout.Button(new GUIContent("Force Update AnimationClips"), GUILayout.Height(20)))
+								SkeletonBaker.GenerateMecanimAnimationClips(m_skeletonDataAsset);				
+						}
 
 
+					}
+					EditorGUI.indentLevel--;
 				}
 				}
-				EditorGUI.indentLevel--;
 			}
 			}
 			#endif
 			#endif
 
 
@@ -310,7 +342,7 @@ namespace Spine.Unity.Editor {
 				return;
 				return;
 
 
 			EditorGUI.BeginChangeCheck();
 			EditorGUI.BeginChangeCheck();
-			EditorGUILayout.PropertyField(defaultMix);
+			SpineInspectorUtility.PropertyFieldWideLabel(defaultMix, DefaultMixLabel, 160);
 
 
 			var animations = new string[m_skeletonData.Animations.Count];
 			var animations = new string[m_skeletonData.Animations.Count];
 			for (int i = 0; i < animations.Length; i++)
 			for (int i = 0; i < animations.Length; i++)
@@ -351,7 +383,7 @@ namespace Spine.Unity.Editor {
 		}
 		}
 
 
 		void DrawAnimationList () {
 		void DrawAnimationList () {
-			showAnimationList = EditorGUILayout.Foldout(showAnimationList, new GUIContent("Animations", SpineEditorUtilities.Icons.animationRoot));
+			showAnimationList = EditorGUILayout.Foldout(showAnimationList, new GUIContent(string.Format("Animations [{0}]", m_skeletonData.Animations.Count), SpineEditorUtilities.Icons.animationRoot));
 			if (!showAnimationList)
 			if (!showAnimationList)
 				return;
 				return;
 
 
@@ -394,16 +426,10 @@ namespace Spine.Unity.Editor {
 			if (m_skeletonAnimation == null || m_skeletonAnimation.skeleton == null) return;
 			if (m_skeletonAnimation == null || m_skeletonAnimation.skeleton == null) return;
 
 
 			EditorGUI.indentLevel++;
 			EditorGUI.indentLevel++;
-
-			try {
-				showAttachments = EditorGUILayout.ToggleLeft("Show Attachments", showAttachments);
-			} catch {
-				return;
-			}
-
-			List<Attachment> slotAttachments = new List<Attachment>();
-			List<string> slotAttachmentNames = new List<string>();
-			List<string> defaultSkinAttachmentNames = new List<string>();
+			showAttachments = EditorGUILayout.ToggleLeft("Show Attachments", showAttachments);
+			var slotAttachments = new List<Attachment>();
+			var slotAttachmentNames = new List<string>();
+			var defaultSkinAttachmentNames = new List<string>();
 			var defaultSkin = m_skeletonData.Skins.Items[0];
 			var defaultSkin = m_skeletonData.Skins.Items[0];
 			Skin skin = m_skeletonAnimation.skeleton.Skin ?? defaultSkin;
 			Skin skin = m_skeletonAnimation.skeleton.Skin ?? defaultSkin;
 
 
@@ -495,7 +521,7 @@ namespace Spine.Unity.Editor {
 					}
 					}
 
 
 					if (detectedNullAtlasEntry)
 					if (detectedNullAtlasEntry)
-						warnings.Add("AtlasAsset elements cannot be Null");
+						warnings.Add("AtlasAsset elements should not be null.");
 					else {
 					else {
 						// Get requirements.
 						// Get requirements.
 						var missingPaths = SpineEditorUtilities.GetRequiredAtlasRegions(AssetDatabase.GetAssetPath((TextAsset)skeletonJSON.objectReferenceValue));
 						var missingPaths = SpineEditorUtilities.GetRequiredAtlasRegions(AssetDatabase.GetAssetPath((TextAsset)skeletonJSON.objectReferenceValue));
@@ -514,11 +540,10 @@ namespace Spine.Unity.Editor {
 						
 						
 					}
 					}
 					#else
 					#else
-					if (spriteCollection.objectReferenceValue == null) {
+					if (spriteCollection.objectReferenceValue == null)
 						warnings.Add("SkeletonDataAsset requires tk2DSpriteCollectionData.");
 						warnings.Add("SkeletonDataAsset requires tk2DSpriteCollectionData.");
-					} else {
+					else
 						warnings.Add("Your sprite collection may have missing images.");
 						warnings.Add("Your sprite collection may have missing images.");
-					}
 					#endif
 					#endif
 				}
 				}
 			}
 			}
@@ -556,13 +581,11 @@ namespace Spine.Unity.Editor {
 			Spine.Animation a = m_skeletonAnimation.state.GetCurrent(0).Animation;
 			Spine.Animation a = m_skeletonAnimation.state.GetCurrent(0).Animation;
 			foreach (Timeline t in a.Timelines) {
 			foreach (Timeline t in a.Timelines) {
 				if (t.GetType() == typeof(EventTimeline)) {
 				if (t.GetType() == typeof(EventTimeline)) {
-					EventTimeline et = (EventTimeline)t;
-
+					var et = (EventTimeline)t;
 					for (int i = 0; i < et.Events.Length; i++) {
 					for (int i = 0; i < et.Events.Length; i++) {
 						m_animEvents.Add(et.Events[i]);
 						m_animEvents.Add(et.Events[i]);
 						m_animEventFrames.Add(et.Frames[i]);
 						m_animEventFrames.Add(et.Frames[i]);
 					}
 					}
-
 				}
 				}
 			}
 			}
 
 
@@ -633,7 +656,7 @@ namespace Spine.Unity.Editor {
 		public override void OnInteractivePreviewGUI (Rect r, GUIStyle background) {
 		public override void OnInteractivePreviewGUI (Rect r, GUIStyle background) {
 			this.InitPreview();
 			this.InitPreview();
 
 
-			if (UnityEngine.Event.current.type == EventType.Repaint) {
+			if (Event.current.type == EventType.Repaint) {
 				if (m_requireRefresh) {
 				if (m_requireRefresh) {
 					this.m_previewUtility.BeginPreview(r, background);
 					this.m_previewUtility.BeginPreview(r, background);
 					this.DoRenderPreview(true);
 					this.DoRenderPreview(true);
@@ -661,7 +684,7 @@ namespace Spine.Unity.Editor {
 
 
 			if (calculateMixTime) {
 			if (calculateMixTime) {
 				if (m_skeletonAnimation.state.GetCurrent(0) != null)
 				if (m_skeletonAnimation.state.GetCurrent(0) != null)
-					m_adjustFrameEndTime = EditorApplication.timeSinceStartup + m_skeletonAnimation.state.GetCurrent(0).Mix;
+					m_adjustFrameEndTime = EditorApplication.timeSinceStartup + m_skeletonAnimation.state.GetCurrent(0).Alpha;
 			}
 			}
 				
 				
 			GameObject go = this.m_previewInstance;
 			GameObject go = this.m_previewInstance;
@@ -724,7 +747,7 @@ namespace Spine.Unity.Editor {
 					foreach (var slot in m_skeletonAnimation.skeleton.Slots) {
 					foreach (var slot in m_skeletonAnimation.skeleton.Slots) {
 						var boundingBoxAttachment = slot.Attachment as BoundingBoxAttachment;
 						var boundingBoxAttachment = slot.Attachment as BoundingBoxAttachment;
 						if (boundingBoxAttachment != null)
 						if (boundingBoxAttachment != null)
-							DrawBoundingBox (slot, boundingBoxAttachment);
+							SpineEditorUtilities.DrawBoundingBox(slot, boundingBoxAttachment);
 					}
 					}
 				}
 				}
 
 
@@ -733,30 +756,6 @@ namespace Spine.Unity.Editor {
 				
 				
 		}
 		}
 
 
-		static void DrawBoundingBox (Slot slot, BoundingBoxAttachment box) {
-			if (box.Vertices.Length <= 0) return; // Handle cases where user creates a BoundingBoxAttachment but doesn't actually define it.
-
-			var worldVerts = new float[box.Vertices.Length];
-			box.ComputeWorldVertices(slot, worldVerts);
-
-			Handles.color = Color.green;
-			Vector3 lastVert = Vector3.back;
-			Vector3 vert = Vector3.back;
-			Vector3 firstVert = new Vector3(worldVerts[0], worldVerts[1], -1);
-			for (int i = 0; i < worldVerts.Length; i += 2) {
-				vert.x = worldVerts[i];
-				vert.y = worldVerts[i + 1];
-
-				if (i > 0)
-					Handles.DrawLine(lastVert, vert);
-
-				lastVert = vert;
-			}
-
-			Handles.DrawLine(lastVert, firstVert);
-
-		}
-
 		void EditorUpdate () {
 		void EditorUpdate () {
 			AdjustCamera();
 			AdjustCamera();
 
 
@@ -815,12 +814,12 @@ namespace Spine.Unity.Editor {
 			TrackEntry t = m_skeletonAnimation.state.GetCurrent(0);
 			TrackEntry t = m_skeletonAnimation.state.GetCurrent(0);
 
 
 			if (t != null) {
 			if (t != null) {
-				int loopCount = (int)(t.Time / t.EndTime);
-				float currentTime = t.Time - (t.EndTime * loopCount);
-
+				int loopCount = (int)(t.TrackTime / t.TrackEnd);
+				float currentTime = t.TrackTime - (t.TrackEnd * loopCount);
 				float normalizedTime = currentTime / t.Animation.Duration;
 				float normalizedTime = currentTime / t.Animation.Duration;
+				float wrappedTime = normalizedTime % 1;
 
 
-				lineRect.x = barRect.x + (width * normalizedTime) - 0.5f;
+				lineRect.x = barRect.x + (width * wrappedTime) - 0.5f;
 				lineRect.width = 2;
 				lineRect.width = 2;
 
 
 				GUI.color = Color.red;
 				GUI.color = Color.red;
@@ -828,34 +827,32 @@ namespace Spine.Unity.Editor {
 				GUI.color = Color.white;
 				GUI.color = Color.white;
 
 
 				for (int i = 0; i < m_animEvents.Count; i++) {
 				for (int i = 0; i < m_animEvents.Count; i++) {
-					// MITCH: left todo: Tooltip
-					//Spine.Event spev = animEvents[i];
-
 					float fr = m_animEventFrames[i];
 					float fr = m_animEventFrames[i];
 					var evRect = new Rect(barRect);
 					var evRect = new Rect(barRect);
-					evRect.x = Mathf.Clamp(((fr / t.Animation.Duration) * width) - (SpineEditorUtilities.Icons._event.width / 2), barRect.x, float.MaxValue);
-					evRect.width = SpineEditorUtilities.Icons._event.width;
-					evRect.height = SpineEditorUtilities.Icons._event.height;
-					evRect.y += SpineEditorUtilities.Icons._event.height;
-					GUI.DrawTexture(evRect, SpineEditorUtilities.Icons._event);
-
-					// MITCH: left todo:  Tooltip
-//					UnityEngine.Event ev = UnityEngine.Event.current;
-//					if (ev.isMouse) {
-//						if (evRect.Contains(ev.mousePosition)) {
-//							Rect tooltipRect = new Rect(evRect);
-//							tooltipRect.width = 500;
-//							tooltipRect.y -= 4;
-//							tooltipRect.x += 4;
-//							GUI.Label(tooltipRect, spev.Data.Name);
-//						}
-//					}
+					evRect.x = Mathf.Clamp(((fr / t.Animation.Duration) * width) - (SpineEditorUtilities.Icons.userEvent.width / 2), barRect.x, float.MaxValue);
+					evRect.width = SpineEditorUtilities.Icons.userEvent.width;
+					evRect.height = SpineEditorUtilities.Icons.userEvent.height;
+					evRect.y += SpineEditorUtilities.Icons.userEvent.height;
+					GUI.DrawTexture(evRect, SpineEditorUtilities.Icons.userEvent);
+
+					Event ev = Event.current;
+					if (ev.type == EventType.Repaint) {
+						if (evRect.Contains(ev.mousePosition)) {
+							Rect tooltipRect = new Rect(evRect);
+							GUIStyle tooltipStyle = EditorStyles.helpBox;
+							tooltipRect.width = tooltipStyle.CalcSize(new GUIContent(m_animEvents[i].Data.Name)).x;
+							tooltipRect.y -= 4;
+							tooltipRect.x += 4;
+							GUI.Label(tooltipRect,  m_animEvents[i].Data.Name, tooltipStyle);
+							GUI.tooltip = m_animEvents[i].Data.Name;
+						}
+					}
 				}
 				}
 			}
 			}
 		}
 		}
 
 
 		void MouseScroll (Rect position) {
 		void MouseScroll (Rect position) {
-			UnityEngine.Event current = UnityEngine.Event.current;
+			Event current = Event.current;
 			int controlID = GUIUtility.GetControlID(SliderHash, FocusType.Passive);
 			int controlID = GUIUtility.GetControlID(SliderHash, FocusType.Passive);
 			switch (current.GetTypeForControl(controlID)) {
 			switch (current.GetTypeForControl(controlID)) {
 			case EventType.ScrollWheel:
 			case EventType.ScrollWheel:
@@ -916,46 +913,36 @@ namespace Spine.Unity.Editor {
 		}
 		}
 
 
 		public override void OnPreviewSettings () {
 		public override void OnPreviewSettings () {
+			const float SliderWidth = 100;
 			if (!m_initialized) {
 			if (!m_initialized) {
-				GUILayout.HorizontalSlider(0, 0, 2, GUILayout.MaxWidth(64));
+				GUILayout.HorizontalSlider(0, 0, 2, GUILayout.MaxWidth(SliderWidth));
 			} else {
 			} else {
-				float speed = GUILayout.HorizontalSlider(m_skeletonAnimation.timeScale, 0, 2, GUILayout.MaxWidth(64));
+				float speed = GUILayout.HorizontalSlider(m_skeletonAnimation.timeScale, 0, 2, GUILayout.MaxWidth(SliderWidth));
 
 
-				//snap to nearest 0.25
-				float y = speed / 0.25f;
+				const float SliderSnap = 0.25f;
+				float y = speed / SliderSnap;
 				int q = Mathf.RoundToInt(y);
 				int q = Mathf.RoundToInt(y);
-				speed = q * 0.25f;
+				speed = q * SliderSnap;
 
 
 				m_skeletonAnimation.timeScale = speed;
 				m_skeletonAnimation.timeScale = speed;
 			}
 			}
 		}
 		}
 
 
-		// MITCH: left todo: Fix first-import error
-		// MITCH: left todo: Update preview without thumbnail
+
 		public override Texture2D RenderStaticPreview (string assetPath, UnityEngine.Object[] subAssets, int width, int height) {
 		public override Texture2D RenderStaticPreview (string assetPath, UnityEngine.Object[] subAssets, int width, int height) {
 			var tex = new Texture2D(width, height, TextureFormat.ARGB32, false);
 			var tex = new Texture2D(width, height, TextureFormat.ARGB32, false);
 
 
 			this.InitPreview();
 			this.InitPreview();
-
 			if (this.m_previewUtility.m_Camera == null)
 			if (this.m_previewUtility.m_Camera == null)
 				return null;
 				return null;
 
 
 			m_requireRefresh = true;
 			m_requireRefresh = true;
 			this.DoRenderPreview(false);
 			this.DoRenderPreview(false);
 			AdjustCameraGoals(false);
 			AdjustCameraGoals(false);
-
 			this.m_previewUtility.m_Camera.orthographicSize = m_orthoGoal / 2;
 			this.m_previewUtility.m_Camera.orthographicSize = m_orthoGoal / 2;
 			this.m_previewUtility.m_Camera.transform.position = m_posGoal;
 			this.m_previewUtility.m_Camera.transform.position = m_posGoal;
 			this.m_previewUtility.BeginStaticPreview(new Rect(0, 0, width, height));
 			this.m_previewUtility.BeginStaticPreview(new Rect(0, 0, width, height));
 			this.DoRenderPreview(false);
 			this.DoRenderPreview(false);
-
-			//MITCH: left todo:  Figure out why this is throwing errors on first attempt
-			//		if(m_previewUtility != null){
-			//			Handles.SetCamera(this.m_previewUtility.m_Camera);
-			//			Handles.BeginGUI();
-			//			GUI.DrawTexture(new Rect(40,60,width,height), SpineEditorUtilities.Icons.spine, ScaleMode.StretchToFill);
-			//			Handles.EndGUI();
-			//		}
 			tex = this.m_previewUtility.EndStaticPreview();
 			tex = this.m_previewUtility.EndStaticPreview();
 			return tex;
 			return tex;
 		}
 		}
@@ -972,11 +959,9 @@ namespace Spine.Unity.Editor {
 
 
 		void SetSkin (object o) {
 		void SetSkin (object o) {
 			Skin skin = (Skin)o;
 			Skin skin = (Skin)o;
-
 			m_skeletonAnimation.initialSkinName = skin.Name;
 			m_skeletonAnimation.initialSkinName = skin.Name;
 			m_skeletonAnimation.Initialize(true);
 			m_skeletonAnimation.Initialize(true);
 			m_requireRefresh = true;
 			m_requireRefresh = true;
-
 			EditorPrefs.SetString(m_skeletonDataAssetGUID + "_lastSkin", skin.Name);
 			EditorPrefs.SetString(m_skeletonDataAssetGUID + "_lastSkin", skin.Name);
 		}
 		}
 		#endregion
 		#endregion

+ 23 - 0
spine-unity/Assets/spine-unity/Asset Types/SkeletonDataAsset.cs

@@ -51,6 +51,29 @@ namespace Spine.Unity {
 		private SkeletonData skeletonData;
 		private SkeletonData skeletonData;
 		private AnimationStateData stateData;
 		private AnimationStateData stateData;
 
 
+		#region Runtime Instantiation
+		/// <summary>
+		/// Creates a runtime SkeletonDataAsset.</summary>
+		public static SkeletonDataAsset CreateRuntimeInstance (TextAsset skeletonDataFile, AtlasAsset atlasAsset, bool initialize, float scale = 0.01f) {
+			return CreateRuntimeInstance(skeletonDataFile, new [] {atlasAsset}, initialize, scale);
+		}
+
+		/// <summary>
+		/// Creates a runtime SkeletonDataAsset.</summary>
+		public static SkeletonDataAsset CreateRuntimeInstance (TextAsset skeletonDataFile, AtlasAsset[] atlasAssets, bool initialize, float scale = 0.01f) {
+			SkeletonDataAsset skeletonDataAsset = ScriptableObject.CreateInstance<SkeletonDataAsset>();
+			skeletonDataAsset.Reset();
+			skeletonDataAsset.skeletonJSON = skeletonDataFile;
+			skeletonDataAsset.atlasAssets = atlasAssets;
+			skeletonDataAsset.scale = scale;
+
+			if (initialize)
+				skeletonDataAsset.GetSkeletonData(true);
+
+			return skeletonDataAsset;
+		}
+		#endregion
+
 		void OnEnable () {
 		void OnEnable () {
 			if (atlasAssets == null)
 			if (atlasAssets == null)
 				atlasAssets = new AtlasAsset[0];
 				atlasAssets = new AtlasAsset[0];

+ 1 - 15
spine-unity/Assets/spine-unity/Editor/BoneFollowerInspector.cs

@@ -61,7 +61,7 @@ namespace Spine.Unity.Editor {
 
 
 			// Find Renderer
 			// Find Renderer
 			if (skeletonRenderer.objectReferenceValue == null) {
 			if (skeletonRenderer.objectReferenceValue == null) {
-				SkeletonRenderer parentRenderer = BoneFollowerInspector.GetInParent<SkeletonRenderer>(targetBoneFollower.transform);
+				SkeletonRenderer parentRenderer = targetBoneFollower.GetComponentInParent<SkeletonRenderer>();
 				if (parentRenderer != null && parentRenderer.gameObject != targetBoneFollower.gameObject) {
 				if (parentRenderer != null && parentRenderer.gameObject != targetBoneFollower.gameObject) {
 					skeletonRenderer.objectReferenceValue = parentRenderer;
 					skeletonRenderer.objectReferenceValue = parentRenderer;
 					Debug.Log("Inspector automatically assigned BoneFollower.SkeletonRenderer");
 					Debug.Log("Inspector automatically assigned BoneFollower.SkeletonRenderer");
@@ -108,20 +108,6 @@ namespace Spine.Unity.Editor {
 			if (serializedObject.ApplyModifiedProperties() || wasUndo)
 			if (serializedObject.ApplyModifiedProperties() || wasUndo)
 				targetBoneFollower.Initialize();
 				targetBoneFollower.Initialize();
 		}
 		}
-
-		public static T GetInParent<T> (Transform origin) where T : Component {
-			#if UNITY_4_3
-			Transform parent = origin.parent;
-			while (parent.GetComponent<T>() == null) {
-				parent = parent.parent;
-				if(parent == null)
-					return default(T);
-			}
-			return parent.GetComponent<T>();
-			#else
-			return origin.GetComponentInParent<T>();
-			#endif
-		}
 	}
 	}
 
 
 }
 }

+ 3 - 3
spine-unity/Assets/spine-unity/Editor/Menus.cs

@@ -35,12 +35,12 @@ using UnityEngine;
 
 
 namespace Spine.Unity.Editor {
 namespace Spine.Unity.Editor {
 	public static class Menus {
 	public static class Menus {
-		[MenuItem("Assets/Create/Spine Atlas")]
+		[MenuItem("Assets/Create/Spine/Atlas Asset")]
 		static public void CreateAtlas () {
 		static public void CreateAtlas () {
 			CreateAsset<AtlasAsset>("New Atlas");
 			CreateAsset<AtlasAsset>("New Atlas");
 		}
 		}
 
 
-		[MenuItem("Assets/Create/Spine SkeletonData")]
+		[MenuItem("Assets/Create/Spine/SkeletonData Asset")]
 		static public void CreateSkeletonData () {
 		static public void CreateSkeletonData () {
 			CreateAsset<SkeletonDataAsset>("New SkeletonData");
 			CreateAsset<SkeletonDataAsset>("New SkeletonData");
 		}
 		}
@@ -74,7 +74,7 @@ namespace Spine.Unity.Editor {
 			var parentGameObject = Selection.activeObject as GameObject;
 			var parentGameObject = Selection.activeObject as GameObject;
 			var parentTransform = parentGameObject == null ? null : parentGameObject.transform;
 			var parentTransform = parentGameObject == null ? null : parentGameObject.transform;
 
 
-			var gameObject = new GameObject("New SkeletonRenderer", typeof(T));
+			var gameObject = new GameObject(name, typeof(T));
 			gameObject.transform.SetParent(parentTransform, false);
 			gameObject.transform.SetParent(parentTransform, false);
 			EditorUtility.FocusProjectWindow();
 			EditorUtility.FocusProjectWindow();
 			Selection.activeObject = gameObject;
 			Selection.activeObject = gameObject;

+ 25 - 25
spine-unity/Assets/spine-unity/Editor/SkeletonAnimationInspector.cs

@@ -40,6 +40,8 @@ namespace Spine.Unity.Editor {
 		protected SerializedProperty animationName, loop, timeScale, autoReset;
 		protected SerializedProperty animationName, loop, timeScale, autoReset;
 		protected bool wasAnimationNameChanged;
 		protected bool wasAnimationNameChanged;
 		protected bool requireRepaint;
 		protected bool requireRepaint;
+		readonly GUIContent LoopLabel = new GUIContent("Loop", "Whether or not .AnimationName should loop. This only applies to the initial animation specified in the inspector, or any subsequent Animations played through .AnimationName. Animations set through state.SetAnimation are unaffected.");
+		readonly GUIContent TimeScaleLabel = new GUIContent("Time Scale", "The rate at which animations progress over time. 1 means normal speed. 0.5 means 50% speed.");
 
 
 		protected override void OnEnable () {
 		protected override void OnEnable () {
 			base.OnEnable();
 			base.OnEnable();
@@ -53,34 +55,33 @@ namespace Spine.Unity.Editor {
 			if (!TargetIsValid) return;
 			if (!TargetIsValid) return;
 			bool sameData = SpineInspectorUtility.TargetsUseSameData(serializedObject);
 			bool sameData = SpineInspectorUtility.TargetsUseSameData(serializedObject);
 
 
-			// Try to reflect the animation name on the scene object.
-			{
-				if (multi)
-					foreach (var o in targets)		
-						TrySetAnimation(o);
-				else
-					TrySetAnimation(target);
-			}
-			
-			EditorGUILayout.Space();
-
-			if (multi && !sameData)
-				EditorGUILayout.DelayedTextField(animationName);
-			else {
-				EditorGUI.BeginChangeCheck();
-				EditorGUILayout.PropertyField(animationName);
-				wasAnimationNameChanged |= EditorGUI.EndChangeCheck(); // Value used in the next update.
-			}
-				
-			EditorGUILayout.PropertyField(loop);
-
-			EditorGUILayout.PropertyField(timeScale);
 			if (multi) {
 			if (multi) {
+				foreach (var o in targets)		
+					TrySetAnimation(o, multi);
+				
+				EditorGUILayout.Space();
+				if (!sameData) {
+					EditorGUILayout.DelayedTextField(animationName);
+				} else {
+					EditorGUI.BeginChangeCheck();
+					EditorGUILayout.PropertyField(animationName);
+					wasAnimationNameChanged |= EditorGUI.EndChangeCheck(); // Value used in the next update.
+				}
+				EditorGUILayout.PropertyField(loop);
+				EditorGUILayout.PropertyField(timeScale);
 				foreach (var o in targets) {
 				foreach (var o in targets) {
 					var component = o as SkeletonAnimation;
 					var component = o as SkeletonAnimation;
 					component.timeScale = Mathf.Max(component.timeScale, 0);
 					component.timeScale = Mathf.Max(component.timeScale, 0);
 				}
 				}
 			} else {
 			} else {
+				TrySetAnimation(target, multi);
+
+				EditorGUILayout.Space();
+				EditorGUI.BeginChangeCheck();
+				EditorGUILayout.PropertyField(animationName);
+				wasAnimationNameChanged |= EditorGUI.EndChangeCheck(); // Value used in the next update.
+				EditorGUILayout.PropertyField(loop, LoopLabel);
+				EditorGUILayout.PropertyField(timeScale, TimeScaleLabel);
 				var component = (SkeletonAnimation)target;
 				var component = (SkeletonAnimation)target;
 				component.timeScale = Mathf.Max(component.timeScale, 0);
 				component.timeScale = Mathf.Max(component.timeScale, 0);
 			}
 			}
@@ -95,7 +96,7 @@ namespace Spine.Unity.Editor {
 			}
 			}
 		}
 		}
 
 
-		protected void TrySetAnimation (Object o) {
+		protected void TrySetAnimation (Object o, bool multi) {
 			var skeletonAnimation = o as SkeletonAnimation;
 			var skeletonAnimation = o as SkeletonAnimation;
 			if (skeletonAnimation == null) return;
 			if (skeletonAnimation == null) return;
 			if (!skeletonAnimation.valid)
 			if (!skeletonAnimation.valid)
@@ -111,7 +112,7 @@ namespace Spine.Unity.Editor {
 					Spine.Animation animationToUse = skeletonAnimation.skeleton.Data.FindAnimation(animationName.stringValue);
 					Spine.Animation animationToUse = skeletonAnimation.skeleton.Data.FindAnimation(animationName.stringValue);
 
 
 					if (!Application.isPlaying) {
 					if (!Application.isPlaying) {
-						if (animationToUse != null) animationToUse.Apply(skeletonAnimation.skeleton, 0f, 0f, false, null);
+						if (animationToUse != null) animationToUse.Apply(skeletonAnimation.skeleton, 0f, 0f, false, null, 1f, true, false);
 						skeletonAnimation.Update();
 						skeletonAnimation.Update();
 						skeletonAnimation.LateUpdate();
 						skeletonAnimation.LateUpdate();
 						requireRepaint = true;
 						requireRepaint = true;
@@ -126,7 +127,6 @@ namespace Spine.Unity.Editor {
 				}
 				}
 
 
 				// Reflect animationName serialized property in the inspector even if SetAnimation API was used.
 				// Reflect animationName serialized property in the inspector even if SetAnimation API was used.
-				bool multi = animationName.serializedObject.isEditingMultipleObjects;
 				if (!multi && Application.isPlaying) {
 				if (!multi && Application.isPlaying) {
 					TrackEntry current = skeletonAnimation.state.GetCurrent(0);
 					TrackEntry current = skeletonAnimation.state.GetCurrent(0);
 					if (current != null) {
 					if (current != null) {

+ 20 - 34
spine-unity/Assets/spine-unity/Editor/SkeletonBaker.cs

@@ -364,12 +364,13 @@ namespace Spine.Unity.Editor {
 
 
 					boneTransform.parent = parentTransform;
 					boneTransform.parent = parentTransform;
 					boneTransform.localPosition = new Vector3(boneData.X, boneData.Y, 0);
 					boneTransform.localPosition = new Vector3(boneData.X, boneData.Y, 0);
-					if (boneData.InheritRotation)
+					var tm = boneData.TransformMode;
+					if (tm.InheritsRotation())
 						boneTransform.localRotation = Quaternion.Euler(0, 0, boneData.Rotation);
 						boneTransform.localRotation = Quaternion.Euler(0, 0, boneData.Rotation);
 					else
 					else
 						boneTransform.rotation = Quaternion.Euler(0, 0, boneData.Rotation);
 						boneTransform.rotation = Quaternion.Euler(0, 0, boneData.Rotation);
 
 
-					if (boneData.InheritScale)
+					if (tm.InheritsScale())
 						boneTransform.localScale = new Vector3(boneData.ScaleX, boneData.ScaleY, 1);
 						boneTransform.localScale = new Vector3(boneData.ScaleX, boneData.ScaleY, 1);
 				}
 				}
 
 
@@ -409,7 +410,7 @@ namespace Spine.Unity.Editor {
 							offset.y = regionAttachment.Y;
 							offset.y = regionAttachment.Y;
 							rotation = regionAttachment.Rotation;
 							rotation = regionAttachment.Rotation;
 							mesh = ExtractRegionAttachment(attachmentMeshName, regionAttachment, mesh);
 							mesh = ExtractRegionAttachment(attachmentMeshName, regionAttachment, mesh);
-							material = ExtractMaterial(attachment);
+							material = attachment.GetMaterial();
 							unusedMeshNames.Remove(attachmentMeshName);
 							unusedMeshNames.Remove(attachmentMeshName);
 							if (newPrefab || meshTable.ContainsKey(attachmentMeshName) == false)
 							if (newPrefab || meshTable.ContainsKey(attachmentMeshName) == false)
 								AssetDatabase.AddObjectToAsset(mesh, prefab);
 								AssetDatabase.AddObjectToAsset(mesh, prefab);
@@ -425,7 +426,7 @@ namespace Spine.Unity.Editor {
 							else
 							else
 								mesh = ExtractMeshAttachment(attachmentMeshName, meshAttachment, mesh);
 								mesh = ExtractMeshAttachment(attachmentMeshName, meshAttachment, mesh);
 							
 							
-							material = ExtractMaterial(attachment);
+							material = attachment.GetMaterial();
 							unusedMeshNames.Remove(attachmentMeshName);
 							unusedMeshNames.Remove(attachmentMeshName);
 							if (newPrefab || meshTable.ContainsKey(attachmentMeshName) == false)
 							if (newPrefab || meshTable.ContainsKey(attachmentMeshName) == false)
 								AssetDatabase.AddObjectToAsset(mesh, prefab);
 								AssetDatabase.AddObjectToAsset(mesh, prefab);
@@ -533,21 +534,6 @@ namespace Spine.Unity.Editor {
 			return extractionSlot;
 			return extractionSlot;
 		}
 		}
 
 
-		static Material ExtractMaterial (Attachment attachment) {
-			if (attachment == null || attachment is BoundingBoxAttachment)
-				return null;
-
-			if (attachment is RegionAttachment) {
-				var att = (RegionAttachment)attachment;
-				return (Material)((AtlasRegion)att.RendererObject).page.rendererObject;
-			} else if (attachment is MeshAttachment) {
-				var att = (MeshAttachment)attachment;
-				return (Material)((AtlasRegion)att.RendererObject).page.rendererObject;
-			} else {
-				return null;
-			}
-		}
-
 		static Mesh ExtractRegionAttachment (string name, RegionAttachment attachment, Mesh mesh = null) {
 		static Mesh ExtractRegionAttachment (string name, RegionAttachment attachment, Mesh mesh = null) {
 			var bone = GetExtractionBone();
 			var bone = GetExtractionBone();
 
 
@@ -820,7 +806,7 @@ namespace Spine.Unity.Editor {
 			}
 			}
 
 
 			foreach (Bone b in skeleton.Bones) {
 			foreach (Bone b in skeleton.Bones) {
-				if (b.Data.InheritRotation == false) {
+				if (!b.Data.TransformMode.InheritsRotation()) {
 					int index = skeleton.FindBoneIndex(b.Data.Name);
 					int index = skeleton.FindBoneIndex(b.Data.Name);
 
 
 					if (ignoreRotateTimelineIndexes.Contains(index) == false) {
 					if (ignoreRotateTimelineIndexes.Contains(index) == false) {
@@ -989,10 +975,9 @@ namespace Spine.Unity.Editor {
 
 
 		static void BakeBone (Bone bone, Spine.Animation animation, AnimationClip clip) {
 		static void BakeBone (Bone bone, Spine.Animation animation, AnimationClip clip) {
 			Skeleton skeleton = bone.Skeleton;
 			Skeleton skeleton = bone.Skeleton;
-			bool inheritRotation = bone.Data.InheritRotation;
+			bool inheritRotation = bone.Data.TransformMode.InheritsRotation();
 
 
-			skeleton.SetToSetupPose();
-			animation.Apply(skeleton, 0, 0, true, null);
+			animation.Apply(skeleton, 0, 0, true, null, 1f, true, false);
 			skeleton.UpdateWorldTransform();
 			skeleton.UpdateWorldTransform();
 			float duration = animation.Duration;
 			float duration = animation.Duration;
 
 
@@ -1021,7 +1006,7 @@ namespace Spine.Unity.Editor {
 				if (i == steps)
 				if (i == steps)
 					currentTime = duration;
 					currentTime = duration;
 
 
-				animation.Apply(skeleton, lastTime, currentTime, true, null);
+				animation.Apply(skeleton, lastTime, currentTime, true, null, 1f, true, false);
 				skeleton.UpdateWorldTransform();
 				skeleton.UpdateWorldTransform();
 
 
 				int pIndex = listIndex - 1;
 				int pIndex = listIndex - 1;
@@ -1119,7 +1104,7 @@ namespace Spine.Unity.Editor {
 
 
 					currentTime = time;
 					currentTime = time;
 
 
-					timeline.Apply(skeleton, lastTime, currentTime, null, 1);
+					timeline.Apply(skeleton, lastTime, currentTime, null, 1, false, false);
 
 
 					lastTime = time;
 					lastTime = time;
 					listIndex++;
 					listIndex++;
@@ -1146,7 +1131,7 @@ namespace Spine.Unity.Editor {
 
 
 					currentTime = time;
 					currentTime = time;
 
 
-					timeline.Apply(skeleton, lastTime, currentTime, null, 1);
+					timeline.Apply(skeleton, lastTime, currentTime, null, 1, false, false);
 
 
 					lastTime = time;
 					lastTime = time;
 					listIndex++;
 					listIndex++;
@@ -1165,7 +1150,7 @@ namespace Spine.Unity.Editor {
 						if (i == steps)
 						if (i == steps)
 							currentTime = time;
 							currentTime = time;
 
 
-						timeline.Apply(skeleton, lastTime, currentTime, null, 1);
+						timeline.Apply(skeleton, lastTime, currentTime, null, 1, false, false);
 
 
 						px = xKeys[listIndex - 1];
 						px = xKeys[listIndex - 1];
 						py = yKeys[listIndex - 1];
 						py = yKeys[listIndex - 1];
@@ -1262,7 +1247,7 @@ namespace Spine.Unity.Editor {
 
 
 					currentTime = time;
 					currentTime = time;
 
 
-					timeline.Apply(skeleton, lastTime, currentTime, null, 1);
+					timeline.Apply(skeleton, lastTime, currentTime, null, 1, false, false);
 
 
 					lastTime = time;
 					lastTime = time;
 					listIndex++;
 					listIndex++;
@@ -1289,7 +1274,7 @@ namespace Spine.Unity.Editor {
 
 
 					currentTime = time;
 					currentTime = time;
 
 
-					timeline.Apply(skeleton, lastTime, currentTime, null, 1);
+					timeline.Apply(skeleton, lastTime, currentTime, null, 1, false, false);
 
 
 					lastTime = time;
 					lastTime = time;
 					listIndex++;
 					listIndex++;
@@ -1307,7 +1292,7 @@ namespace Spine.Unity.Editor {
 						if (i == steps)
 						if (i == steps)
 							currentTime = time;
 							currentTime = time;
 
 
-						timeline.Apply(skeleton, lastTime, currentTime, null, 1);
+						timeline.Apply(skeleton, lastTime, currentTime, null, 1, false, false);
 
 
 						px = xKeys[listIndex - 1];
 						px = xKeys[listIndex - 1];
 						py = yKeys[listIndex - 1];
 						py = yKeys[listIndex - 1];
@@ -1391,7 +1376,7 @@ namespace Spine.Unity.Editor {
 
 
 					currentTime = time;
 					currentTime = time;
 
 
-					timeline.Apply(skeleton, lastTime, currentTime, null, 1);
+					timeline.Apply(skeleton, lastTime, currentTime, null, 1, false, false);
 
 
 					lastTime = time;
 					lastTime = time;
 					listIndex++;
 					listIndex++;
@@ -1416,7 +1401,7 @@ namespace Spine.Unity.Editor {
 
 
 					currentTime = time;
 					currentTime = time;
 
 
-					timeline.Apply(skeleton, lastTime, currentTime, null, 1);
+					timeline.Apply(skeleton, lastTime, currentTime, null, 1, false, false);
 
 
 					lastTime = time;
 					lastTime = time;
 					listIndex++;
 					listIndex++;
@@ -1426,7 +1411,7 @@ namespace Spine.Unity.Editor {
 
 
 					float time = frames[f];
 					float time = frames[f];
 
 
-					timeline.Apply(skeleton, lastTime, currentTime, null, 1);
+					timeline.Apply(skeleton, lastTime, currentTime, null, 1, false, false);
 					skeleton.UpdateWorldTransform();
 					skeleton.UpdateWorldTransform();
 
 
 					rotation = frames[f + 1] + boneData.Rotation;
 					rotation = frames[f + 1] + boneData.Rotation;
@@ -1440,7 +1425,7 @@ namespace Spine.Unity.Editor {
 						if (i == steps)
 						if (i == steps)
 							currentTime = time;
 							currentTime = time;
 
 
-						timeline.Apply(skeleton, lastTime, currentTime, null, 1);
+						timeline.Apply(skeleton, lastTime, currentTime, null, 1, false, false);
 						skeleton.UpdateWorldTransform();
 						skeleton.UpdateWorldTransform();
 						pk = keys[listIndex - 1];
 						pk = keys[listIndex - 1];
 
 
@@ -1506,3 +1491,4 @@ namespace Spine.Unity.Editor {
 	}
 	}
 
 
 }
 }
+	

+ 116 - 80
spine-unity/Assets/spine-unity/Editor/SkeletonRendererInspector.cs

@@ -35,15 +35,21 @@ using System.Collections.Generic;
 using UnityEngine;
 using UnityEngine;
 
 
 namespace Spine.Unity.Editor {
 namespace Spine.Unity.Editor {
-	
+	using Event = UnityEngine.Event;
+
 	[CustomEditor(typeof(SkeletonRenderer))]
 	[CustomEditor(typeof(SkeletonRenderer))]
 	[CanEditMultipleObjects]
 	[CanEditMultipleObjects]
 	public class SkeletonRendererInspector : UnityEditor.Editor {
 	public class SkeletonRendererInspector : UnityEditor.Editor {
 		protected static bool advancedFoldout;
 		protected static bool advancedFoldout;
-		protected SerializedProperty skeletonDataAsset, initialSkinName, normals, tangents, meshes, immutableTriangles, separatorSlotNames, frontFacing, zSpacing, pmaVertexColors;
+		protected SerializedProperty skeletonDataAsset, initialSkinName, normals, tangents, meshes, immutableTriangles, separatorSlotNames, frontFacing, zSpacing, pmaVertexColors, clearStateOnDisable;
 		protected SpineInspectorUtility.SerializedSortingProperties sortingProperties;
 		protected SpineInspectorUtility.SerializedSortingProperties sortingProperties;
 		protected bool isInspectingPrefab;
 		protected bool isInspectingPrefab;
 
 
+		protected GUIContent SkeletonDataAssetLabel, SkeletonUtilityButtonContent;
+		protected GUIContent PMAVertexColorsLabel, ClearStateOnDisableLabel, ZSpacingLabel, MeshesLabel, ImmubleTrianglesLabel;
+		protected GUIContent NormalsLabel, TangentsLabel;
+		const string ReloadButtonLabel = "Reload";
+
 		protected bool TargetIsValid {
 		protected bool TargetIsValid {
 			get {
 			get {
 				if (serializedObject.isEditingMultipleObjects) {
 				if (serializedObject.isEditingMultipleObjects) {
@@ -64,18 +70,33 @@ namespace Spine.Unity.Editor {
 			isInspectingPrefab = (PrefabUtility.GetPrefabType(target) == PrefabType.Prefab);
 			isInspectingPrefab = (PrefabUtility.GetPrefabType(target) == PrefabType.Prefab);
 			
 			
 			SpineEditorUtilities.ConfirmInitialization();
 			SpineEditorUtilities.ConfirmInitialization();
-			skeletonDataAsset = serializedObject.FindProperty("skeletonDataAsset");
-			initialSkinName = serializedObject.FindProperty("initialSkinName");
-			normals = serializedObject.FindProperty("calculateNormals");
-			tangents = serializedObject.FindProperty("calculateTangents");
-			meshes = serializedObject.FindProperty("renderMeshes");
-			immutableTriangles = serializedObject.FindProperty("immutableTriangles");
-			pmaVertexColors = serializedObject.FindProperty("pmaVertexColors");
-			separatorSlotNames = serializedObject.FindProperty("separatorSlotNames");
+
+			// Labels
+			SkeletonDataAssetLabel = new GUIContent("SkeletonData Asset", SpineEditorUtilities.Icons.spine);
+			SkeletonUtilityButtonContent = new GUIContent("Add Skeleton Utility", SpineEditorUtilities.Icons.skeletonUtility);
+			MeshesLabel = new GUIContent("Render MeshAttachments", "Disable to optimize rendering for skeletons that don't use Mesh Attachments");
+			ImmubleTrianglesLabel = new GUIContent("Immutable Triangles", "Enable to optimize rendering for skeletons that never change attachment visbility");
+			PMAVertexColorsLabel = new GUIContent("PMA Vertex Colors", "Use this if you are using the default Spine/Skeleton shader or any premultiply-alpha shader.");
+			ClearStateOnDisableLabel = new GUIContent("Clear State On Disable", "Use this if you are pooling or enabling/disabling your Spine GameObject.");
+			ZSpacingLabel = new GUIContent("Z Spacing", "A value other than 0 adds a space between each rendered attachment to prevent Z Fighting when using shaders that read or write to the depth buffer. Large values may cause unwanted parallax and spaces depending on camera setup.");
+			NormalsLabel = new GUIContent("Add Normals", "Use this if your shader requires vertex normals. A more efficient solution for 2D setups is to modify the shader to assume a single normal value for the whole mesh.");
+			TangentsLabel = new GUIContent("Solve Tangents", "Calculates the tangents per frame. Use this if you are using lit shaders (usually with normal maps) that require vertex tangents.");
+
+			var so = this.serializedObject;
+			skeletonDataAsset = so.FindProperty("skeletonDataAsset");
+			initialSkinName = so.FindProperty("initialSkinName");
+			normals = so.FindProperty("calculateNormals");
+			tangents = so.FindProperty("calculateTangents");
+			meshes = so.FindProperty("renderMeshes");
+			immutableTriangles = so.FindProperty("immutableTriangles");
+			pmaVertexColors = so.FindProperty("pmaVertexColors");
+			clearStateOnDisable = so.FindProperty("clearStateOnDisable");
+
+			separatorSlotNames = so.FindProperty("separatorSlotNames");
 			separatorSlotNames.isExpanded = true;
 			separatorSlotNames.isExpanded = true;
 
 
-			frontFacing = serializedObject.FindProperty("frontFacing");
-			zSpacing = serializedObject.FindProperty("zSpacing");
+			frontFacing = so.FindProperty("frontFacing");
+			zSpacing = so.FindProperty("zSpacing");
 
 
 			SerializedObject rso = SpineInspectorUtility.GetRenderersSerializedObject(serializedObject);
 			SerializedObject rso = SpineInspectorUtility.GetRenderersSerializedObject(serializedObject);
 			sortingProperties = new SpineInspectorUtility.SerializedSortingProperties(rso);
 			sortingProperties = new SpineInspectorUtility.SerializedSortingProperties(rso);
@@ -101,12 +122,13 @@ namespace Spine.Unity.Editor {
 
 
 		protected virtual void DrawInspectorGUI (bool multi) {
 		protected virtual void DrawInspectorGUI (bool multi) {
 			bool valid = TargetIsValid;
 			bool valid = TargetIsValid;
+			var reloadWidth = GUILayout.Width(GUI.skin.label.CalcSize(new GUIContent(ReloadButtonLabel)).x + 20);
+			var reloadButtonStyle = EditorStyles.miniButtonRight;
+
 			if (multi) {
 			if (multi) {
-				using (new EditorGUILayout.HorizontalScope()) {
-					EditorGUILayout.PropertyField(skeletonDataAsset);
-					const string ReloadButtonLabel = "Reload";
-					float reloadWidth = GUI.skin.label.CalcSize(new GUIContent(ReloadButtonLabel)).x + 20;
-					if (GUILayout.Button(ReloadButtonLabel, GUILayout.Width(reloadWidth))) {
+				using (new EditorGUILayout.HorizontalScope(EditorStyles.helpBox)) {
+					SpineInspectorUtility.PropertyFieldFitLabel(skeletonDataAsset, SkeletonDataAssetLabel);
+					if (GUILayout.Button(ReloadButtonLabel, reloadButtonStyle, reloadWidth)) {
 						foreach (var c in targets) {
 						foreach (var c in targets) {
 							var component = c as SkeletonRenderer;
 							var component = c as SkeletonRenderer;
 							if (component.skeletonDataAsset != null) {
 							if (component.skeletonDataAsset != null) {
@@ -124,8 +146,10 @@ namespace Spine.Unity.Editor {
 				foreach (var c in targets) {
 				foreach (var c in targets) {
 					var component = c as SkeletonRenderer;
 					var component = c as SkeletonRenderer;
 					if (!component.valid) {
 					if (!component.valid) {
-						component.Initialize(true);
-						component.LateUpdate();
+						if (Event.current.type == EventType.Layout) {
+							component.Initialize(true);
+							component.LateUpdate();
+						}
 						if (!component.valid)
 						if (!component.valid)
 							continue;
 							continue;
 					}
 					}
@@ -144,12 +168,15 @@ namespace Spine.Unity.Editor {
 			} else {
 			} else {
 				var component = (SkeletonRenderer)target;
 				var component = (SkeletonRenderer)target;
 
 
-				using (new EditorGUILayout.HorizontalScope()) {
-					EditorGUILayout.PropertyField(skeletonDataAsset);
-					if (valid) {
-						const string ReloadButtonLabel = "Reload";
-						float reloadWidth = GUI.skin.label.CalcSize(new GUIContent(ReloadButtonLabel)).x + 20;
-						if (GUILayout.Button(ReloadButtonLabel, GUILayout.Width(reloadWidth))) {
+				if (!component.valid && Event.current.type == EventType.Layout) {
+					component.Initialize(true);
+					component.LateUpdate();
+				}
+
+				using (new EditorGUILayout.HorizontalScope(EditorStyles.helpBox)) {
+					SpineInspectorUtility.PropertyFieldFitLabel(skeletonDataAsset, SkeletonDataAssetLabel);
+					if (component.valid) {
+						if (GUILayout.Button(ReloadButtonLabel, reloadButtonStyle, reloadWidth)) {
 							if (component.skeletonDataAsset != null) {
 							if (component.skeletonDataAsset != null) {
 								foreach (AtlasAsset aa in component.skeletonDataAsset.atlasAssets) {
 								foreach (AtlasAsset aa in component.skeletonDataAsset.atlasAssets) {
 									if (aa != null)
 									if (aa != null)
@@ -162,13 +189,9 @@ namespace Spine.Unity.Editor {
 					}
 					}
 				}
 				}
 
 
-				if (!component.valid) {
-					component.Initialize(true);
-					component.LateUpdate();
-					if (!component.valid) {
-						EditorGUILayout.HelpBox("Skeleton Data Asset required", MessageType.Warning);
-						return;
-					}
+				if (component.skeletonDataAsset == null) {
+					EditorGUILayout.HelpBox("Skeleton Data Asset required", MessageType.Warning);
+					return;
 				}
 				}
 
 
 				#if NO_PREFAB_MESH
 				#if NO_PREFAB_MESH
@@ -180,7 +203,7 @@ namespace Spine.Unity.Editor {
 				#endif
 				#endif
 
 
 				// Initial skin name.
 				// Initial skin name.
-				if (valid) {
+				if (component.valid) {
 					string[] skins = new string[component.skeleton.Data.Skins.Count];
 					string[] skins = new string[component.skeleton.Data.Skins.Count];
 					int skinIndex = 0;
 					int skinIndex = 0;
 					for (int i = 0; i < skins.Length; i++) {
 					for (int i = 0; i < skins.Length; i++) {
@@ -199,54 +222,75 @@ namespace Spine.Unity.Editor {
 			// Sorting Layers
 			// Sorting Layers
 			SpineInspectorUtility.SortingPropertyFields(sortingProperties, applyModifiedProperties: true);
 			SpineInspectorUtility.SortingPropertyFields(sortingProperties, applyModifiedProperties: true);
 
 
-			if (!valid) return;
+			if (!TargetIsValid) return;
 			
 			
 			// More Render Options...
 			// More Render Options...
-			using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) {
-				EditorGUI.indentLevel++;
-				advancedFoldout = EditorGUILayout.Foldout(advancedFoldout, "Advanced");
-				if (advancedFoldout) {
-					EditorGUI.indentLevel++;
-					SeparatorsField(separatorSlotNames);
-					EditorGUILayout.Space();
+			using (new SpineInspectorUtility.BoxScope()) {
+				if (advancedFoldout = EditorGUILayout.Foldout(advancedFoldout, "Advanced")) {
+					using (new SpineInspectorUtility.IndentScope()) {
+						SeparatorsField(separatorSlotNames);
+						EditorGUILayout.Space();
 
 
-					// Optimization options
-					SpineInspectorUtility.PropertyFieldWideLabel(meshes,
-						new GUIContent("Render MeshAttachments", "Disable to optimize rendering for skeletons that don't use Mesh Attachments"));
-					SpineInspectorUtility.PropertyFieldWideLabel(immutableTriangles,
-						new GUIContent("Immutable Triangles", "Enable to optimize rendering for skeletons that never change attachment visbility"));
-					EditorGUILayout.Space();
+						using (new SpineInspectorUtility.LabelWidthScope()) {
+							// Optimization options
+							EditorGUILayout.PropertyField(meshes, MeshesLabel);
+							EditorGUILayout.PropertyField(immutableTriangles, ImmubleTrianglesLabel);
+							EditorGUILayout.Space();
+						}
 
 
-					// Render options
-					const float MinZSpacing = -0.1f;
-					const float MaxZSpacing = 0f;
-					EditorGUILayout.Slider(zSpacing, MinZSpacing, MaxZSpacing);
-					EditorGUILayout.Space();
-					SpineInspectorUtility.PropertyFieldWideLabel(pmaVertexColors,
-						new GUIContent("PMA Vertex Colors", "Use this if you are using the default Spine/Skeleton shader or any premultiply-alpha shader."));
+						// Render options
+						const float MinZSpacing = -0.1f;
+						const float MaxZSpacing = 0f;
+						EditorGUILayout.Slider(zSpacing, MinZSpacing, MaxZSpacing, ZSpacingLabel);
+						EditorGUILayout.Space();
 
 
-					// Optional fields. May be disabled in SkeletonRenderer.
-					if (normals != null) SpineInspectorUtility.PropertyFieldWideLabel(normals, new GUIContent("Add Normals"));
-					if (tangents != null) SpineInspectorUtility.PropertyFieldWideLabel(tangents, new GUIContent("Solve Tangents"));
-					if (frontFacing != null) SpineInspectorUtility.PropertyFieldWideLabel(frontFacing);
+						using (new SpineInspectorUtility.LabelWidthScope()) {
+							EditorGUILayout.PropertyField(pmaVertexColors, PMAVertexColorsLabel);
+							EditorGUILayout.PropertyField(clearStateOnDisable, ClearStateOnDisableLabel);
+
+							// Optional fields. May be disabled in SkeletonRenderer.
+							if (normals != null) EditorGUILayout.PropertyField(normals, NormalsLabel);
+							if (tangents != null) EditorGUILayout.PropertyField(tangents, TangentsLabel);
+							if (frontFacing != null) EditorGUILayout.PropertyField(frontFacing);
+						}
 
 
-					EditorGUI.indentLevel--;
+						EditorGUILayout.Space();
+					}
 				}
 				}
-				EditorGUI.indentLevel--;
 			}
 			}
 		}
 		}
 
 
 		public static void SeparatorsField (SerializedProperty separatorSlotNames) {
 		public static void SeparatorsField (SerializedProperty separatorSlotNames) {
+			bool multi = separatorSlotNames.serializedObject.isEditingMultipleObjects;
+			bool hasTerminalSlot = false;
+			if (!multi) {
+				var sr = separatorSlotNames.serializedObject.targetObject as ISkeletonComponent;
+				var skeleton = sr.Skeleton;
+				int lastSlot = skeleton.Slots.Count - 1;
+				if (skeleton != null) {					
+					for (int i = 0, n = separatorSlotNames.arraySize; i < n; i++) {
+						int index = skeleton.FindSlotIndex(separatorSlotNames.GetArrayElementAtIndex(i).stringValue);
+						if (index == 0 || index == lastSlot) {
+							hasTerminalSlot = true;
+							break;
+						}
+					}
+				}
+			}
+
+			string terminalSlotWarning = hasTerminalSlot ? " (!)" : "";
+
 			using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) {
 			using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) {
-				if (separatorSlotNames.isExpanded)
-					EditorGUILayout.PropertyField(separatorSlotNames, includeChildren: true);
-				else
-					EditorGUILayout.PropertyField(separatorSlotNames, new GUIContent(separatorSlotNames.displayName + string.Format(" [{0}]", separatorSlotNames.arraySize)), includeChildren: true);
+				const string SeparatorsDescription = "Stored names of slots where the Skeleton's render will be split into different batches. This is used by separate components that split the render into different MeshRenderers or GameObjects.";
+				if (separatorSlotNames.isExpanded) {
+					EditorGUILayout.PropertyField(separatorSlotNames, new GUIContent(separatorSlotNames.displayName + terminalSlotWarning, SeparatorsDescription), true);
+					EditorGUILayout.Space();
+				} else
+					EditorGUILayout.PropertyField(separatorSlotNames, new GUIContent(separatorSlotNames.displayName + string.Format("{0} [{1}]", terminalSlotWarning, separatorSlotNames.arraySize), SeparatorsDescription), true);
 			}
 			}
 		}
 		}
 
 
 		public void DrawSkeletonUtilityButton (bool multi) {
 		public void DrawSkeletonUtilityButton (bool multi) {
-			var buttonContent = new GUIContent("Add Skeleton Utility", SpineEditorUtilities.Icons.skeletonUtility);
 			if (multi) {
 			if (multi) {
 				// Support multi-edit SkeletonUtility button.
 				// Support multi-edit SkeletonUtility button.
 				//	EditorGUILayout.Space();
 				//	EditorGUILayout.Space();
@@ -258,9 +302,9 @@ namespace Spine.Unity.Editor {
 				//	}
 				//	}
 			} else {
 			} else {
 				EditorGUILayout.Space();
 				EditorGUILayout.Space();
-				var component = (SkeletonAnimation)target;
+				var component = (Component)target;
 				if (component.GetComponent<SkeletonUtility>() == null) {						
 				if (component.GetComponent<SkeletonUtility>() == null) {						
-					if (GUILayout.Button(buttonContent, GUILayout.Height(30)))
+					if (SpineInspectorUtility.LargeCenteredButton(SkeletonUtilityButtonContent))
 						component.gameObject.AddComponent<SkeletonUtility>();
 						component.gameObject.AddComponent<SkeletonUtility>();
 				}
 				}
 			}
 			}
@@ -270,22 +314,14 @@ namespace Spine.Unity.Editor {
 			//serializedObject.Update();
 			//serializedObject.Update();
 			bool multi = serializedObject.isEditingMultipleObjects;
 			bool multi = serializedObject.isEditingMultipleObjects;
 			DrawInspectorGUI(multi);
 			DrawInspectorGUI(multi);
-			if (serializedObject.ApplyModifiedProperties() ||
-				(UnityEngine.Event.current.type == EventType.ValidateCommand && UnityEngine.Event.current.commandName == "UndoRedoPerformed")
-			) {
+			if (serializedObject.ApplyModifiedProperties() || SpineInspectorUtility.UndoRedoPerformed(Event.current)) {
 				if (!Application.isPlaying) {
 				if (!Application.isPlaying) {
-					if (multi) {
-						foreach (var o in targets) {
-							var sr = o as SkeletonRenderer;
-							sr.Initialize(true);
-						}
-					} else {
+					if (multi)
+						foreach (var o in targets)
+							((SkeletonRenderer)o).Initialize(true);
+					else
 						((SkeletonRenderer)target).Initialize(true);
 						((SkeletonRenderer)target).Initialize(true);
-					}
-
 				}
 				}
-					
-					
 			}
 			}
 		}
 		}
 
 

+ 4 - 2
spine-unity/Assets/spine-unity/Editor/SpineAttributeDrawers.cs

@@ -151,8 +151,10 @@ namespace Spine.Unity.Editor {
 
 
 						bool hasBoundingBox = false;
 						bool hasBoundingBox = false;
 						foreach (var attachment in attachments) {
 						foreach (var attachment in attachments) {
-							if (attachment is BoundingBoxAttachment) {
-								menu.AddItem(new GUIContent(name), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
+							var bbAttachment = attachment as BoundingBoxAttachment;
+							if (bbAttachment != null) {
+								string menuLabel = bbAttachment.IsWeighted() ? name + " (!)" : name;
+								menu.AddItem(new GUIContent(menuLabel), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
 								hasBoundingBox = true;
 								hasBoundingBox = true;
 								break;
 								break;
 							}
 							}

+ 79 - 100
spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs

@@ -43,6 +43,7 @@ using System.Reflection;
 using Spine;
 using Spine;
 
 
 namespace Spine.Unity.Editor {
 namespace Spine.Unity.Editor {
+	using EventType = UnityEngine.EventType;
 	
 	
 	// Analysis disable once ConvertToStaticType
 	// Analysis disable once ConvertToStaticType
 	[InitializeOnLoad]
 	[InitializeOnLoad]
@@ -66,7 +67,7 @@ namespace Spine.Unity.Editor {
 			public static Texture2D animation;
 			public static Texture2D animation;
 			public static Texture2D animationRoot;
 			public static Texture2D animationRoot;
 			public static Texture2D spine;
 			public static Texture2D spine;
-			public static Texture2D _event;
+			public static Texture2D userEvent;
 			public static Texture2D constraintNib;
 			public static Texture2D constraintNib;
 			public static Texture2D warning;
 			public static Texture2D warning;
 			public static Texture2D skeletonUtility;
 			public static Texture2D skeletonUtility;
@@ -99,13 +100,8 @@ namespace Spine.Unity.Editor {
 			public static Material BoneMaterial {
 			public static Material BoneMaterial {
 				get {
 				get {
 					if (_boneMaterial == null) {
 					if (_boneMaterial == null) {
-						#if UNITY_4_3
-						_boneMaterial = new Material(Shader.Find("Particles/Alpha Blended"));
-						_boneMaterial.SetColor("_TintColor", new Color(0.4f, 0.4f, 0.4f, 0.25f));
-						#else
 						_boneMaterial = new Material(Shader.Find("Hidden/Spine/Bones"));
 						_boneMaterial = new Material(Shader.Find("Hidden/Spine/Bones"));
 						_boneMaterial.SetColor("_Color", new Color(0.4f, 0.4f, 0.4f, 0.25f));
 						_boneMaterial.SetColor("_Color", new Color(0.4f, 0.4f, 0.4f, 0.25f));
-						#endif
 					}
 					}
 					return _boneMaterial;
 					return _boneMaterial;
 				}
 				}
@@ -129,7 +125,7 @@ namespace Spine.Unity.Editor {
 				animation = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-animation.png");
 				animation = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-animation.png");
 				animationRoot = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-animationRoot.png");
 				animationRoot = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-animationRoot.png");
 				spine = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-spine.png");
 				spine = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-spine.png");
-				_event = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-event.png");
+				userEvent = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-event.png");
 				constraintNib = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-constraintNib.png");
 				constraintNib = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-constraintNib.png");
 				warning = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-warning.png");
 				warning = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-warning.png");
 				skeletonUtility = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-skeletonUtility.png");
 				skeletonUtility = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-skeletonUtility.png");
@@ -152,7 +148,6 @@ namespace Spine.Unity.Editor {
 		/// Unity can mistakenly unload assets whose references are only on the stack.
 		/// Unity can mistakenly unload assets whose references are only on the stack.
 		/// This leads to MissingReferenceException and other errors.
 		/// This leads to MissingReferenceException and other errors.
 		static readonly List<ScriptableObject> protectFromStackGarbageCollection = new List<ScriptableObject>();
 		static readonly List<ScriptableObject> protectFromStackGarbageCollection = new List<ScriptableObject>();
-
 		static HashSet<string> assetsImportedInWrongState;
 		static HashSet<string> assetsImportedInWrongState;
 		static Dictionary<int, GameObject> skeletonRendererTable;
 		static Dictionary<int, GameObject> skeletonRendererTable;
 		static Dictionary<int, SkeletonUtilityBone> skeletonUtilityBoneTable;
 		static Dictionary<int, SkeletonUtilityBone> skeletonUtilityBoneTable;
@@ -279,7 +274,7 @@ namespace Spine.Unity.Editor {
 		}
 		}
 		#endregion
 		#endregion
 
 
-		#region Drag and Drop to Scene View
+		#region Drag and Drop Instantiation
 
 
 		public delegate Component InstantiateDelegate (SkeletonDataAsset skeletonDataAsset);
 		public delegate Component InstantiateDelegate (SkeletonDataAsset skeletonDataAsset);
 
 
@@ -541,7 +536,6 @@ namespace Spine.Unity.Editor {
 			// Import atlases first.
 			// Import atlases first.
 			var atlases = new List<AtlasAsset>();
 			var atlases = new List<AtlasAsset>();
 			foreach (string ap in atlasPaths) {
 			foreach (string ap in atlasPaths) {
-				// MITCH: left note: Always import atlas data now.
 				TextAsset atlasText = (TextAsset)AssetDatabase.LoadAssetAtPath(ap, typeof(TextAsset));
 				TextAsset atlasText = (TextAsset)AssetDatabase.LoadAssetAtPath(ap, typeof(TextAsset));
 				AtlasAsset atlas = IngestSpineAtlas(atlasText);
 				AtlasAsset atlas = IngestSpineAtlas(atlasText);
 				atlases.Add(atlas);
 				atlases.Add(atlas);
@@ -613,7 +607,7 @@ namespace Spine.Unity.Editor {
 					break;
 					break;
 				#endif
 				#endif
 			}
 			}
-			// MITCH: left a todo: any post processing of images
+			// Any post processing of images
 		}
 		}
 
 
 		static void ResetExistingSkeletonData (string skeletonJSONPath) {
 		static void ResetExistingSkeletonData (string skeletonJSONPath) {
@@ -1089,26 +1083,26 @@ namespace Spine.Unity.Editor {
 
 
 			#else
 			#else
 			if (spineJson != null && atlasAssets != null) {
 			if (spineJson != null && atlasAssets != null) {
-				SkeletonDataAsset skelDataAsset = (SkeletonDataAsset)AssetDatabase.LoadAssetAtPath(filePath, typeof(SkeletonDataAsset));
-				if (skelDataAsset == null) {
-					skelDataAsset = SkeletonDataAsset.CreateInstance<SkeletonDataAsset>();
-					skelDataAsset.atlasAssets = atlasAssets;
-					skelDataAsset.skeletonJSON = spineJson;
-					skelDataAsset.fromAnimation = new string[0];
-					skelDataAsset.toAnimation = new string[0];
-					skelDataAsset.duration = new float[0];
-					skelDataAsset.defaultMix = defaultMix;
-					skelDataAsset.scale = defaultScale;
-
-					AssetDatabase.CreateAsset(skelDataAsset, filePath);
+				SkeletonDataAsset skeletonDataAsset = (SkeletonDataAsset)AssetDatabase.LoadAssetAtPath(filePath, typeof(SkeletonDataAsset));
+				if (skeletonDataAsset == null) {
+					skeletonDataAsset = SkeletonDataAsset.CreateInstance<SkeletonDataAsset>();
+					skeletonDataAsset.atlasAssets = atlasAssets;
+					skeletonDataAsset.skeletonJSON = spineJson;
+					skeletonDataAsset.fromAnimation = new string[0];
+					skeletonDataAsset.toAnimation = new string[0];
+					skeletonDataAsset.duration = new float[0];
+					skeletonDataAsset.defaultMix = defaultMix;
+					skeletonDataAsset.scale = defaultScale;
+
+					AssetDatabase.CreateAsset(skeletonDataAsset, filePath);
 					AssetDatabase.SaveAssets();
 					AssetDatabase.SaveAssets();
 				} else {
 				} else {
-					skelDataAsset.atlasAssets = atlasAssets;
-					skelDataAsset.Reset();
-					skelDataAsset.GetSkeletonData(true);
+					skeletonDataAsset.atlasAssets = atlasAssets;
+					skeletonDataAsset.Reset();
+					skeletonDataAsset.GetSkeletonData(true);
 				}
 				}
 
 
-				return skelDataAsset;
+				return skeletonDataAsset;
 			} else {
 			} else {
 				EditorUtility.DisplayDialog("Error!", "Must specify both Spine JSON and AtlasAsset array", "OK");
 				EditorUtility.DisplayDialog("Error!", "Must specify both Spine JSON and AtlasAsset array", "OK");
 				return null;
 				return null;
@@ -1118,8 +1112,8 @@ namespace Spine.Unity.Editor {
 		#endregion
 		#endregion
 
 
 		#region Checking Methods
 		#region Checking Methods
-		static int[][] compatibleVersions = { new[] {3, 4, 0}, new[] {3, 3, 0} };
-		static bool isFixVersionRequired = false;
+		static int[][] compatibleVersions = { new[] {3, 5, 0} };
+		//static bool isFixVersionRequired = false;
 
 
 		static bool CheckForValidSkeletonData (string skeletonJSONPath) {
 		static bool CheckForValidSkeletonData (string skeletonJSONPath) {
 			string dir = Path.GetDirectoryName(skeletonJSONPath);
 			string dir = Path.GetDirectoryName(skeletonJSONPath);
@@ -1170,8 +1164,7 @@ namespace Spine.Unity.Editor {
 						bool primaryMatch = version[0] == int.Parse(jsonVersionSplit[0]);
 						bool primaryMatch = version[0] == int.Parse(jsonVersionSplit[0]);
 						bool secondaryMatch = version[1] == int.Parse(jsonVersionSplit[1]);
 						bool secondaryMatch = version[1] == int.Parse(jsonVersionSplit[1]);
 
 
-						if (isFixVersionRequired)
-							secondaryMatch &= version[2] <= int.Parse(jsonVersionSplit[2]);
+						// if (isFixVersionRequired) secondaryMatch &= version[2] <= int.Parse(jsonVersionSplit[2]);
 
 
 						if (primaryMatch && secondaryMatch) {
 						if (primaryMatch && secondaryMatch) {
 							match = true;
 							match = true;
@@ -1193,32 +1186,32 @@ namespace Spine.Unity.Editor {
 		#endregion
 		#endregion
 
 
 		#region SkeletonAnimation Menu
 		#region SkeletonAnimation Menu
-		[MenuItem("Assets/Spine/Instantiate (SkeletonAnimation)", false, 10)]
-		static void InstantiateSkeletonAnimation () {
-			Object[] arr = Selection.objects;
-			foreach (Object o in arr) {
-				string guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(o));
-				string skinName = EditorPrefs.GetString(guid + "_lastSkin", "");
-
-				InstantiateSkeletonAnimation((SkeletonDataAsset)o, skinName, false);
-				SceneView.RepaintAll();
-			}
-		}
-
-		[MenuItem("Assets/Spine/Instantiate (SkeletonAnimation)", true, 10)]
-		static bool ValidateInstantiateSkeletonAnimation () {
-			Object[] arr = Selection.objects;
-
-			if (arr.Length == 0)
-				return false;
-
-			foreach (Object o in arr) {
-				if (o.GetType() != typeof(SkeletonDataAsset))
-					return false;
-			}
-
-			return true;
-		}
+//		[MenuItem("Assets/Spine/Instantiate (SkeletonAnimation)", false, 10)]
+//		static void InstantiateSkeletonAnimation () {
+//			Object[] arr = Selection.objects;
+//			foreach (Object o in arr) {
+//				string guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(o));
+//				string skinName = EditorPrefs.GetString(guid + "_lastSkin", "");
+//
+//				InstantiateSkeletonAnimation((SkeletonDataAsset)o, skinName, false);
+//				SceneView.RepaintAll();
+//			}
+//		}
+//
+//		[MenuItem("Assets/Spine/Instantiate (SkeletonAnimation)", true, 10)]
+//		static bool ValidateInstantiateSkeletonAnimation () {
+//			Object[] arr = Selection.objects;
+//
+//			if (arr.Length == 0)
+//				return false;
+//
+//			foreach (Object o in arr) {
+//				if (o.GetType() != typeof(SkeletonDataAsset))
+//					return false;
+//			}
+//
+//			return true;
+//		}
 
 
 		public static SkeletonAnimation InstantiateSkeletonAnimation (SkeletonDataAsset skeletonDataAsset, string skinName, bool destroyInvalid = true) {
 		public static SkeletonAnimation InstantiateSkeletonAnimation (SkeletonDataAsset skeletonDataAsset, string skinName, bool destroyInvalid = true) {
 			var skeletonData = skeletonDataAsset.GetSkeletonData(true);
 			var skeletonData = skeletonDataAsset.GetSkeletonData(true);
@@ -1242,9 +1235,6 @@ namespace Spine.Unity.Editor {
 				return null;
 				return null;
 			}
 			}
 
 
-			if (skin == null) skin = data.DefaultSkin;
-			if (skin == null) skin = data.Skins.Items[0];
-
 			string spineGameObjectName = string.Format("Spine GameObject ({0})", skeletonDataAsset.name.Replace("_SkeletonData", ""));
 			string spineGameObjectName = string.Format("Spine GameObject ({0})", skeletonDataAsset.name.Replace("_SkeletonData", ""));
 			GameObject go = new GameObject(spineGameObjectName, typeof(MeshFilter), typeof(MeshRenderer), typeof(SkeletonAnimation));
 			GameObject go = new GameObject(spineGameObjectName, typeof(MeshFilter), typeof(MeshRenderer), typeof(SkeletonAnimation));
 			SkeletonAnimation newSkeletonAnimation = go.GetComponent<SkeletonAnimation>();
 			SkeletonAnimation newSkeletonAnimation = go.GetComponent<SkeletonAnimation>();
@@ -1273,11 +1263,12 @@ namespace Spine.Unity.Editor {
 				throw e;
 				throw e;
 			}
 			}
 
 
+			skin = skin ?? data.DefaultSkin ?? data.Skins.Items[0];
 			newSkeletonAnimation.skeleton.SetSkin(skin);
 			newSkeletonAnimation.skeleton.SetSkin(skin);
 			newSkeletonAnimation.initialSkinName = skin.Name;
 			newSkeletonAnimation.initialSkinName = skin.Name;
 
 
-			newSkeletonAnimation.skeleton.Update(1);
-			newSkeletonAnimation.state.Update(1);
+			newSkeletonAnimation.skeleton.Update(0);
+			newSkeletonAnimation.state.Update(0);
 			newSkeletonAnimation.state.Apply(newSkeletonAnimation.skeleton);
 			newSkeletonAnimation.state.Apply(newSkeletonAnimation.skeleton);
 			newSkeletonAnimation.skeleton.UpdateWorldTransform();
 			newSkeletonAnimation.skeleton.UpdateWorldTransform();
 
 
@@ -1294,33 +1285,6 @@ namespace Spine.Unity.Editor {
 			SkeletonBaker.GenerateMecanimAnimationClips(skeletonDataAsset);
 			SkeletonBaker.GenerateMecanimAnimationClips(skeletonDataAsset);
 		}
 		}
 
 
-		[MenuItem("Assets/Spine/Instantiate (Mecanim)", false, 100)]
-		static void InstantiateSkeletonAnimator () {
-			Object[] arr = Selection.objects;
-			foreach (Object o in arr) {
-				string guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(o));
-				string skinName = EditorPrefs.GetString(guid + "_lastSkin", "");
-
-				InstantiateSkeletonAnimator((SkeletonDataAsset)o, skinName);
-				SceneView.RepaintAll();
-			}
-		}
-
-		[MenuItem("Assets/Spine/Instantiate (Mecanim)", true, 100)]
-		static bool ValidateInstantiateSkeletonAnimator () {
-			Object[] arr = Selection.objects;
-
-			if (arr.Length == 0)
-				return false;
-
-			foreach (Object o in arr) {
-				if (o.GetType() != typeof(SkeletonDataAsset))
-					return false;
-			}
-
-			return true;
-		}
-
 		public static SkeletonAnimator InstantiateSkeletonAnimator (SkeletonDataAsset skeletonDataAsset, string skinName) {
 		public static SkeletonAnimator InstantiateSkeletonAnimator (SkeletonDataAsset skeletonDataAsset, string skinName) {
 			return InstantiateSkeletonAnimator(skeletonDataAsset, skeletonDataAsset.GetSkeletonData(true).FindSkin(skinName));
 			return InstantiateSkeletonAnimator(skeletonDataAsset, skeletonDataAsset.GetSkeletonData(true).FindSkin(skinName));
 		}
 		}
@@ -1339,8 +1303,8 @@ namespace Spine.Unity.Editor {
 			SkeletonAnimator anim = go.GetComponent<SkeletonAnimator>();
 			SkeletonAnimator anim = go.GetComponent<SkeletonAnimator>();
 			anim.skeletonDataAsset = skeletonDataAsset;
 			anim.skeletonDataAsset = skeletonDataAsset;
 
 
+			// Detect "Lit" shader and automatically enable calculateNormals.
 			bool requiresNormals = false;
 			bool requiresNormals = false;
-
 			foreach (AtlasAsset atlasAsset in anim.skeletonDataAsset.atlasAssets) {
 			foreach (AtlasAsset atlasAsset in anim.skeletonDataAsset.atlasAssets) {
 				foreach (Material m in atlasAsset.materials) {
 				foreach (Material m in atlasAsset.materials) {
 					if (m.shader.name.Contains("Lit")) {
 					if (m.shader.name.Contains("Lit")) {
@@ -1349,32 +1313,24 @@ namespace Spine.Unity.Editor {
 					}
 					}
 				}
 				}
 			}
 			}
-
 			anim.calculateNormals = requiresNormals;
 			anim.calculateNormals = requiresNormals;
 
 
 			SkeletonData data = skeletonDataAsset.GetSkeletonData(true);
 			SkeletonData data = skeletonDataAsset.GetSkeletonData(true);
-
 			if (data == null) {
 			if (data == null) {
 				for (int i = 0; i < skeletonDataAsset.atlasAssets.Length; i++) {
 				for (int i = 0; i < skeletonDataAsset.atlasAssets.Length; i++) {
 					string reloadAtlasPath = AssetDatabase.GetAssetPath(skeletonDataAsset.atlasAssets[i]);
 					string reloadAtlasPath = AssetDatabase.GetAssetPath(skeletonDataAsset.atlasAssets[i]);
 					skeletonDataAsset.atlasAssets[i] = (AtlasAsset)AssetDatabase.LoadAssetAtPath(reloadAtlasPath, typeof(AtlasAsset));
 					skeletonDataAsset.atlasAssets[i] = (AtlasAsset)AssetDatabase.LoadAssetAtPath(reloadAtlasPath, typeof(AtlasAsset));
 				}
 				}
-
 				data = skeletonDataAsset.GetSkeletonData(true);
 				data = skeletonDataAsset.GetSkeletonData(true);
 			}
 			}
 
 
-			if (skin == null)
-				skin = data.DefaultSkin;
-
-			if (skin == null)
-				skin = data.Skins.Items[0];
+			skin = skin ?? data.DefaultSkin ?? data.Skins.Items[0];
 
 
 			anim.Initialize(false);
 			anim.Initialize(false);
-
 			anim.skeleton.SetSkin(skin);
 			anim.skeleton.SetSkin(skin);
 			anim.initialSkinName = skin.Name;
 			anim.initialSkinName = skin.Name;
 
 
-			anim.skeleton.Update(1);
+			anim.skeleton.Update(0);
 			anim.skeleton.UpdateWorldTransform();
 			anim.skeleton.UpdateWorldTransform();
 			anim.LateUpdate();
 			anim.LateUpdate();
 
 
@@ -1494,6 +1450,29 @@ namespace Spine.Unity.Editor {
 //			Handles.DrawLine(p0, p1);
 //			Handles.DrawLine(p0, p1);
 //			Handles.DrawLine(p3, p2);
 //			Handles.DrawLine(p3, p2);
 		}
 		}
+
+		static public void DrawBoundingBox (Slot slot, BoundingBoxAttachment box) {
+			if (box.Vertices.Length <= 0) return; // Handle cases where user creates a BoundingBoxAttachment but doesn't actually define it.
+
+			var worldVerts = new float[box.Vertices.Length];
+			box.ComputeWorldVertices(slot, worldVerts);
+
+			Handles.color = Color.green;
+			Vector3 lastVert = Vector3.back;
+			Vector3 vert = Vector3.back;
+			Vector3 firstVert = new Vector3(worldVerts[0], worldVerts[1], -1);
+			for (int i = 0; i < worldVerts.Length; i += 2) {
+				vert.x = worldVerts[i];
+				vert.y = worldVerts[i + 1];
+
+				if (i > 0)
+					Handles.DrawLine(lastVert, vert);
+
+				lastVert = vert;
+			}
+
+			Handles.DrawLine(lastVert, firstVert);
+		}
 		#endregion
 		#endregion
 
 
 		public static string GetPathSafeRegionName (AtlasRegion region) {
 		public static string GetPathSafeRegionName (AtlasRegion region) {

+ 173 - 58
spine-unity/Assets/spine-unity/Editor/SpineInspectorUtility.cs

@@ -49,52 +49,140 @@ namespace Spine.Unity.Editor {
 		}
 		}
 
 
 		public static void PropertyFieldWideLabel (SerializedProperty property, GUIContent label = null, float minimumLabelWidth = 150) {
 		public static void PropertyFieldWideLabel (SerializedProperty property, GUIContent label = null, float minimumLabelWidth = 150) {
-			using (new EditorGUILayout.HorizontalScope()) {
-				GUILayout.Label(label ?? new GUIContent(property.displayName, property.tooltip), GUILayout.MinWidth(minimumLabelWidth));
-				//GUILayout.FlexibleSpace();
-				EditorGUILayout.PropertyField(property, GUIContent.none, true, GUILayout.MinWidth(100));
-			}
+			EditorGUIUtility.labelWidth = minimumLabelWidth;
+			EditorGUILayout.PropertyField(property, label ?? new GUIContent(property.displayName, property.tooltip));
+			EditorGUIUtility.labelWidth = 0; // Resets to default
 		}
 		}
 
 
-		#region Sorting Layer Field Helpers
-		static readonly GUIContent SortingLayerLabel = new GUIContent("Sorting Layer");
-		static readonly GUIContent OrderInLayerLabel = new GUIContent("Order in Layer");
+		public static void PropertyFieldFitLabel (SerializedProperty property, GUIContent label = null, float extraSpace = 5f) {
+			label = label ?? new GUIContent(property.displayName, property.tooltip);
+			float width = GUI.skin.label.CalcSize(new GUIContent(label.text)).x + extraSpace;
+			if (label.image != null)
+				width += EditorGUIUtility.singleLineHeight;
+			PropertyFieldWideLabel(property, label, width);
 
 
-		static MethodInfo m_SortingLayerFieldMethod;
-		static MethodInfo SortingLayerFieldMethod {
+		}
+
+		public static bool UndoRedoPerformed (UnityEngine.Event current) {
+			return current.type == EventType.ValidateCommand && current.commandName == "UndoRedoPerformed";
+		}
+
+		#region Layout Scopes
+		static GUIStyle grayMiniLabel;
+		public static GUIStyle GrayMiniLabel {
 			get {
 			get {
-				if (m_SortingLayerFieldMethod == null)
-					m_SortingLayerFieldMethod = typeof(EditorGUILayout).GetMethod("SortingLayerField", BindingFlags.Static | BindingFlags.NonPublic, null, new [] { typeof(GUIContent), typeof(SerializedProperty), typeof(GUIStyle) }, null);
+				if (grayMiniLabel == null) {
+					grayMiniLabel = new GUIStyle(EditorStyles.centeredGreyMiniLabel);
+					grayMiniLabel.alignment = TextAnchor.UpperLeft;
+				}
+				return grayMiniLabel;
+			}
+		}
 
 
-				return m_SortingLayerFieldMethod;
+		public class LabelWidthScope : System.IDisposable {
+			public LabelWidthScope (float minimumLabelWidth = 190f) {
+				EditorGUIUtility.labelWidth = minimumLabelWidth;
+			}
+
+			public void Dispose () {
+				EditorGUIUtility.labelWidth = 0f;
 			}
 			}
 		}
 		}
 
 
-		public struct SerializedSortingProperties {
-			public SerializedObject renderer;
-			public SerializedProperty sortingLayerID;
-			public SerializedProperty sortingOrder;
+		public class IndentScope : System.IDisposable {
+			public IndentScope () { EditorGUI.indentLevel++; }
+			public void Dispose () { EditorGUI.indentLevel--; }
+		}
 
 
-			public SerializedSortingProperties (Renderer r) : this(new SerializedObject(r)) {}
-			public SerializedSortingProperties (Object[] renderers) : this(new SerializedObject(renderers)) {}
-			public SerializedSortingProperties (SerializedObject rendererSerializedObject) {
-				renderer = rendererSerializedObject;
-				sortingLayerID = renderer.FindProperty("m_SortingLayerID");
-				sortingOrder = renderer.FindProperty("m_SortingOrder");
+		public class BoxScope : System.IDisposable {
+			readonly bool indent;
+
+			static GUIStyle boxScopeStyle;
+			public static GUIStyle BoxScopeStyle {
+				get {
+					if (boxScopeStyle == null) {
+						boxScopeStyle = new GUIStyle(EditorStyles.helpBox);
+						var p = boxScopeStyle.padding;
+						p.right += 6;
+						p.top += 1;
+						p.left += 3;
+					}
+
+					return boxScopeStyle;
+				}
 			}
 			}
 
 
-			public void ApplyModifiedProperties () {
-				renderer.ApplyModifiedProperties();
-				this.SetDirty();
+			public BoxScope (bool indent = true) {
+				this.indent = indent;
+				EditorGUILayout.BeginVertical(BoxScopeStyle);
+				if (indent) EditorGUI.indentLevel++;
+			}
+				
+			public void Dispose () {
+				if (indent) EditorGUI.indentLevel--;
+				EditorGUILayout.EndVertical();
 			}
 			}
+		}
+		#endregion
 
 
-			internal void SetDirty () {
-				if (renderer.isEditingMultipleObjects)
-					foreach (var o in renderer.targetObjects)
-						EditorUtility.SetDirty(o);
-				else
-					EditorUtility.SetDirty(renderer.targetObject);
+		#region Button
+		const float CenterButtonMaxWidth = 270f;
+		const float CenterButtonHeight = 35f;
+		static GUIStyle spineButtonStyle;
+		static GUIStyle SpineButtonStyle {
+			get {
+				if (spineButtonStyle == null) {
+					spineButtonStyle = new GUIStyle(GUI.skin.button);
+					spineButtonStyle.name = "Spine Button";
+					spineButtonStyle.padding = new RectOffset(10, 10, 10, 10);
+				}
+				return spineButtonStyle;
+			}
+		}
+
+		public static bool LargeCenteredButton (string label, bool sideSpace = true) {
+			if (sideSpace) {
+				bool clicked;
+				using (new EditorGUILayout.HorizontalScope()) {
+					EditorGUILayout.Space();
+					clicked = GUILayout.Button(label, SpineButtonStyle, GUILayout.MaxWidth(CenterButtonMaxWidth), GUILayout.Height(CenterButtonHeight));
+					EditorGUILayout.Space();
+				}
+				EditorGUILayout.Space();
+				return clicked;
+			} else {
+				return GUILayout.Button(label, GUILayout.MaxWidth(CenterButtonMaxWidth), GUILayout.Height(CenterButtonHeight));
+			}
+		}
+
+		public static bool LargeCenteredButton (GUIContent content, bool sideSpace = true) {
+			if (sideSpace) {
+				bool clicked;
+				using (new EditorGUILayout.HorizontalScope()) {
+					EditorGUILayout.Space();
+					clicked = GUILayout.Button(content, SpineButtonStyle, GUILayout.MaxWidth(CenterButtonMaxWidth), GUILayout.Height(CenterButtonHeight));
+					EditorGUILayout.Space();
+				}
+				EditorGUILayout.Space();
+				return clicked;
+			} else {
+				return GUILayout.Button(content, GUILayout.MaxWidth(CenterButtonMaxWidth), GUILayout.Height(CenterButtonHeight));
+			}
+		}
+		#endregion
+
+		#region Multi-Editing Helpers
+		public static bool TargetsUseSameData (SerializedObject so) {
+			if (so.isEditingMultipleObjects) {
+				int n = so.targetObjects.Length;
+				var first = so.targetObjects[0] as ISkeletonComponent;
+				for (int i = 1; i < n; i++) {
+					var sr = so.targetObjects[i] as ISkeletonComponent;
+					if (sr != null && sr.SkeletonDataAsset != first.SkeletonDataAsset)
+						return false;
+				}
 			}
 			}
+			return true;
 		}
 		}
 
 
 		public static SerializedObject GetRenderersSerializedObject (SerializedObject serializedObject) {
 		public static SerializedObject GetRenderersSerializedObject (SerializedObject serializedObject) {
@@ -120,40 +208,67 @@ namespace Spine.Unity.Editor {
 
 
 			return null;
 			return null;
 		}
 		}
+		#endregion
 
 
-		public static bool TargetsUseSameData (SerializedObject so) {
-			bool multi = so.isEditingMultipleObjects;
-			if (multi) {
-				int n = so.targetObjects.Length;
-				var first = so.targetObjects[0] as SkeletonRenderer;
-				for (int i = 1; i < n; i++) {
-					var sr = so.targetObjects[i] as SkeletonRenderer;
-					if (sr != null && sr.skeletonDataAsset != first.skeletonDataAsset)
-						return false;
-				}
+		#region Sorting Layer Field Helpers
+		static readonly GUIContent SortingLayerLabel = new GUIContent("Sorting Layer", "MeshRenderer.sortingLayerID");
+		static readonly GUIContent OrderInLayerLabel = new GUIContent("Order in Layer", "MeshRenderer.sortingOrder");
+
+		static MethodInfo m_SortingLayerFieldMethod;
+		static MethodInfo SortingLayerFieldMethod {
+			get {
+				if (m_SortingLayerFieldMethod == null)
+					m_SortingLayerFieldMethod = typeof(EditorGUILayout).GetMethod("SortingLayerField", BindingFlags.Static | BindingFlags.NonPublic, null, new [] { typeof(GUIContent), typeof(SerializedProperty), typeof(GUIStyle) }, null);
+
+				return m_SortingLayerFieldMethod;
 			}
 			}
-			return true;
 		}
 		}
 
 
-		public static void SortingPropertyFields (SerializedSortingProperties prop, bool applyModifiedProperties) {
-			if (applyModifiedProperties) {
-				EditorGUI.BeginChangeCheck();
-				SortingPropertyFields(prop.sortingLayerID, prop.sortingOrder);
-				if(EditorGUI.EndChangeCheck())
-					prop.ApplyModifiedProperties();
-			} else {
-				SortingPropertyFields(prop.sortingLayerID, prop.sortingOrder);
+		public struct SerializedSortingProperties {
+			public SerializedObject renderer;
+			public SerializedProperty sortingLayerID;
+			public SerializedProperty sortingOrder;
+
+			public SerializedSortingProperties (Renderer r) : this(new SerializedObject(r)) {}
+			public SerializedSortingProperties (Object[] renderers) : this(new SerializedObject(renderers)) {}
+
+			/// <summary>
+			/// Initializes a new instance of the
+			/// <see cref="Spine.Unity.Editor.SpineInspectorUtility.SerializedSortingProperties"/> struct.
+			/// </summary>
+			/// <param name="rendererSerializedObject">SerializedObject of the renderer. Use 
+			/// <see cref="Spine.Unity.Editor.SpineInspectorUtility.GetRenderersSerializedObject"/> to easily generate this.</param>
+			public SerializedSortingProperties (SerializedObject rendererSerializedObject) {
+				renderer = rendererSerializedObject;
+				sortingLayerID = renderer.FindProperty("m_SortingLayerID");
+				sortingOrder = renderer.FindProperty("m_SortingOrder");
 			}
 			}
-		}
 
 
-		public static void SortingPropertyFields (SerializedProperty m_SortingLayerID, SerializedProperty m_SortingOrder) {
-			if (SpineInspectorUtility.SortingLayerFieldMethod != null && m_SortingLayerID != null) {
-				SpineInspectorUtility.SortingLayerFieldMethod.Invoke(null, new object[] { SortingLayerLabel, m_SortingLayerID, EditorStyles.popup } );
-			} else {
-				EditorGUILayout.PropertyField(m_SortingLayerID);
+			public void ApplyModifiedProperties () {
+				renderer.ApplyModifiedProperties();
+
+				// SetDirty
+				if (renderer.isEditingMultipleObjects)
+					foreach (var o in renderer.targetObjects)
+						EditorUtility.SetDirty(o);
+				else
+					EditorUtility.SetDirty(renderer.targetObject);
 			}
 			}
+		}
+
+		public static void SortingPropertyFields (SerializedSortingProperties prop, bool applyModifiedProperties) {
+			if (applyModifiedProperties)
+				EditorGUI.BeginChangeCheck();
 
 
-			EditorGUILayout.PropertyField(m_SortingOrder, OrderInLayerLabel);
+			if (SpineInspectorUtility.SortingLayerFieldMethod != null && prop.sortingLayerID != null)
+				SpineInspectorUtility.SortingLayerFieldMethod.Invoke(null, new object[] { SortingLayerLabel, prop.sortingLayerID, EditorStyles.popup } );
+			else
+				EditorGUILayout.PropertyField(prop.sortingLayerID);
+
+			EditorGUILayout.PropertyField(prop.sortingOrder, OrderInLayerLabel);
+
+			if (applyModifiedProperties && EditorGUI.EndChangeCheck())
+					prop.ApplyModifiedProperties();
 		}
 		}
 		#endregion
 		#endregion
 	}
 	}

+ 0 - 2
spine-unity/Assets/spine-unity/ISkeletonAnimation.cs

@@ -28,8 +28,6 @@
  * POSSIBILITY OF SUCH DAMAGE.
  * POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
-using UnityEngine;
-
 namespace Spine.Unity {
 namespace Spine.Unity {
 	public delegate void UpdateBonesDelegate (ISkeletonAnimation animatedSkeletonComponent);
 	public delegate void UpdateBonesDelegate (ISkeletonAnimation animatedSkeletonComponent);
 
 

+ 0 - 3
spine-unity/Assets/spine-unity/Mesh Generation/DoubleBuffered.cs

@@ -28,9 +28,6 @@
  * POSSIBILITY OF SUCH DAMAGE.
  * POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
-using UnityEngine;
-using System.Collections;
-
 namespace Spine.Unity {
 namespace Spine.Unity {
 	public class DoubleBuffered<T> where T : new() {
 	public class DoubleBuffered<T> where T : new() {
 		readonly T a = new T();
 		readonly T a = new T();

+ 78 - 56
spine-unity/Assets/spine-unity/Modules/BoundingBoxFollower/BoundingBoxFollower.cs

@@ -35,11 +35,14 @@ namespace Spine.Unity {
 
 
 	[ExecuteInEditMode]
 	[ExecuteInEditMode]
 	public class BoundingBoxFollower : MonoBehaviour {
 	public class BoundingBoxFollower : MonoBehaviour {
+		internal static bool DebugMessages = true;
+
 		#region Inspector
 		#region Inspector
 		public SkeletonRenderer skeletonRenderer;
 		public SkeletonRenderer skeletonRenderer;
 		[SpineSlot(dataField: "skeletonRenderer", containsBoundingBoxes: true)]
 		[SpineSlot(dataField: "skeletonRenderer", containsBoundingBoxes: true)]
 		public string slotName;
 		public string slotName;
 		public bool isTrigger;
 		public bool isTrigger;
+		public bool clearStateOnDisable = true;
 		#endregion
 		#endregion
 
 
 		Slot slot;
 		Slot slot;
@@ -47,10 +50,8 @@ namespace Spine.Unity {
 		string currentAttachmentName;
 		string currentAttachmentName;
 		PolygonCollider2D currentCollider;
 		PolygonCollider2D currentCollider;
 
 
-		bool hasReset;
-
 		public readonly Dictionary<BoundingBoxAttachment, PolygonCollider2D> colliderTable = new Dictionary<BoundingBoxAttachment, PolygonCollider2D>();
 		public readonly Dictionary<BoundingBoxAttachment, PolygonCollider2D> colliderTable = new Dictionary<BoundingBoxAttachment, PolygonCollider2D>();
-		public readonly Dictionary<BoundingBoxAttachment, string> attachmentNameTable = new Dictionary<BoundingBoxAttachment, string>();
+		public readonly Dictionary<BoundingBoxAttachment, string> nameTable = new Dictionary<BoundingBoxAttachment, string>();
 
 
 		public Slot Slot { get { return slot; } }
 		public Slot Slot { get { return slot; } }
 		public BoundingBoxAttachment CurrentAttachment { get { return currentAttachment; } }
 		public BoundingBoxAttachment CurrentAttachment { get { return currentAttachment; } }
@@ -58,90 +59,111 @@ namespace Spine.Unity {
 		public PolygonCollider2D CurrentCollider { get { return currentCollider; } }
 		public PolygonCollider2D CurrentCollider { get { return currentCollider; } }
 		public bool IsTrigger { get { return isTrigger; } }
 		public bool IsTrigger { get { return isTrigger; } }
 
 
-		void OnEnable () {
-			ClearColliders();
-
-			if (skeletonRenderer == null)
-				skeletonRenderer = GetComponentInParent<SkeletonRenderer>();
+		void Start () {
+			Initialize();
+		}
 
 
+		void OnEnable () {
 			if (skeletonRenderer != null) {
 			if (skeletonRenderer != null) {
 				skeletonRenderer.OnRebuild -= HandleRebuild;
 				skeletonRenderer.OnRebuild -= HandleRebuild;
 				skeletonRenderer.OnRebuild += HandleRebuild;
 				skeletonRenderer.OnRebuild += HandleRebuild;
-
-				if (hasReset)
-					HandleRebuild(skeletonRenderer);
 			}
 			}
-		}
 
 
-		void OnDisable () {
-			skeletonRenderer.OnRebuild -= HandleRebuild;
+			Initialize();
 		}
 		}
 
 
-		void Start () {
-			if (!hasReset && skeletonRenderer != null)
-				HandleRebuild(skeletonRenderer);
+		void HandleRebuild (SkeletonRenderer sr) {
+			//if (BoundingBoxFollower.DebugMessages) Debug.Log("Skeleton was rebuilt. Repopulating BoundingBoxFollower.");
+			Initialize();
 		}
 		}
 
 
-		public void HandleRebuild (SkeletonRenderer renderer) {
+		/// <summary>
+		/// Initialize and instantiate the BoundingBoxFollower colliders. This is method checks if the BoundingBoxFollower has already been initialized for the skeleton instance and slotName and prevents overwriting unless it detects a new setup.</summary>
+		public void Initialize () {
+			if (skeletonRenderer == null)
+				return;
+
+			skeletonRenderer.Initialize(false);
+			
 			if (string.IsNullOrEmpty(slotName))
 			if (string.IsNullOrEmpty(slotName))
 				return;
 				return;
 
 
-			hasReset = true;
-			ClearColliders();
-			colliderTable.Clear();
+			// Don't reinitialize if the setup did not change.
+			if (colliderTable.Count > 0 && slot != null			// Slot is set and colliders already populated.
+				&&
+				skeletonRenderer.skeleton == slot.Skeleton		// Skeleton object did not change.
+				&&
+				slotName == slot.data.name						// Slot object did not change.
+			) 
+				return;
 
 
-			if (skeletonRenderer.skeleton == null) {
-				skeletonRenderer.OnRebuild -= HandleRebuild;
-				skeletonRenderer.Initialize(false);
-				skeletonRenderer.OnRebuild += HandleRebuild;
-			}
+			DisposeColliders();
 
 
 			var skeleton = skeletonRenderer.skeleton;
 			var skeleton = skeletonRenderer.skeleton;
 			slot = skeleton.FindSlot(slotName);
 			slot = skeleton.FindSlot(slotName);
 			int slotIndex = skeleton.FindSlotIndex(slotName);
 			int slotIndex = skeleton.FindSlotIndex(slotName);
 
 
+			if (slot == null) {
+				if (BoundingBoxFollower.DebugMessages)
+					Debug.LogWarning(string.Format("Slot '{0}' not found for BoundingBoxFollower on '{1}'. (Previous colliders were disposed.)", slotName, this.gameObject.name));
+				return;
+			}
+
 			if (this.gameObject.activeInHierarchy) {
 			if (this.gameObject.activeInHierarchy) {
 				foreach (var skin in skeleton.Data.Skins) {
 				foreach (var skin in skeleton.Data.Skins) {
 					var attachmentNames = new List<string>();
 					var attachmentNames = new List<string>();
 					skin.FindNamesForSlot(slotIndex, attachmentNames);
 					skin.FindNamesForSlot(slotIndex, attachmentNames);
 
 
-					foreach (var attachmentName in attachmentNames) {
-						var attachment = skin.GetAttachment(slotIndex, attachmentName);
+					foreach (var skinKey in attachmentNames) {
+						var attachment = skin.GetAttachment(slotIndex, skinKey);
 						var boundingBoxAttachment = attachment as BoundingBoxAttachment;
 						var boundingBoxAttachment = attachment as BoundingBoxAttachment;
 
 
-#if UNITY_EDITOR
-						if (attachment != null && boundingBoxAttachment == null)
-							Debug.Log("BoundingBoxFollower tried to follow a slot that contains non-boundingbox attachments: " + slotName);
-#endif
+						if (BoundingBoxFollower.DebugMessages && attachment != null && boundingBoxAttachment == null)
+							Debug.Log("BoundingBoxFollower tried to follow a slot that contains non-boundingbox attachments: " + slotName);						
 
 
 						if (boundingBoxAttachment != null) {
 						if (boundingBoxAttachment != null) {
-							var bbCollider = SkeletonUtility.AddBoundingBoxAsComponent(boundingBoxAttachment, gameObject, true);
+							var bbCollider = SkeletonUtility.AddBoundingBoxAsComponent(boundingBoxAttachment, slot, gameObject, isTrigger);
 							bbCollider.enabled = false;
 							bbCollider.enabled = false;
 							bbCollider.hideFlags = HideFlags.NotEditable;
 							bbCollider.hideFlags = HideFlags.NotEditable;
 							bbCollider.isTrigger = IsTrigger;
 							bbCollider.isTrigger = IsTrigger;
 							colliderTable.Add(boundingBoxAttachment, bbCollider);
 							colliderTable.Add(boundingBoxAttachment, bbCollider);
-							attachmentNameTable.Add(boundingBoxAttachment, attachmentName);
+							nameTable.Add(boundingBoxAttachment, skinKey);
 						}
 						}
 					}
 					}
 				}
 				}
 			}
 			}
 
 
-#if UNITY_EDITOR
-			bool valid = colliderTable.Count != 0;
-			if (!valid) {
-				if (this.gameObject.activeInHierarchy)
-					Debug.LogWarning("Bounding Box Follower not valid! Slot [" + slotName + "] does not contain any Bounding Box Attachments!");
-				else 
-					Debug.LogWarning("Bounding Box Follower tried to rebuild as a prefab.");
+			if (BoundingBoxFollower.DebugMessages) {
+				bool valid = colliderTable.Count != 0;
+				if (!valid) {
+					if (this.gameObject.activeInHierarchy)
+						Debug.LogWarning("Bounding Box Follower not valid! Slot [" + slotName + "] does not contain any Bounding Box Attachments!");
+					else 
+						Debug.LogWarning("Bounding Box Follower tried to rebuild as a prefab.");
+				}
 			}
 			}
-#endif
 		}
 		}
 
 
-		void ClearColliders () {
+		void OnDisable () {
+			if (clearStateOnDisable)
+				ClearState();
+		}
+
+		public void ClearState () {
+			if (colliderTable != null)
+				foreach (var col in colliderTable.Values)
+					col.enabled = false;
+
+			currentAttachment = null;
+			currentAttachmentName = null;
+			currentCollider = null;
+		}
+
+		void DisposeColliders () {
+			#if UNITY_EDITOR
 			var colliders = GetComponents<PolygonCollider2D>();
 			var colliders = GetComponents<PolygonCollider2D>();
 			if (colliders.Length == 0) return;
 			if (colliders.Length == 0) return;
 
 
-#if UNITY_EDITOR
 			if (Application.isPlaying) {
 			if (Application.isPlaying) {
 				foreach (var c in colliders) {
 				foreach (var c in colliders) {
 					if (c != null)
 					if (c != null)
@@ -149,22 +171,24 @@ namespace Spine.Unity {
 				}
 				}
 			} else {
 			} else {
 				foreach (var c in colliders)
 				foreach (var c in colliders)
-					DestroyImmediate(c);
+					if (c != null) 
+						DestroyImmediate(c);
 			}
 			}
-#else
-			foreach (var c in colliders)
+			#else
+			foreach (PolygonCollider2D c in colliderTable.Values)
 				if (c != null)
 				if (c != null)
 					Destroy(c);
 					Destroy(c);
-#endif
+			#endif
 
 
+			slot = null;
+			currentAttachment = null;
+			currentAttachmentName = null;
+			currentCollider = null;
 			colliderTable.Clear();
 			colliderTable.Clear();
-			attachmentNameTable.Clear();
+			nameTable.Clear();
 		}
 		}
 
 
 		void LateUpdate () {
 		void LateUpdate () {
-			if (!skeletonRenderer.valid)
-				return;
-
 			if (slot != null && slot.Attachment != currentAttachment)
 			if (slot != null && slot.Attachment != currentAttachment)
 				MatchAttachment(slot.Attachment);
 				MatchAttachment(slot.Attachment);
 		}
 		}
@@ -174,10 +198,8 @@ namespace Spine.Unity {
 		void MatchAttachment (Attachment attachment) {
 		void MatchAttachment (Attachment attachment) {
 			var bbAttachment = attachment as BoundingBoxAttachment;
 			var bbAttachment = attachment as BoundingBoxAttachment;
 
 
-#if UNITY_EDITOR
-			if (attachment != null && bbAttachment == null)
+			if (BoundingBoxFollower.DebugMessages && attachment != null && bbAttachment == null)
 				Debug.LogWarning("BoundingBoxFollower tried to match a non-boundingbox attachment. It will treat it as null.");
 				Debug.LogWarning("BoundingBoxFollower tried to match a non-boundingbox attachment. It will treat it as null.");
-#endif
 
 
 			if (currentCollider != null)
 			if (currentCollider != null)
 				currentCollider.enabled = false;
 				currentCollider.enabled = false;
@@ -190,7 +212,7 @@ namespace Spine.Unity {
 			}
 			}
 
 
 			currentAttachment = bbAttachment;
 			currentAttachment = bbAttachment;
-			currentAttachmentName = currentAttachment == null ? null : attachmentNameTable[bbAttachment];
+			currentAttachmentName = currentAttachment == null ? null : nameTable[bbAttachment];
 		}
 		}
 	}
 	}
 
 

+ 112 - 28
spine-unity/Assets/spine-unity/Modules/BoundingBoxFollower/Editor/BoundingBoxFollowerInspector.cs

@@ -32,79 +32,163 @@ using UnityEngine;
 using UnityEditor;
 using UnityEditor;
 
 
 namespace Spine.Unity.Editor {
 namespace Spine.Unity.Editor {
-	
+	using Event = UnityEngine.Event;
+
 	[CustomEditor(typeof(BoundingBoxFollower))]
 	[CustomEditor(typeof(BoundingBoxFollower))]
 	public class BoundingBoxFollowerInspector : UnityEditor.Editor {
 	public class BoundingBoxFollowerInspector : UnityEditor.Editor {
-		SerializedProperty skeletonRenderer, slotName, isTrigger;
+		SerializedProperty skeletonRenderer, slotName, isTrigger, clearStateOnDisable;
 		BoundingBoxFollower follower;
 		BoundingBoxFollower follower;
 		bool rebuildRequired = false;
 		bool rebuildRequired = false;
 		bool addBoneFollower = false;
 		bool addBoneFollower = false;
+		bool sceneRepaintRequired = false;
+		bool debugIsExpanded;
+
+		readonly GUIContent AddBoneFollowerLabel = new GUIContent("Add Bone Follower", SpineEditorUtilities.Icons.bone);
 
 
 		void OnEnable () {
 		void OnEnable () {
 			skeletonRenderer = serializedObject.FindProperty("skeletonRenderer");
 			skeletonRenderer = serializedObject.FindProperty("skeletonRenderer");
 			slotName = serializedObject.FindProperty("slotName");
 			slotName = serializedObject.FindProperty("slotName");
 			isTrigger = serializedObject.FindProperty("isTrigger");
 			isTrigger = serializedObject.FindProperty("isTrigger");
+			clearStateOnDisable = serializedObject.FindProperty("clearStateOnDisable");
 			follower = (BoundingBoxFollower)target;
 			follower = (BoundingBoxFollower)target;
 		}
 		}
 
 
 		public override void OnInspectorGUI () {
 		public override void OnInspectorGUI () {
 			bool isInspectingPrefab = (PrefabUtility.GetPrefabType(target) == PrefabType.Prefab);
 			bool isInspectingPrefab = (PrefabUtility.GetPrefabType(target) == PrefabType.Prefab);
-			bool repaintEvent = UnityEngine.Event.current.type == EventType.Repaint;
 
 
-			if (rebuildRequired) {
-				follower.HandleRebuild(null);
-				rebuildRequired = false;
+			// Try to auto-assign SkeletonRenderer field.
+			if (skeletonRenderer.objectReferenceValue == null) {
+				var foundSkeletonRenderer = follower.GetComponentInParent<SkeletonRenderer>();
+				if (foundSkeletonRenderer != null)
+					Debug.Log("BoundingBoxFollower automatically assigned: " + foundSkeletonRenderer.gameObject.name);
+				else if (Event.current.type == EventType.Repaint)
+					Debug.Log("No Spine GameObject detected. Make sure to set this GameObject as a child of the Spine GameObject; or set BoundingBoxFollower's 'Skeleton Renderer' field in the inspector.");
+
+				skeletonRenderer.objectReferenceValue = foundSkeletonRenderer;
+				serializedObject.ApplyModifiedProperties();
+			}
+
+			var sr = skeletonRenderer.objectReferenceValue as SkeletonRenderer;
+			if (sr != null && sr.gameObject == follower.gameObject) {
+				using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) {
+					EditorGUILayout.HelpBox("It's ideal to add BoundingBoxFollower to a separate child GameObject of the Spine GameObject.", MessageType.Warning);
+
+					if (GUILayout.Button(new GUIContent("Move BoundingBoxFollower to new GameObject", SpineEditorUtilities.Icons.boundingBox), GUILayout.Height(50f))) {
+						AddBoundingBoxFollowerChild(sr, follower);
+						DestroyImmediate(follower);
+						return;
+					}
+				}
+				EditorGUILayout.Space();
 			}
 			}
 
 
 			EditorGUI.BeginChangeCheck();
 			EditorGUI.BeginChangeCheck();
 			EditorGUILayout.PropertyField(skeletonRenderer);
 			EditorGUILayout.PropertyField(skeletonRenderer);
 			EditorGUILayout.PropertyField(slotName, new GUIContent("Slot"));
 			EditorGUILayout.PropertyField(slotName, new GUIContent("Slot"));
-			EditorGUILayout.PropertyField(isTrigger);
-
 			if (EditorGUI.EndChangeCheck()) {
 			if (EditorGUI.EndChangeCheck()) {
 				serializedObject.ApplyModifiedProperties();
 				serializedObject.ApplyModifiedProperties();
 				if (!isInspectingPrefab)
 				if (!isInspectingPrefab)
 					rebuildRequired = true;
 					rebuildRequired = true;
 			}
 			}
 
 
-			bool hasBoneFollower = follower.GetComponent<BoneFollower>() != null;
-			using (new EditorGUI.DisabledGroupScope(hasBoneFollower || follower.Slot == null)) {
-				if (GUILayout.Button(new GUIContent("Add Bone Follower", SpineEditorUtilities.Icons.bone))) {
-					addBoneFollower = true;
+			using (new SpineInspectorUtility.LabelWidthScope(150f)) {
+				EditorGUI.BeginChangeCheck();
+				EditorGUILayout.PropertyField(isTrigger);
+				bool triggerChanged = EditorGUI.EndChangeCheck();
+
+				EditorGUI.BeginChangeCheck();
+				EditorGUILayout.PropertyField(clearStateOnDisable, new GUIContent(clearStateOnDisable.displayName, "Enable this if you are pooling your Spine GameObject"));
+				bool clearStateChanged = EditorGUI.EndChangeCheck();
+
+				if (clearStateChanged || triggerChanged) {
+					serializedObject.ApplyModifiedProperties();
+					if (triggerChanged)
+						foreach (var col in follower.colliderTable.Values)
+							col.isTrigger = isTrigger.boolValue;
 				}
 				}
 			}
 			}
 
 
 			if (isInspectingPrefab) {
 			if (isInspectingPrefab) {
 				follower.colliderTable.Clear();
 				follower.colliderTable.Clear();
-				follower.attachmentNameTable.Clear();
+				follower.nameTable.Clear();
 				EditorGUILayout.HelpBox("BoundingBoxAttachments cannot be previewed in prefabs.", MessageType.Info);
 				EditorGUILayout.HelpBox("BoundingBoxAttachments cannot be previewed in prefabs.", MessageType.Info);
 
 
 				// How do you prevent components from being saved into the prefab? No such HideFlag. DontSaveInEditor | DontSaveInBuild does not work. DestroyImmediate does not work.
 				// How do you prevent components from being saved into the prefab? No such HideFlag. DontSaveInEditor | DontSaveInBuild does not work. DestroyImmediate does not work.
 				var collider = follower.GetComponent<PolygonCollider2D>();
 				var collider = follower.GetComponent<PolygonCollider2D>();
 				if (collider != null) Debug.LogWarning("Found BoundingBoxFollower collider components in prefab. These are disposed and regenerated at runtime.");
 				if (collider != null) Debug.LogWarning("Found BoundingBoxFollower collider components in prefab. These are disposed and regenerated at runtime.");
 
 
-			} else {				
-				EditorGUILayout.LabelField(string.Format("Attachment Names ({0} PolygonCollider2D)", follower.colliderTable.Count), EditorStyles.boldLabel);
-				EditorGUI.BeginChangeCheck();
-				foreach (var kp in follower.attachmentNameTable) {
-					string attachmentName = kp.Value;
-					var collider = follower.colliderTable[kp.Key];
-					bool isPlaceholder = attachmentName != kp.Key.Name;
-					collider.enabled = EditorGUILayout.ToggleLeft(new GUIContent(!isPlaceholder ? attachmentName : attachmentName + " [" + kp.Key.Name + "]", isPlaceholder ? SpineEditorUtilities.Icons.skinPlaceholder : SpineEditorUtilities.Icons.boundingBox), collider.enabled);
+			} else {
+				using (new SpineInspectorUtility.BoxScope()) {
+					if (debugIsExpanded = EditorGUILayout.Foldout(debugIsExpanded, "Debug Colliders")) {
+						EditorGUI.indentLevel++;
+						EditorGUILayout.LabelField(string.Format("Attachment Names ({0} PolygonCollider2D)", follower.colliderTable.Count));
+						EditorGUI.BeginChangeCheck();
+						foreach (var kp in follower.nameTable) {
+							string attachmentName = kp.Value;
+							var collider = follower.colliderTable[kp.Key];
+							bool isPlaceholder = attachmentName != kp.Key.Name;
+							collider.enabled = EditorGUILayout.ToggleLeft(new GUIContent(!isPlaceholder ? attachmentName : attachmentName + " [" + kp.Key.Name + "]", isPlaceholder ? SpineEditorUtilities.Icons.skinPlaceholder : SpineEditorUtilities.Icons.boundingBox), collider.enabled);
+						}
+						sceneRepaintRequired |= EditorGUI.EndChangeCheck();
+						EditorGUI.indentLevel--;
+					}
 				}
 				}
-				if (EditorGUI.EndChangeCheck()) {
+
+			}
+
+			bool hasBoneFollower = follower.GetComponent<BoneFollower>() != null;
+			if (!hasBoneFollower) {
+				bool buttonDisabled = follower.Slot == null;
+				using (new EditorGUI.DisabledGroupScope(buttonDisabled)) {
+					addBoneFollower |= SpineInspectorUtility.LargeCenteredButton(AddBoneFollowerLabel, true);
+					EditorGUILayout.Space();
+				}
+			}
+
+
+			if (Event.current.type == EventType.Repaint) {
+				if (addBoneFollower) {
+					var boneFollower = follower.gameObject.AddComponent<BoneFollower>();
+					boneFollower.boneName = follower.Slot.Data.BoneData.Name;
+					addBoneFollower = false;
+				}
+
+				if (sceneRepaintRequired) {
 					SceneView.RepaintAll();
 					SceneView.RepaintAll();
+					sceneRepaintRequired = false;
 				}
 				}
 
 
-				if (!Application.isPlaying)
-					EditorGUILayout.HelpBox("\nAt runtime, BoundingBoxFollower enables and disables PolygonCollider2Ds based on the currently active attachment in the slot.\n\nCheckboxes in Edit Mode are only for preview. Checkbox states are not saved.\n", MessageType.Info);
+				if (rebuildRequired) {
+					follower.Initialize();
+					rebuildRequired = false;
+				}
 			}
 			}
+		}
+
+		#region Menus
+		[MenuItem("CONTEXT/SkeletonRenderer/Add BoundingBoxFollower GameObject")]
+		static void AddBoundingBoxFollowerChild (MenuCommand command) {
+			AddBoundingBoxFollowerChild((SkeletonRenderer)command.context);
+		}
+		#endregion
 
 
-			if (addBoneFollower && repaintEvent) {
-				var boneFollower = follower.gameObject.AddComponent<BoneFollower>();
-				boneFollower.boneName = follower.Slot.Data.BoneData.Name;
-				addBoneFollower = false;
+		static void AddBoundingBoxFollowerChild (SkeletonRenderer sr, BoundingBoxFollower original = null) {
+			var go = new GameObject("BoundingBoxFollower");
+			go.transform.SetParent(sr.transform, false);
+			var newFollower = go.AddComponent<BoundingBoxFollower>();
+
+			if (original != null) {
+				newFollower.slotName = original.slotName;
+				newFollower.isTrigger = original.isTrigger;
+				newFollower.clearStateOnDisable = original.clearStateOnDisable;
 			}
 			}
+
+			newFollower.skeletonRenderer = sr;
+			newFollower.Initialize();
+
+
+			Selection.activeGameObject = go;
+			EditorGUIUtility.PingObject(go);
 		}
 		}
 
 
 	}
 	}

+ 1 - 1
spine-unity/Assets/spine-unity/Modules/CustomMaterials/Editor/SkeletonRendererCustomMaterialsInspector.cs

@@ -134,7 +134,7 @@ namespace Spine.Unity.Editor {
 			_customMaterialOverridesPrev = CopyList(componentCustomMaterialOverrides);
 			_customMaterialOverridesPrev = CopyList(componentCustomMaterialOverrides);
 			_customSlotMaterialsPrev = CopyList(componentCustomSlotMaterials);
 			_customSlotMaterialsPrev = CopyList(componentCustomSlotMaterials);
 
 
-			if (GUILayout.Button(new GUIContent("Clear and Reapply Changes", "Removes all non-serialized overrides in the SkeletonRenderer and reapplies the overrides on this component."))) {
+			if (SpineInspectorUtility.LargeCenteredButton(new GUIContent("Clear and Reapply Changes", "Removes all non-serialized overrides in the SkeletonRenderer and reapplies the overrides on this component."))) {
 				if (skeletonRenderer != null) {
 				if (skeletonRenderer != null) {
 					skeletonRenderer.CustomMaterialOverride.Clear();
 					skeletonRenderer.CustomMaterialOverride.Clear();
 					skeletonRenderer.CustomSlotMaterials.Clear();
 					skeletonRenderer.CustomSlotMaterials.Clear();

+ 5 - 5
spine-unity/Assets/spine-unity/Modules/Ghost/SkeletonGhost.cs

@@ -86,11 +86,11 @@ namespace Spine.Unity.Modules {
 
 
 		//SkeletonAnimation
 		//SkeletonAnimation
 		/*
 		/*
-	 *	Int Value:		0 sets ghostingEnabled to false, 1 sets ghostingEnabled to true
-	 *	Float Value:	Values greater than 0 set the spawnRate equal the float value
-	 *	String Value:	Pass RGBA hex color values in to set the color property.  IE:   "A0FF8BFF"
-	 */
-		void OnEvent (Spine.AnimationState state, int trackIndex, Spine.Event e) {
+		 *	Int Value:		0 sets ghostingEnabled to false, 1 sets ghostingEnabled to true
+		 *	Float Value:	Values greater than 0 set the spawnRate equal the float value
+		 *	String Value:	Pass RGBA hex color values in to set the color property.  IE:   "A0FF8BFF"
+		 */
+		void OnEvent (Spine.TrackEntry trackEntry, Spine.Event e) {
 			if (e.Data.Name == "Ghosting") {
 			if (e.Data.Name == "Ghosting") {
 				ghostingEnabled = e.Int > 0;
 				ghostingEnabled = e.Int > 0;
 				if (e.Float > 0)
 				if (e.Float > 0)

+ 1 - 1
spine-unity/Assets/spine-unity/Modules/Ragdoll/SkeletonRagdoll.cs

@@ -353,7 +353,7 @@ namespace Spine.Unity.Modules {
 				b.x = Mathf.Lerp(b.x, boneLocalPosition.x, mix);
 				b.x = Mathf.Lerp(b.x, boneLocalPosition.x, mix);
 				b.y = Mathf.Lerp(b.y, boneLocalPosition.y, mix);
 				b.y = Mathf.Lerp(b.y, boneLocalPosition.y, mix);
 				b.rotation = Mathf.Lerp(b.rotation, boneLocalRotation, mix);
 				b.rotation = Mathf.Lerp(b.rotation, boneLocalRotation, mix);
-				b.appliedRotation = Mathf.Lerp(b.appliedRotation, boneLocalRotation, mix);
+				//b.AppliedRotation = Mathf.Lerp(b.AppliedRotation, boneLocalRotation, mix);
 			}
 			}
 		}
 		}
 
 

+ 2 - 2
spine-unity/Assets/spine-unity/Modules/Ragdoll/SkeletonRagdoll2D.cs

@@ -380,7 +380,7 @@ namespace Spine.Unity.Modules {
 				b.x = Mathf.Lerp(b.x, boneLocalPosition.x, mix);
 				b.x = Mathf.Lerp(b.x, boneLocalPosition.x, mix);
 				b.y = Mathf.Lerp(b.y, boneLocalPosition.y, mix);
 				b.y = Mathf.Lerp(b.y, boneLocalPosition.y, mix);
 				b.rotation = Mathf.Lerp(b.rotation, boneLocalRotation, mix);
 				b.rotation = Mathf.Lerp(b.rotation, boneLocalRotation, mix);
-				b.appliedRotation = Mathf.Lerp(b.appliedRotation, boneLocalRotation, mix);
+				//b.AppliedRotation = Mathf.Lerp(b.AppliedRotation, boneLocalRotation, mix);
 			}
 			}
 		}
 		}
 
 
@@ -399,7 +399,7 @@ namespace Spine.Unity.Modules {
 							if (!a.Name.ToLower().Contains(AttachmentNameMarker))
 							if (!a.Name.ToLower().Contains(AttachmentNameMarker))
 								continue;
 								continue;
 
 
-							var bbCollider = SkeletonUtility.AddBoundingBoxAsComponent(bbAttachment, go, false);
+							var bbCollider = SkeletonUtility.AddBoundingBoxAsComponent(bbAttachment, s, go, false);
 							colliders.Add(bbCollider);
 							colliders.Add(bbCollider);
 						}
 						}
 					}
 					}

+ 30 - 29
spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/Editor/SkeletonGraphicInspector.cs

@@ -44,7 +44,7 @@ namespace Spine.Unity.Editor {
 	public class SkeletonGraphicInspector : UnityEditor.Editor {
 	public class SkeletonGraphicInspector : UnityEditor.Editor {
 		SerializedProperty material_, color_;
 		SerializedProperty material_, color_;
 		SerializedProperty skeletonDataAsset_, initialSkinName_;
 		SerializedProperty skeletonDataAsset_, initialSkinName_;
-		SerializedProperty startingAnimation_, startingLoop_, timeScale_, freeze_;
+		SerializedProperty startingAnimation_, startingLoop_, timeScale_, freeze_, unscaledTime_;
 	#if !PREUNITY_5_2
 	#if !PREUNITY_5_2
 		SerializedProperty raycastTarget_;
 		SerializedProperty raycastTarget_;
 
 
@@ -67,6 +67,7 @@ namespace Spine.Unity.Editor {
 			startingAnimation_ = so.FindProperty("startingAnimation");
 			startingAnimation_ = so.FindProperty("startingAnimation");
 			startingLoop_ = so.FindProperty("startingLoop");
 			startingLoop_ = so.FindProperty("startingLoop");
 			timeScale_ = so.FindProperty("timeScale");
 			timeScale_ = so.FindProperty("timeScale");
+			unscaledTime_ = so.FindProperty("unscaledTime");
 			freeze_ = so.FindProperty("freeze");
 			freeze_ = so.FindProperty("freeze");
 		}
 		}
 
 
@@ -91,6 +92,7 @@ namespace Spine.Unity.Editor {
 			EditorGUILayout.PropertyField(startingAnimation_);
 			EditorGUILayout.PropertyField(startingAnimation_);
 			EditorGUILayout.PropertyField(startingLoop_);
 			EditorGUILayout.PropertyField(startingLoop_);
 			EditorGUILayout.PropertyField(timeScale_);
 			EditorGUILayout.PropertyField(timeScale_);
+			EditorGUILayout.PropertyField(unscaledTime_, new GUIContent(unscaledTime_.displayName, "If checked, this will use Time.unscaledDeltaTime to make this update independent of game Time.timeScale. Instance SkeletonGraphic.timeScale will still be applied."));
 			EditorGUILayout.Space();
 			EditorGUILayout.Space();
 			EditorGUILayout.PropertyField(freeze_);
 			EditorGUILayout.PropertyField(freeze_);
 			EditorGUILayout.Space();
 			EditorGUILayout.Space();
@@ -99,9 +101,8 @@ namespace Spine.Unity.Editor {
 
 
 			bool wasChanged = EditorGUI.EndChangeCheck();
 			bool wasChanged = EditorGUI.EndChangeCheck();
 
 
-			if (wasChanged) {
+			if (wasChanged)
 				serializedObject.ApplyModifiedProperties();
 				serializedObject.ApplyModifiedProperties();
-			}
 		}
 		}
 
 
 		#region Menus
 		#region Menus
@@ -138,32 +139,32 @@ namespace Spine.Unity.Editor {
 			EditorGUIUtility.PingObject(Selection.activeObject);
 			EditorGUIUtility.PingObject(Selection.activeObject);
 		}
 		}
 
 
-		[MenuItem("Assets/Spine/Instantiate (UnityUI)", false, 20)]
-		static void InstantiateSkeletonGraphic () {
-			Object[] arr = Selection.objects;
-			foreach (Object o in arr) {
-				string guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(o));
-				string skinName = EditorPrefs.GetString(guid + "_lastSkin", "");
-
-				InstantiateSkeletonGraphic((SkeletonDataAsset)o, skinName);
-				SceneView.RepaintAll();
-			}
-		}
-
-		[MenuItem("Assets/Spine/Instantiate (UnityUI)", true, 20)]
-		static bool ValidateInstantiateSkeletonGraphic () {
-			Object[] arr = Selection.objects;
-
-			if (arr.Length == 0)
-				return false;
-
-			foreach (var selected in arr) {
-				if (selected.GetType() != typeof(SkeletonDataAsset))
-					return false;
-			}
-
-			return true;
-		}
+//		[MenuItem("Assets/Spine/Instantiate (UnityUI)", false, 20)]
+//		static void InstantiateSkeletonGraphic () {
+//			Object[] arr = Selection.objects;
+//			foreach (Object o in arr) {
+//				string guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(o));
+//				string skinName = EditorPrefs.GetString(guid + "_lastSkin", "");
+//
+//				InstantiateSkeletonGraphic((SkeletonDataAsset)o, skinName);
+//				SceneView.RepaintAll();
+//			}
+//		}
+//
+//		[MenuItem("Assets/Spine/Instantiate (UnityUI)", true, 20)]
+//		static bool ValidateInstantiateSkeletonGraphic () {
+//			Object[] arr = Selection.objects;
+//
+//			if (arr.Length == 0)
+//				return false;
+//
+//			foreach (var selected in arr) {
+//				if (selected.GetType() != typeof(SkeletonDataAsset))
+//					return false;
+//			}
+//
+//			return true;
+//		}
 
 
 		// SpineEditorUtilities.InstantiateDelegate. Used by drag and drop.
 		// SpineEditorUtilities.InstantiateDelegate. Used by drag and drop.
 		public static Component SpawnSkeletonGraphicFromDrop (SkeletonDataAsset data) {
 		public static Component SpawnSkeletonGraphicFromDrop (SkeletonDataAsset data) {

+ 2 - 1
spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/SkeletonGraphic.cs

@@ -83,6 +83,7 @@ namespace Spine.Unity {
 		public bool startingLoop;
 		public bool startingLoop;
 		public float timeScale = 1f;
 		public float timeScale = 1f;
 		public bool freeze;
 		public bool freeze;
+		public bool unscaledTime;
 
 
 		#if UNITY_EDITOR
 		#if UNITY_EDITOR
 		protected override void OnValidate () {
 		protected override void OnValidate () {
@@ -149,7 +150,7 @@ namespace Spine.Unity {
 
 
 		public virtual void Update () {
 		public virtual void Update () {
 			if (freeze) return;
 			if (freeze) return;
-			Update(Time.deltaTime);
+			Update(unscaledTime ? Time.unscaledDeltaTime : Time.deltaTime);
 		}
 		}
 
 
 		public virtual void Update (float deltaTime) {
 		public virtual void Update (float deltaTime) {

+ 1 - 31
spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonPartsRendererInspector.cs

@@ -27,37 +27,7 @@
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  * POSSIBILITY OF SUCH DAMAGE.
  * POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
-
-/******************************************************************************
- * 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 UnityEngine;
 using UnityEngine;
 using UnityEditor;
 using UnityEditor;
 using Spine.Unity.Editor;
 using Spine.Unity.Editor;

+ 12 - 12
spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonRenderSeparatorInspector.cs

@@ -68,11 +68,10 @@ namespace Spine.Unity.Modules {
 
 
 		int SkeletonRendererSeparatorCount {
 		int SkeletonRendererSeparatorCount {
 			get {
 			get {
-				if (Application.isPlaying) {
+				if (Application.isPlaying)
 					return component.SkeletonRenderer.separatorSlots.Count;
 					return component.SkeletonRenderer.separatorSlots.Count;
-				} else {
+				else
 					return separatorNamesProp == null ? 0 : separatorNamesProp.arraySize;
 					return separatorNamesProp == null ? 0 : separatorNamesProp.arraySize;
-				}
 			}
 			}
 		}
 		}
 
 
@@ -81,17 +80,18 @@ namespace Spine.Unity.Modules {
 			var componentRenderers = component.partsRenderers;
 			var componentRenderers = component.partsRenderers;
 			int totalParts;
 			int totalParts;
 
 
-			bool componentEnabled = component.enabled;
-			bool checkBox = EditorGUILayout.Toggle("Enable Separator", componentEnabled);
-			if (checkBox != componentEnabled) {
-				component.enabled = checkBox;
-			}
+			using (new SpineInspectorUtility.LabelWidthScope()) {
+				bool componentEnabled = component.enabled;
+				bool checkBox = EditorGUILayout.Toggle("Enable Separator", componentEnabled);
+				if (checkBox != componentEnabled)
+					component.enabled = checkBox;
 
 
-			EditorGUILayout.PropertyField(copyPropertyBlock_);
-			EditorGUILayout.PropertyField(copyMeshRendererFlags_);
+				EditorGUILayout.PropertyField(copyPropertyBlock_);
+				EditorGUILayout.PropertyField(copyMeshRendererFlags_);
+			}
 
 
 			// SkeletonRenderer Box
 			// SkeletonRenderer Box
-			using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) {
+			using (new SpineInspectorUtility.BoxScope(false)) {
 				// Fancy SkeletonRenderer foldout reference field
 				// Fancy SkeletonRenderer foldout reference field
 				{
 				{
 					EditorGUI.indentLevel++;
 					EditorGUI.indentLevel++;
@@ -149,7 +149,7 @@ namespace Spine.Unity.Modules {
 			}
 			}
 
 
 			// Parts renderers
 			// Parts renderers
-			using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) {
+			using (new SpineInspectorUtility.BoxScope(false)) {
 				EditorGUI.indentLevel++;
 				EditorGUI.indentLevel++;
 				EditorGUILayout.PropertyField(this.partsRenderers_, true);
 				EditorGUILayout.PropertyField(this.partsRenderers_, true);
 				EditorGUI.indentLevel--;
 				EditorGUI.indentLevel--;

+ 0 - 17
spine-unity/Assets/spine-unity/Modules/SkeletonUtility Modules/SkeletonUtilityGroundConstraint.cs

@@ -35,16 +35,6 @@ namespace Spine.Unity.Modules {
 	[RequireComponent(typeof(SkeletonUtilityBone)), ExecuteInEditMode]
 	[RequireComponent(typeof(SkeletonUtilityBone)), ExecuteInEditMode]
 	public class SkeletonUtilityGroundConstraint : SkeletonUtilityConstraint {
 	public class SkeletonUtilityGroundConstraint : SkeletonUtilityConstraint {
 
 
-		#if UNITY_4_3
-		public LayerMask groundMask;
-		public bool use2D = false;
-		public bool useRadius = false;
-		public float castRadius = 0.1f;
-		public float castDistance = 5f;
-		public float castOffset = 0;
-		public float groundOffset = 0;
-		public float adjustSpeed = 5;
-		#else
 		[Tooltip("LayerMask for what objects to raycast against")]
 		[Tooltip("LayerMask for what objects to raycast against")]
 		public LayerMask groundMask;
 		public LayerMask groundMask;
 		[Tooltip("The 2D")]
 		[Tooltip("The 2D")]
@@ -61,8 +51,6 @@ namespace Spine.Unity.Modules {
 		public float groundOffset = 0;
 		public float groundOffset = 0;
 		[Tooltip("How fast the target IK position adjusts to the ground.  Use smaller values to prevent snapping")]
 		[Tooltip("How fast the target IK position adjusts to the ground.  Use smaller values to prevent snapping")]
 		public float adjustSpeed = 5;
 		public float adjustSpeed = 5;
-		#endif
-
 
 
 		Vector3 rayOrigin;
 		Vector3 rayOrigin;
 		Vector3 rayDir = new Vector3(0, -1, 0);
 		Vector3 rayDir = new Vector3(0, -1, 0);
@@ -86,12 +74,7 @@ namespace Spine.Unity.Modules {
 				RaycastHit2D hit;
 				RaycastHit2D hit;
 
 
 				if (useRadius) {
 				if (useRadius) {
-					#if UNITY_4_3
-					//NOTE:  Unity 4.3.x does not have CircleCast
-					hit = Physics2D.Raycast(rayOrigin , rayDir, castDistance + groundOffset, groundMask);
-					#else
 					hit = Physics2D.CircleCast(rayOrigin, castRadius, rayDir, castDistance + groundOffset, groundMask);
 					hit = Physics2D.CircleCast(rayOrigin, castRadius, rayDir, castDistance + groundOffset, groundMask);
-					#endif
 				} else {
 				} else {
 					hit = Physics2D.Raycast(rayOrigin, rayDir, castDistance + groundOffset, groundMask);
 					hit = Physics2D.Raycast(rayOrigin, rayDir, castDistance + groundOffset, groundMask);
 				}
 				}

+ 9 - 7
spine-unity/Assets/spine-unity/Modules/SpriteAttacher.cs

@@ -28,14 +28,16 @@
  * POSSIBILITY OF SUCH DAMAGE.
  * POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
+// Contributed by: Mitch Thompson
+
 using UnityEngine;
 using UnityEngine;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using Spine;
 using Spine;
 
 
 namespace Spine.Unity.Modules {
 namespace Spine.Unity.Modules {
 	public class SpriteAttacher : MonoBehaviour {
 	public class SpriteAttacher : MonoBehaviour {
-		const string DefaultPMAShader = "Spine/Skeleton";
-		const string DefaultStraightAlphaShader = "Sprites/Default";
+		public const string DefaultPMAShader = "Spine/Skeleton";
+		public const string DefaultStraightAlphaShader = "Sprites/Default";
 
 
 		#region Inspector
 		#region Inspector
 		public bool attachOnStart = true;
 		public bool attachOnStart = true;
@@ -218,25 +220,25 @@ namespace Spine.Unity.Modules {
 	}
 	}
 
 
 	public static class SpriteAttachmentExtensions {
 	public static class SpriteAttachmentExtensions {
-		public static Attachment AttachUnitySprite (this Skeleton skeleton, string slotName, Sprite sprite, string shaderName = "Spine/Skeleton", bool applyPMA = true) {
+		public static RegionAttachment AttachUnitySprite (this Skeleton skeleton, string slotName, Sprite sprite, string shaderName = SpriteAttacher.DefaultPMAShader, bool applyPMA = true) {
 			return skeleton.AttachUnitySprite(slotName, sprite, Shader.Find(shaderName), applyPMA);
 			return skeleton.AttachUnitySprite(slotName, sprite, Shader.Find(shaderName), applyPMA);
 		}
 		}
 
 
-		public static Attachment AddUnitySprite (this SkeletonData skeletonData, string slotName, Sprite sprite, string skinName = "", string shaderName = "Spine/Skeleton", bool applyPMA = true) {
+		public static RegionAttachment AddUnitySprite (this SkeletonData skeletonData, string slotName, Sprite sprite, string skinName = "", string shaderName = SpriteAttacher.DefaultPMAShader, bool applyPMA = true) {
 			return skeletonData.AddUnitySprite(slotName, sprite, skinName, Shader.Find(shaderName), applyPMA);
 			return skeletonData.AddUnitySprite(slotName, sprite, skinName, Shader.Find(shaderName), applyPMA);
 		}
 		}
 
 
-		public static RegionAttachment ToRegionAttachment (this Sprite sprite, string shaderName = "Spine/Skeleton", bool applyPMA = true) {
+		public static RegionAttachment ToRegionAttachment (this Sprite sprite, string shaderName = SpriteAttacher.DefaultPMAShader, bool applyPMA = true) {
 			return sprite.ToRegionAttachment(Shader.Find(shaderName), applyPMA);
 			return sprite.ToRegionAttachment(Shader.Find(shaderName), applyPMA);
 		}
 		}
 
 
-		public static Attachment AttachUnitySprite (this Skeleton skeleton, string slotName, Sprite sprite, Shader shader, bool applyPMA) {
+		public static RegionAttachment AttachUnitySprite (this Skeleton skeleton, string slotName, Sprite sprite, Shader shader, bool applyPMA) {
 			var att = sprite.ToRegionAttachment(shader, applyPMA);
 			var att = sprite.ToRegionAttachment(shader, applyPMA);
 			skeleton.FindSlot(slotName).Attachment = att;
 			skeleton.FindSlot(slotName).Attachment = att;
 			return att;
 			return att;
 		}
 		}
 
 
-		public static Attachment AddUnitySprite (this SkeletonData skeletonData, string slotName, Sprite sprite, string skinName, Shader shader, bool applyPMA) {
+		public static RegionAttachment AddUnitySprite (this SkeletonData skeletonData, string slotName, Sprite sprite, string skinName, Shader shader, bool applyPMA) {
 			var att = sprite.ToRegionAttachment(shader, applyPMA);
 			var att = sprite.ToRegionAttachment(shader, applyPMA);
 
 
 			var slotIndex = skeletonData.FindSlotIndex(slotName);
 			var slotIndex = skeletonData.FindSlotIndex(slotName);

+ 1 - 1
spine-unity/Assets/spine-unity/Modules/YieldInstructions/WaitForSpineAnimationComplete.cs

@@ -52,7 +52,7 @@ namespace Spine.Unity {
 			SafeSubscribe(trackEntry);
 			SafeSubscribe(trackEntry);
 		}
 		}
 
 
-		void HandleComplete (AnimationState state, int trackIndex, int loopCount) {
+		void HandleComplete (TrackEntry trackEntry) {
 			m_WasFired = true;
 			m_WasFired = true;
 		}
 		}
 
 

+ 4 - 8
spine-unity/Assets/spine-unity/Modules/YieldInstructions/WaitForSpineEvent.cs

@@ -115,20 +115,16 @@ namespace Spine.Unity {
 		#endregion
 		#endregion
 
 
 		#region Event Handlers
 		#region Event Handlers
-		void HandleAnimationStateEventByName (AnimationState state, int trackIndex, Spine.Event e) {
-			if (state != m_AnimationState) return;
-
+		void HandleAnimationStateEventByName (Spine.TrackEntry trackEntry, Spine.Event e) {
 			m_WasFired |= (e.Data.Name == m_EventName);			// Check event name string match.
 			m_WasFired |= (e.Data.Name == m_EventName);			// Check event name string match.
 			if (m_WasFired && m_unsubscribeAfterFiring)
 			if (m_WasFired && m_unsubscribeAfterFiring)
-				state.Event -= HandleAnimationStateEventByName;	// Unsubscribe after correct event fires.
+				m_AnimationState.Event -= HandleAnimationStateEventByName;	// Unsubscribe after correct event fires.
 		}
 		}
 
 
-		void HandleAnimationStateEvent (AnimationState state, int trackIndex, Spine.Event e) {
-			if (state != m_AnimationState) return;
-
+		void HandleAnimationStateEvent (Spine.TrackEntry trackEntry, Spine.Event e) {
 			m_WasFired |= (e.Data == m_TargetEvent);			// Check event data reference match.
 			m_WasFired |= (e.Data == m_TargetEvent);			// Check event data reference match.
 			if (m_WasFired && m_unsubscribeAfterFiring)
 			if (m_WasFired && m_unsubscribeAfterFiring)
-				state.Event -= HandleAnimationStateEvent; 		// Usubscribe after correct event fires.
+				m_AnimationState.Event -= HandleAnimationStateEvent; 		// Usubscribe after correct event fires.
 		}
 		}
 		#endregion
 		#endregion
 
 

+ 28 - 27
spine-unity/Assets/spine-unity/SkeletonAnimation.cs

@@ -36,51 +36,48 @@ namespace Spine.Unity {
 	[ExecuteInEditMode]
 	[ExecuteInEditMode]
 	[AddComponentMenu("Spine/SkeletonAnimation")]
 	[AddComponentMenu("Spine/SkeletonAnimation")]
 	[HelpURL("http://esotericsoftware.com/spine-unity-documentation#Controlling-Animation")]
 	[HelpURL("http://esotericsoftware.com/spine-unity-documentation#Controlling-Animation")]
-	public class SkeletonAnimation : SkeletonRenderer, ISkeletonAnimation, Spine.Unity.IAnimationStateComponent {
+	public class SkeletonAnimation : SkeletonRenderer, ISkeletonAnimation, IAnimationStateComponent {
 
 
+		#region IAnimationStateComponent
 		/// <summary>
 		/// <summary>
 		/// This is the Spine.AnimationState object of this SkeletonAnimation. You can control animations through it. 
 		/// This is the Spine.AnimationState object of this SkeletonAnimation. You can control animations through it. 
 		/// Note that this object, like .skeleton, is not guaranteed to exist in Awake. Do all accesses and caching to it in Start</summary>
 		/// Note that this object, like .skeleton, is not guaranteed to exist in Awake. Do all accesses and caching to it in Start</summary>
 		public Spine.AnimationState state;
 		public Spine.AnimationState state;
+		/// <summary>
+		/// This is the Spine.AnimationState object of this SkeletonAnimation. You can control animations through it. 
+		/// Note that this object, like .skeleton, is not guaranteed to exist in Awake. Do all accesses and caching to it in Start</summary>
 		public Spine.AnimationState AnimationState { get { return this.state; } }
 		public Spine.AnimationState AnimationState { get { return this.state; } }
+		#endregion
+
+		#region Bone Callbacks ISkeletonAnimation
+		protected event UpdateBonesDelegate _UpdateLocal;
+		protected event UpdateBonesDelegate _UpdateWorld;
+		protected event UpdateBonesDelegate _UpdateComplete;
 
 
 		/// <summary>
 		/// <summary>
 		/// Occurs after the animations are applied and before world space values are resolved.
 		/// Occurs after the animations are applied and before world space values are resolved.
 		/// Use this callback when you want to set bone local values.
 		/// Use this callback when you want to set bone local values.
 		/// </summary>
 		/// </summary>
-		public event UpdateBonesDelegate UpdateLocal {
-			add { _UpdateLocal += value; }
-			remove { _UpdateLocal -= value; }
-		}
+		public event UpdateBonesDelegate UpdateLocal { add { _UpdateLocal += value; } remove { _UpdateLocal -= value; } }
 
 
 		/// <summary>
 		/// <summary>
 		/// Occurs after the Skeleton's bone world space values are resolved (including all constraints).
 		/// Occurs after the Skeleton's bone world space values are resolved (including all constraints).
 		/// Using this callback will cause the world space values to be solved an extra time.
 		/// Using this callback will cause the world space values to be solved an extra time.
-		/// Use this callback if want to use bone world space values, and also set bone local values.
-		/// </summary>
-		public event UpdateBonesDelegate UpdateWorld {
-			add { _UpdateWorld += value; }
-			remove { _UpdateWorld -= value; }
-		}
+		/// Use this callback if want to use bone world space values, and also set bone local values.</summary>
+		public event UpdateBonesDelegate UpdateWorld { add { _UpdateWorld += value; } remove { _UpdateWorld -= value; } }
 
 
 		/// <summary>
 		/// <summary>
 		/// Occurs after the Skeleton's bone world space values are resolved (including all constraints).
 		/// Occurs after the Skeleton's bone world space values are resolved (including all constraints).
 		/// Use this callback if you want to use bone world space values, but don't intend to modify bone local values.
 		/// Use this callback if you want to use bone world space values, but don't intend to modify bone local values.
-		/// This callback can also be used when setting world position and the bone matrix.
-		/// </summary>
-		public event UpdateBonesDelegate UpdateComplete {
-			add { _UpdateComplete += value; }
-			remove { _UpdateComplete -= value; }
-		}
-
-		protected event UpdateBonesDelegate _UpdateLocal;
-		protected event UpdateBonesDelegate _UpdateWorld;
-		protected event UpdateBonesDelegate _UpdateComplete;
+		/// This callback can also be used when setting world position and the bone matrix.</summary>
+		public event UpdateBonesDelegate UpdateComplete { add { _UpdateComplete += value; } remove { _UpdateComplete -= value; } }
+		#endregion
 
 
+		#region Serialized state and Beginner API
 		[SerializeField]
 		[SerializeField]
 		[SpineAnimation]
 		[SpineAnimation]
-		private String _animationName;
-		public String AnimationName {
+		private string _animationName;
+		public string AnimationName {
 			get {
 			get {
 				if (!valid) {
 				if (!valid) {
 					Debug.LogWarning("You tried access AnimationName but the SkeletonAnimation was not valid. Try checking your Skeleton Data for errors.");
 					Debug.LogWarning("You tried access AnimationName but the SkeletonAnimation was not valid. Try checking your Skeleton Data for errors.");
@@ -107,15 +104,14 @@ namespace Spine.Unity {
 			}
 			}
 		}
 		}
 
 
-		/// <summary>Whether or not an animation should loop. This only applies to the initial animation specified in the inspector, or any subsequent Animations played through .AnimationName. Animations set through state.SetAnimation are unaffected.</summary>
-		[Tooltip("Whether or not an animation should loop. This only applies to the initial animation specified in the inspector, or any subsequent Animations played through .AnimationName. Animations set through state.SetAnimation are unaffected.")]
+		/// <summary>Whether or not <see cref="AnimationName"/> should loop. This only applies to the initial animation specified in the inspector, or any subsequent Animations played through .AnimationName. Animations set through state.SetAnimation are unaffected.</summary>
 		public bool loop;
 		public bool loop;
 
 
 		/// <summary>
 		/// <summary>
 		/// The rate at which animations progress over time. 1 means 100%. 0.5 means 50%.</summary>
 		/// The rate at which animations progress over time. 1 means 100%. 0.5 means 50%.</summary>
 		/// <remarks>AnimationState and TrackEntry also have their own timeScale. These are combined multiplicatively.</remarks>
 		/// <remarks>AnimationState and TrackEntry also have their own timeScale. These are combined multiplicatively.</remarks>
-		[Tooltip("The rate at which animations progress over time. 1 means 100%. 0.5 means 50%.")]
 		public float timeScale = 1;
 		public float timeScale = 1;
+		#endregion
 
 
 		#region Runtime Instantiation
 		#region Runtime Instantiation
 		/// <summary>Adds and prepares a SkeletonAnimation component to a GameObject at runtime.</summary>
 		/// <summary>Adds and prepares a SkeletonAnimation component to a GameObject at runtime.</summary>
@@ -131,6 +127,11 @@ namespace Spine.Unity {
 		}
 		}
 		#endregion
 		#endregion
 
 
+		protected override void ClearState () {
+			base.ClearState();
+			state.ClearTracks();
+		}
+
 		public override void Initialize (bool overwrite) {
 		public override void Initialize (bool overwrite) {
 			if (valid && !overwrite)
 			if (valid && !overwrite)
 				return;
 				return;
@@ -150,7 +151,7 @@ namespace Spine.Unity {
 					// Assume SkeletonAnimation is valid for skeletonData and skeleton. Checked above.
 					// Assume SkeletonAnimation is valid for skeletonData and skeleton. Checked above.
 					var animationObject = skeletonDataAsset.GetSkeletonData(false).FindAnimation(_animationName);
 					var animationObject = skeletonDataAsset.GetSkeletonData(false).FindAnimation(_animationName);
 					if (animationObject != null)
 					if (animationObject != null)
-						animationObject.Apply(skeleton, 0f, 0f, false, null);
+						animationObject.Apply(skeleton, 0f, 0f, false, null, 1f, true, false);
 				}
 				}
 				Update(0);
 				Update(0);
 			}
 			}

+ 67 - 174
spine-unity/Assets/spine-unity/SkeletonAnimator.cs

@@ -30,8 +30,6 @@
 
 
 // Contributed by: Mitch Thompson
 // Contributed by: Mitch Thompson
 
 
-//#define USE_SPINE_EVENTS // Uncomment this define to use C# events to handle Spine events. (Does not disable Unity AnimationClip Events)
-
 using UnityEngine;
 using UnityEngine;
 using System.Collections.Generic;
 using System.Collections.Generic;
 
 
@@ -42,251 +40,146 @@ namespace Spine.Unity {
 		public enum MixMode { AlwaysMix, MixNext, SpineStyle }
 		public enum MixMode { AlwaysMix, MixNext, SpineStyle }
 		public MixMode[] layerMixModes = new MixMode[0];
 		public MixMode[] layerMixModes = new MixMode[0];
 
 
+		#region Bone Callbacks (ISkeletonAnimation)
+		protected event UpdateBonesDelegate _UpdateLocal;
+		protected event UpdateBonesDelegate _UpdateWorld;
+		protected event UpdateBonesDelegate _UpdateComplete;
+
 		/// <summary>
 		/// <summary>
 		/// Occurs after the animations are applied and before world space values are resolved.
 		/// Occurs after the animations are applied and before world space values are resolved.
-		/// Use this callback when you want to set bone local values.
-		/// </summary>
-		public event UpdateBonesDelegate UpdateLocal {
-			add { _UpdateLocal += value; }
-			remove { _UpdateLocal -= value; }
-		}
+		/// Use this callback when you want to set bone local values.</summary>
+		public event UpdateBonesDelegate UpdateLocal { add { _UpdateLocal += value; } remove { _UpdateLocal -= value; } }
 
 
 		/// <summary>
 		/// <summary>
 		/// Occurs after the Skeleton's bone world space values are resolved (including all constraints).
 		/// Occurs after the Skeleton's bone world space values are resolved (including all constraints).
 		/// Using this callback will cause the world space values to be solved an extra time.
 		/// Using this callback will cause the world space values to be solved an extra time.
-		/// Use this callback if want to use bone world space values, and also set bone local values.
-		/// </summary>
-		public event UpdateBonesDelegate UpdateWorld {
-			add { _UpdateWorld += value; }
-			remove { _UpdateWorld -= value; }
-		}
+		/// Use this callback if want to use bone world space values, and also set bone local values.</summary>
+		public event UpdateBonesDelegate UpdateWorld { add { _UpdateWorld += value; } remove { _UpdateWorld -= value; } }
 
 
 		/// <summary>
 		/// <summary>
 		/// Occurs after the Skeleton's bone world space values are resolved (including all constraints).
 		/// Occurs after the Skeleton's bone world space values are resolved (including all constraints).
 		/// Use this callback if you want to use bone world space values, but don't intend to modify bone local values.
 		/// Use this callback if you want to use bone world space values, but don't intend to modify bone local values.
-		/// This callback can also be used when setting world position and the bone matrix.
-		/// </summary>
-		public event UpdateBonesDelegate UpdateComplete {
-			add { _UpdateComplete += value; }
-			remove { _UpdateComplete -= value; }
-		}
-
-		protected event UpdateBonesDelegate _UpdateLocal;
-		protected event UpdateBonesDelegate _UpdateWorld;
-		protected event UpdateBonesDelegate _UpdateComplete;
+		/// This callback can also be used when setting world position and the bone matrix.</summary>
+		public event UpdateBonesDelegate UpdateComplete { add { _UpdateComplete += value; } remove { _UpdateComplete -= value; } }
+		#endregion
 
 
 		readonly Dictionary<int, Spine.Animation> animationTable = new Dictionary<int, Spine.Animation>();
 		readonly Dictionary<int, Spine.Animation> animationTable = new Dictionary<int, Spine.Animation>();
 		readonly Dictionary<AnimationClip, int> clipNameHashCodeTable = new Dictionary<AnimationClip, int>();
 		readonly Dictionary<AnimationClip, int> clipNameHashCodeTable = new Dictionary<AnimationClip, int>();
 		Animator animator;
 		Animator animator;
-		float lastTime;
-
-		#if USE_SPINE_EVENTS
-		public delegate void SkeletonAnimatorEventDelegate (Spine.Event firedEvent, float weight);
-		public event SkeletonAnimatorEventDelegate AnimationEvent;
-		public readonly ExposedList<Spine.Event> events = new ExposedList<Spine.Event>();
-		#else
-		public readonly ExposedList<Spine.Event> events = null;
-		#endif
 
 
 		public override void Initialize (bool overwrite) {
 		public override void Initialize (bool overwrite) {
-			if (valid && !overwrite)
-				return;
-
+			if (valid && !overwrite) return;
 			base.Initialize(overwrite);
 			base.Initialize(overwrite);
-
-			if (!valid)
-				return;
+			if (!valid) return;
 
 
 			animationTable.Clear();
 			animationTable.Clear();
 			clipNameHashCodeTable.Clear();
 			clipNameHashCodeTable.Clear();
-
+			animator = GetComponent<Animator>();
 			var data = skeletonDataAsset.GetSkeletonData(true);
 			var data = skeletonDataAsset.GetSkeletonData(true);
-
-			foreach (var a in data.Animations) {
+			foreach (var a in data.Animations)
 				animationTable.Add(a.Name.GetHashCode(), a);
 				animationTable.Add(a.Name.GetHashCode(), a);
-			}
-
-			animator = GetComponent<Animator>();
-
-			lastTime = Time.time;
 		}
 		}
 
 
 		void Update () {
 		void Update () {
-			if (!valid)
-				return;
+			if (!valid) return;
 
 
-			if (layerMixModes.Length != animator.layerCount) {
+			if (layerMixModes.Length < animator.layerCount)
 				System.Array.Resize<MixMode>(ref layerMixModes, animator.layerCount);
 				System.Array.Resize<MixMode>(ref layerMixModes, animator.layerCount);
-			}
-			float deltaTime = Time.time - lastTime;
-
-			skeleton.Update(Time.deltaTime);
+			
+			//skeleton.Update(Time.deltaTime); // Doesn't actually do anything, currently. (Spine 3.5).
 
 
-			//apply
-			int layerCount = animator.layerCount;
-
-			for (int i = 0; i < layerCount; i++) {
-
-				float layerWeight = animator.GetLayerWeight(i);
-				if (i == 0)
-					layerWeight = 1;
-
-				var stateInfo = animator.GetCurrentAnimatorStateInfo(i);
-				var nextStateInfo = animator.GetNextAnimatorStateInfo(i);
+			// Apply
+			for (int layer = 0, n = animator.layerCount; layer < n; layer++) {
+				float layerWeight = (layer == 0) ? 1 : animator.GetLayerWeight(layer);
+				AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(layer);
+				AnimatorStateInfo nextStateInfo = animator.GetNextAnimatorStateInfo(layer);
 
 
 				#if UNITY_5
 				#if UNITY_5
-				var clipInfo = animator.GetCurrentAnimatorClipInfo(i);
-				var nextClipInfo = animator.GetNextAnimatorClipInfo(i);
+				bool hasNext = nextStateInfo.fullPathHash != 0;
+				AnimatorClipInfo[] clipInfo = animator.GetCurrentAnimatorClipInfo(layer);
+				AnimatorClipInfo[] nextClipInfo = animator.GetNextAnimatorClipInfo(layer);
 				#else
 				#else
+				bool hasNext = nextStateInfo.nameHash != 0;
 				var clipInfo = animator.GetCurrentAnimationClipState(i);
 				var clipInfo = animator.GetCurrentAnimationClipState(i);
 				var nextClipInfo = animator.GetNextAnimationClipState(i);
 				var nextClipInfo = animator.GetNextAnimationClipState(i);
 				#endif
 				#endif
-				MixMode mode = layerMixModes[i];
 
 
+				MixMode mode = layerMixModes[layer];
 				if (mode == MixMode.AlwaysMix) {
 				if (mode == MixMode.AlwaysMix) {
-					//always use Mix instead of Applying the first non-zero weighted clip
+					// Always use Mix instead of Applying the first non-zero weighted clip.
 					for (int c = 0; c < clipInfo.Length; c++) {
 					for (int c = 0; c < clipInfo.Length; c++) {
-						var info = clipInfo[c];
-						float weight = info.weight * layerWeight;
-						if (weight == 0)
-							continue;
-
-						float time = stateInfo.normalizedTime * info.clip.length;
-						animationTable[GetAnimationClipNameHashCode(info.clip)].Mix(skeleton, Mathf.Max(0, time - deltaTime), time, stateInfo.loop, events, weight);
-						#if USE_SPINE_EVENTS
-						FireEvents(events, weight, this.AnimationEvent);
-						#endif
+						var info = clipInfo[c];	float weight = info.weight * layerWeight; if (weight == 0) continue;
+						animationTable[NameHashCode(info.clip)].Apply(skeleton, 0, AnimationTime(stateInfo.normalizedTime, info.clip.length), stateInfo.loop, null, weight, false, false);
 					}
 					}
-					#if UNITY_5
-					if (nextStateInfo.fullPathHash != 0) {
-					#else
-					if (nextStateInfo.nameHash != 0) {
-					#endif
+					if (hasNext) {
 						for (int c = 0; c < nextClipInfo.Length; c++) {
 						for (int c = 0; c < nextClipInfo.Length; c++) {
-							var info = nextClipInfo[c];
-							float weight = info.weight * layerWeight;
-							if (weight == 0)
-								continue;
-
-							float time = nextStateInfo.normalizedTime * info.clip.length;
-							animationTable[GetAnimationClipNameHashCode(info.clip)].Mix(skeleton, Mathf.Max(0, time - deltaTime), time, nextStateInfo.loop, events, weight);
-							#if USE_SPINE_EVENTS
-							FireEvents(events, weight, this.AnimationEvent);
-							#endif
+							var info = nextClipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue;
+							animationTable[NameHashCode(info.clip)].Apply(skeleton, 0, nextStateInfo.normalizedTime * info.clip.length, nextStateInfo.loop, null, weight, false, false);
 						}
 						}
 					}
 					}
-				} else if (mode >= MixMode.MixNext) {
-					//apply first non-zero weighted clip
+				} else { // case MixNext || SpineStyle
+					// Apply first non-zero weighted clip
 					int c = 0;
 					int c = 0;
-
 					for (; c < clipInfo.Length; c++) {
 					for (; c < clipInfo.Length; c++) {
-						var info = clipInfo[c];
-						float weight = info.weight * layerWeight;
-						if (weight == 0)
-							continue;
-
-						float time = stateInfo.normalizedTime * info.clip.length;
-						animationTable[GetAnimationClipNameHashCode(info.clip)].Apply(skeleton, Mathf.Max(0, time - deltaTime), time, stateInfo.loop, events);
-						#if USE_SPINE_EVENTS
-						FireEvents(events, weight, this.AnimationEvent);
-						#endif
+						var info = clipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue;
+						animationTable[NameHashCode(info.clip)].Apply(skeleton, 0, AnimationTime(stateInfo.normalizedTime, info.clip.length), stateInfo.loop, null, 1f, false, false);
 						break;
 						break;
 					}
 					}
-
-					//mix the rest
+					// Mix the rest
 					for (; c < clipInfo.Length; c++) {
 					for (; c < clipInfo.Length; c++) {
-						var info = clipInfo[c];
-						float weight = info.weight * layerWeight;
-						if (weight == 0)
-							continue;
-
-						float time = stateInfo.normalizedTime * info.clip.length;
-						animationTable[GetAnimationClipNameHashCode(info.clip)].Mix(skeleton, Mathf.Max(0, time - deltaTime), time, stateInfo.loop, events, weight);
-						#if USE_SPINE_EVENTS
-						FireEvents(events, weight, this.AnimationEvent);
-						#endif
+						var info = clipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue;
+						animationTable[NameHashCode(info.clip)].Apply(skeleton, 0, AnimationTime(stateInfo.normalizedTime, info.clip.length), stateInfo.loop, null, weight, false, false);
 					}
 					}
 
 
 					c = 0;
 					c = 0;
-					#if UNITY_5
-					if (nextStateInfo.fullPathHash != 0) {
-					#else
-					if (nextStateInfo.nameHash != 0) {
-					#endif
-						//apply next clip directly instead of mixing (ie:  no crossfade, ignores mecanim transition weights)
+					if (hasNext) {
+						// Apply next clip directly instead of mixing (ie: no crossfade, ignores mecanim transition weights)
 						if (mode == MixMode.SpineStyle) {
 						if (mode == MixMode.SpineStyle) {
 							for (; c < nextClipInfo.Length; c++) {
 							for (; c < nextClipInfo.Length; c++) {
-								var info = nextClipInfo[c];
-								float weight = info.weight * layerWeight;
-								if (weight == 0)
-									continue;
-
-								float time = nextStateInfo.normalizedTime * info.clip.length;
-								animationTable[GetAnimationClipNameHashCode(info.clip)].Apply(skeleton, Mathf.Max(0, time - deltaTime), time, nextStateInfo.loop, events);
-								#if USE_SPINE_EVENTS
-								FireEvents(events, weight, this.AnimationEvent);
-								#endif
+								var info = nextClipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue;
+								animationTable[NameHashCode(info.clip)].Apply(skeleton, 0, nextStateInfo.normalizedTime * info.clip.length, nextStateInfo.loop, null, 1f, false, false);
 								break;
 								break;
 							}
 							}
 						}
 						}
-
-						//mix the rest
+						// Mix the rest
 						for (; c < nextClipInfo.Length; c++) {
 						for (; c < nextClipInfo.Length; c++) {
-							var info = nextClipInfo[c];
-							float weight = info.weight * layerWeight;
-							if (weight == 0)
-								continue;
-
-							float time = nextStateInfo.normalizedTime * info.clip.length;
-							animationTable[GetAnimationClipNameHashCode(info.clip)].Mix(skeleton, Mathf.Max(0, time - deltaTime), time, nextStateInfo.loop, events, weight);
-							#if USE_SPINE_EVENTS
-							FireEvents(events, weight, this.AnimationEvent);
-							#endif
+							var info = nextClipInfo[c];	float weight = info.weight * layerWeight; if (weight == 0) continue;
+							animationTable[NameHashCode(info.clip)].Apply(skeleton, 0, nextStateInfo.normalizedTime * info.clip.length, nextStateInfo.loop, null, weight, false, false);
 						}
 						}
 					}
 					}
 				}
 				}
 			}
 			}
 
 
-			if (_UpdateLocal != null)
-				_UpdateLocal(this);
+			// UpdateWorldTransform and Bone Callbacks
+			{
+				if (_UpdateLocal != null)
+					_UpdateLocal(this);
 
 
-			skeleton.UpdateWorldTransform();
-
-			if (_UpdateWorld != null) {
-				_UpdateWorld(this);
 				skeleton.UpdateWorldTransform();
 				skeleton.UpdateWorldTransform();
-			}
 
 
-			if (_UpdateComplete != null) {
-				_UpdateComplete(this);
+				if (_UpdateWorld != null) {
+					_UpdateWorld(this);
+					skeleton.UpdateWorldTransform();
+				}
+
+				if (_UpdateComplete != null)
+					_UpdateComplete(this);	
 			}
 			}
+		}
 
 
-			lastTime = Time.time;
+		static float AnimationTime (float normalizedTime, float clipLength) {
+			float time = normalizedTime * clipLength;
+			const float EndSnapEpsilon = 1f/30f; // Workaround for end-duration keys not being applied.
+			return (clipLength - time < EndSnapEpsilon) ? clipLength : time; // return a time snapped to clipLength;
 		}
 		}
 
 
-		private int GetAnimationClipNameHashCode (AnimationClip clip) {
+		int NameHashCode (AnimationClip clip) {
 			int clipNameHashCode;
 			int clipNameHashCode;
 			if (!clipNameHashCodeTable.TryGetValue(clip, out clipNameHashCode)) {
 			if (!clipNameHashCodeTable.TryGetValue(clip, out clipNameHashCode)) {
 				clipNameHashCode = clip.name.GetHashCode();
 				clipNameHashCode = clip.name.GetHashCode();
 				clipNameHashCodeTable.Add(clip, clipNameHashCode);
 				clipNameHashCodeTable.Add(clip, clipNameHashCode);
 			}
 			}
-
 			return clipNameHashCode;
 			return clipNameHashCode;
 		}
 		}
-
-		#if USE_SPINE_EVENTS
-		static void FireEvents (ExposedList<Spine.Event> eventList, float weight, SkeletonAnimatorEventDelegate callback) {
-			int eventsCount = eventList.Count;
-			if (eventsCount > 0) {
-				var eventListItems = eventList.Items;
-				for (int i = 0; i < eventsCount; i++) {
-					if (callback != null)
-						callback(eventListItems[i], weight);
-				}
-
-				eventList.Clear(false);
-			}
-		}
-		#endif
 	}
 	}
 }
 }

+ 78 - 72
spine-unity/Assets/spine-unity/SkeletonExtensions.cs

@@ -36,9 +36,8 @@ using Spine;
 namespace Spine.Unity {
 namespace Spine.Unity {
 	public static class SkeletonExtensions {
 	public static class SkeletonExtensions {
 
 
-		const float ByteToFloat = 1f / 255f;
-
 		#region Colors
 		#region Colors
+		const float ByteToFloat = 1f / 255f;
 		public static Color GetColor (this Skeleton s) { return new Color(s.r, s.g, s.b, s.a); }
 		public static Color GetColor (this Skeleton s) { return new Color(s.r, s.g, s.b, s.a); }
 		public static Color GetColor (this RegionAttachment a) { return new Color(a.r, a.g, a.b, a.a); }
 		public static Color GetColor (this RegionAttachment a) { return new Color(a.r, a.g, a.b, a.a); }
 		public static Color GetColor (this MeshAttachment a) { return new Color(a.r, a.g, a.b, a.a); }
 		public static Color GetColor (this MeshAttachment a) { return new Color(a.r, a.g, a.b, a.a); }
@@ -111,6 +110,10 @@ namespace Spine.Unity {
 			bone.Y = position.y;
 			bone.Y = position.y;
 		}
 		}
 
 
+		public static Vector2 GetLocalPosition (this Bone bone) {
+			return new Vector2(bone.x, bone.y);
+		}
+
 		public static Vector2 GetSkeletonSpacePosition (this Bone bone) {
 		public static Vector2 GetSkeletonSpacePosition (this Bone bone) {
 			return new Vector2(bone.worldX, bone.worldY);
 			return new Vector2(bone.worldX, bone.worldY);
 		}
 		}
@@ -128,24 +131,65 @@ namespace Spine.Unity {
 		}
 		}
 		#endregion
 		#endregion
 
 
+		#region Attachments
+		public static Material GetMaterial (this Attachment a) {
+			var regionAttachment = a as RegionAttachment;
+			if (regionAttachment != null)
+				return (Material)((AtlasRegion)regionAttachment.RendererObject).page.rendererObject;
+
+			var meshAttachment = a as MeshAttachment;
+			if (meshAttachment != null)
+				return (Material)((AtlasRegion)meshAttachment.RendererObject).page.rendererObject;			
+
+			return null;
+		}
+
+		/// <summary>Calculates world vertices and fills a Vector2 buffer.</summary>
+		/// <param name="a">The VertexAttachment</param>
+		/// <param name="slot">Slot where </param>
+		/// <param name="buffer">Correctly-sized buffer. Use attachment's .WorldVerticesLength to get the correct size. If null, a new Vector2[] of the correct size will be allocated.</param>
+		public static Vector2[] GetWorldVertices (this VertexAttachment a, Slot slot, Vector2[] buffer) {
+			int worldVertsLength = a.worldVerticesLength;
+			int bufferTargetSize = worldVertsLength >> 1;
+			buffer = buffer ?? new Vector2[bufferTargetSize];
+			if (buffer.Length < bufferTargetSize) throw new System.ArgumentException(string.Format("Vector2 buffer too small. {0} requires an array of size {1}. Use the attachment's .WorldVerticesLength to get the correct size.", a.Name, worldVertsLength), "buffer");
+			
+			var floats = new float[worldVertsLength];
+			a.ComputeWorldVertices(slot, floats);
+
+			for (int i = 0, n = worldVertsLength >> 1; i < n; i++) {
+				int j = i * 2;
+				buffer[i] = new Vector2(floats[j], floats[j + 1]);
+			}
+
+			return buffer;
+		}
+		#endregion
 	}
 	}
 }
 }
 
 
 namespace Spine {
 namespace Spine {
 	public static class SkeletonExtensions {
 	public static class SkeletonExtensions {
+		public static bool IsWeighted (this VertexAttachment va) {
+			return va.bones != null && va.bones.Length > 0;
+		}
+
+		#region Transform Modes
+		public static bool InheritsRotation (this TransformMode mode) {
+			const int RotationBit = 0;
+			return ((int)mode & (1U << RotationBit)) == 0;
+		}
+
+		public static bool InheritsScale (this TransformMode mode) {
+			const int ScaleBit = 1;
+			return ((int)mode & (1U << ScaleBit)) == 0;
+		}
+		#endregion
+
 		#region Posing
 		#region Posing
-		/// <summary>
-		/// Shortcut for posing a skeleton at a specific time. Time is in seconds. (frameNumber / 30f) will give you seconds.
-		/// If you need to do this often, you should get the Animation object yourself using skeleton.data.FindAnimation. and call Apply on that.</summary>
-		/// <param name = "skeleton">The skeleton to pose.</param>
-		/// <param name="animationName">The name of the animation to use.</param>
-		/// <param name = "time">The time of the pose within the animation.</param>
-		/// <param name = "loop">Wraps the time around if it is longer than the duration of the animation.</param>
-		public static void PoseWithAnimation (this Skeleton skeleton, string animationName, float time, bool loop) {
-			// Fail loud when skeleton.data is null.
-			Spine.Animation animation = skeleton.data.FindAnimation(animationName);
-			if (animation == null) return;
-			animation.Apply(skeleton, 0, time, loop, null);
+		[System.Obsolete("Old Animation.Apply method signature. Please use the 8 parameter signature. See summary to learn about the extra arguments.")]
+		public static void Apply (this Spine.Animation animation, Skeleton skeleton, float lastTime, float time, bool loop, ExposedList<Event> events) {
+			animation.Apply(skeleton, lastTime, time, loop, events, 1f, false, false);
 		}
 		}
 
 
 		/// <summary>Resets the DrawOrder to the Setup Pose's draw order</summary>
 		/// <summary>Resets the DrawOrder to the Setup Pose's draw order</summary>
@@ -157,7 +201,6 @@ namespace Spine {
 			drawOrder.Clear(false);
 			drawOrder.Clear(false);
 			drawOrder.GrowIfNeeded(n);
 			drawOrder.GrowIfNeeded(n);
 			System.Array.Copy(slotsItems, drawOrder.Items, n);
 			System.Array.Copy(slotsItems, drawOrder.Items, n);
-			drawOrder.Count = n;
 		}
 		}
 
 
 		/// <summary>Resets the color of a slot to Setup Pose value.</summary>
 		/// <summary>Resets the color of a slot to Setup Pose value.</summary>
@@ -177,69 +220,32 @@ namespace Spine {
 		/// <summary>Resets the attachment of slot at a given slotIndex to setup pose. This is faster than Slot.SetAttachmentToSetupPose.</summary>
 		/// <summary>Resets the attachment of slot at a given slotIndex to setup pose. This is faster than Slot.SetAttachmentToSetupPose.</summary>
 		public static void SetSlotAttachmentToSetupPose (this Skeleton skeleton, int slotIndex) {
 		public static void SetSlotAttachmentToSetupPose (this Skeleton skeleton, int slotIndex) {
 			var slot = skeleton.slots.Items[slotIndex];
 			var slot = skeleton.slots.Items[slotIndex];
-			// Based on Slot.SetToSetupPose
-			if (slot.data.attachmentName == null)
+			var attachmentName = slot.data.attachmentName;
+			if (string.IsNullOrEmpty(attachmentName)) {
 				slot.Attachment = null;
 				slot.Attachment = null;
-			else {
-				slot.attachment = null;
-				slot.Attachment = skeleton.GetAttachment(slotIndex, slot.data.attachmentName);
+			} else {
+				var attachment = skeleton.GetAttachment(slotIndex, attachmentName);
+				slot.Attachment = attachment;
 			}
 			}
 		}
 		}
 
 
-		/// <summary>Resets Skeleton parts to Setup Pose according to a Spine.Animation's keyed items.</summary>
-		public static void SetKeyedItemsToSetupPose (this Animation animation, Skeleton skeleton) {
-			var timelinesItems = animation.timelines.Items;
-			for (int i = 0, n = timelinesItems.Length; i < n; i++)
-				timelinesItems[i].SetToSetupPose(skeleton);
+		/// <summary>
+		/// Shortcut for posing a skeleton at a specific time. Time is in seconds. (frameNumber / 30f) will give you seconds.
+		/// If you need to do this often, you should get the Animation object yourself using skeleton.data.FindAnimation. and call Apply on that.</summary>
+		/// <param name = "skeleton">The skeleton to pose.</param>
+		/// <param name="animationName">The name of the animation to use.</param>
+		/// <param name = "time">The time of the pose within the animation.</param>
+		/// <param name = "loop">Wraps the time around if it is longer than the duration of the animation.</param>
+		public static void PoseWithAnimation (this Skeleton skeleton, string animationName, float time, bool loop) {
+			// Fail loud when skeleton.data is null.
+			Spine.Animation animation = skeleton.data.FindAnimation(animationName);
+			if (animation == null) return;
+			animation.Apply(skeleton, 0, time, loop, null, 1f, false, false);
 		}
 		}
 
 
-		public static void SetToSetupPose (this Timeline timeline, Skeleton skeleton) {
-			if (timeline != null) {
-				// sorted according to assumed likelihood here
-
-				// Bone
-				if (timeline is RotateTimeline) {
-					var bone = skeleton.bones.Items[((RotateTimeline)timeline).boneIndex];
-					bone.rotation = bone.data.rotation;
-				} else if (timeline is TranslateTimeline) {
-					var bone = skeleton.bones.Items[((TranslateTimeline)timeline).boneIndex];
-					bone.x = bone.data.x;
-					bone.y = bone.data.y;
-				} else if (timeline is ScaleTimeline) {
-					var bone = skeleton.bones.Items[((ScaleTimeline)timeline).boneIndex];
-					bone.scaleX = bone.data.scaleX;
-					bone.scaleY = bone.data.scaleY;
-
-
-				// Attachment
-				} else if (timeline is DeformTimeline) {
-					var slot = skeleton.slots.Items[((DeformTimeline)timeline).slotIndex];
-					slot.attachmentVertices.Clear(false);
-
-				// Slot
-				} else if (timeline is AttachmentTimeline) {
-					skeleton.SetSlotAttachmentToSetupPose(((AttachmentTimeline)timeline).slotIndex);
-
-				} else if (timeline is ColorTimeline) {
-					skeleton.slots.Items[((ColorTimeline)timeline).slotIndex].SetColorToSetupPose();
-
-
-				// Constraint
-				} else if (timeline is IkConstraintTimeline) {
-					var ikTimeline = (IkConstraintTimeline)timeline;
-					var ik = skeleton.ikConstraints.Items[ikTimeline.ikConstraintIndex];
-					var data = ik.data;
-					ik.bendDirection = data.bendDirection;
-					ik.mix = data.mix;
-
-				// Skeleton
-				} else if (timeline is DrawOrderTimeline) {
-					skeleton.SetDrawOrderToSetupPose();
-
-				}
-
-			}
-
+		/// <summary>Resets Skeleton parts to Setup Pose according to a Spine.Animation's keyed items.</summary>
+		public static void SetKeyedItemsToSetupPose (this Animation animation, Skeleton skeleton) {
+			animation.Apply(skeleton, 0, 0, false, null, 0, true, true);
 		}
 		}
 		#endregion
 		#endregion
 	}
 	}

+ 14 - 42
spine-unity/Assets/spine-unity/SkeletonRenderer.cs

@@ -34,7 +34,6 @@
 #define SPINE_OPTIONAL_SOLVETANGENTS
 #define SPINE_OPTIONAL_SOLVETANGENTS
 
 
 //#define SPINE_OPTIONAL_FRONTFACING
 //#define SPINE_OPTIONAL_FRONTFACING
-//#define SPINE_OPTIONAL_SUBMESHRENDERER // Deprecated
 
 
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -51,8 +50,8 @@ namespace Spine.Unity {
 		public SkeletonRendererDelegate OnRebuild;
 		public SkeletonRendererDelegate OnRebuild;
 
 
 		public SkeletonDataAsset skeletonDataAsset;
 		public SkeletonDataAsset skeletonDataAsset;
-		public SkeletonDataAsset SkeletonDataAsset { get { return skeletonDataAsset; } }
-		public String initialSkinName;
+		public SkeletonDataAsset SkeletonDataAsset { get { return skeletonDataAsset; } } // ISkeletonComponent
+		public string initialSkinName;
 
 
 		#region Advanced
 		#region Advanced
 		// Submesh Separation
 		// Submesh Separation
@@ -65,6 +64,7 @@ namespace Spine.Unity {
 		public float zSpacing;
 		public float zSpacing;
 		public bool renderMeshes = true, immutableTriangles;
 		public bool renderMeshes = true, immutableTriangles;
 		public bool pmaVertexColors = true;
 		public bool pmaVertexColors = true;
+		public bool clearStateOnDisable = false;
 
 
 		#if SPINE_OPTIONAL_NORMALS
 		#if SPINE_OPTIONAL_NORMALS
 		public bool calculateNormals;
 		public bool calculateNormals;
@@ -100,10 +100,6 @@ namespace Spine.Unity {
 		}
 		}
 		#endif
 		#endif
 
 
-		#if SPINE_OPTIONAL_SUBMESHRENDERER
-		private Spine.Unity.Modules.SkeletonUtilitySubmeshRenderer[] submeshRenderers;
-		#endif
-
 		#if SPINE_OPTIONAL_MATERIALOVERRIDE
 		#if SPINE_OPTIONAL_MATERIALOVERRIDE
 		[System.NonSerialized] readonly Dictionary<Material, Material> customMaterialOverride = new Dictionary<Material, Material>();
 		[System.NonSerialized] readonly Dictionary<Material, Material> customMaterialOverride = new Dictionary<Material, Material>();
 		public Dictionary<Material, Material> CustomMaterialOverride { get { return customMaterialOverride; } }
 		public Dictionary<Material, Material> CustomMaterialOverride { get { return customMaterialOverride; } }
@@ -164,6 +160,17 @@ namespace Spine.Unity {
 			Initialize(false);
 			Initialize(false);
 		}
 		}
 
 
+		void OnDisable () {
+			if (clearStateOnDisable)
+				ClearState();
+		}
+
+		protected virtual void ClearState () {
+			meshFilter.sharedMesh = null;
+			currentInstructions.Clear();
+			skeleton.SetToSetupPose();
+		}
+
 		public virtual void Initialize (bool overwrite) {
 		public virtual void Initialize (bool overwrite) {
 			if (valid && !overwrite)
 			if (valid && !overwrite)
 				return;
 				return;
@@ -212,10 +219,6 @@ namespace Spine.Unity {
 			for (int i = 0; i < separatorSlotNames.Length; i++)
 			for (int i = 0; i < separatorSlotNames.Length; i++)
 				separatorSlots.Add(skeleton.FindSlot(separatorSlotNames[i]));
 				separatorSlots.Add(skeleton.FindSlot(separatorSlotNames[i]));
 
 
-			#if SPINE_OPTIONAL_SUBMESHRENDERER
-			submeshRenderers = GetComponentsInChildren<Spine.Unity.Modules.SkeletonUtilitySubmeshRenderer>();
-			#endif
-
 			LateUpdate();
 			LateUpdate();
 
 
 			if (OnRebuild != null)
 			if (OnRebuild != null)
@@ -231,9 +234,6 @@ namespace Spine.Unity {
 				#if SPINE_OPTIONAL_RENDEROVERRIDE
 				#if SPINE_OPTIONAL_RENDEROVERRIDE
 				&& this.generateMeshOverride == null
 				&& this.generateMeshOverride == null
 				#endif
 				#endif
-				#if SPINE_OPTIONAL_SUBMESHRENDERER
-				&& submeshRenderers.Length > 0
-				#endif
 			)
 			)
 				return;
 				return;
 			
 			
@@ -528,18 +528,6 @@ namespace Spine.Unity {
 			meshFilter.sharedMesh = currentMesh;
 			meshFilter.sharedMesh = currentMesh;
 			currentSmartMesh.instructionUsed.Set(workingInstruction);
 			currentSmartMesh.instructionUsed.Set(workingInstruction);
 
 
-
-			#if SPINE_OPTIONAL_SUBMESHRENDERER
-			if (submeshRenderers.Length > 0) {
-				for (int i = 0; i < submeshRenderers.Length; i++) {
-					var submeshRenderer = submeshRenderers[i];
-					if (submeshRenderer.submeshIndex < sharedMaterials.Length)
-						submeshRenderer.SetMesh(meshRenderer, currentMesh, sharedMaterials[submeshRenderer.submeshIndex]);
-					else
-						submeshRenderer.GetComponent<Renderer>().enabled = false;
-				}
-			}
-			#endif
 		}
 		}
 
 
 		static bool CheckIfMustUpdateMeshStructure (SmartMesh.Instruction a, SmartMesh.Instruction b) {
 		static bool CheckIfMustUpdateMeshStructure (SmartMesh.Instruction a, SmartMesh.Instruction b) {
@@ -705,22 +693,6 @@ namespace Spine.Unity {
 		}
 		}
 		#endif
 		#endif
 
 
-		#if UNITY_EDITOR
-		void OnDrawGizmos () {
-			// Make scene view selection easier by drawing a clear gizmo over the skeleton.
-			meshFilter = GetComponent<MeshFilter>();
-			if (meshFilter == null) return;
-
-			Mesh mesh = meshFilter.sharedMesh;
-			if (mesh == null) return;
-
-			Bounds meshBounds = mesh.bounds;
-			Gizmos.color = Color.clear;
-			Gizmos.matrix = transform.localToWorldMatrix;
-			Gizmos.DrawCube(meshBounds.center, meshBounds.size);
-		}
-		#endif
-
 		///<summary>This is a Mesh that also stores the instructions SkeletonRenderer generated for it.</summary>
 		///<summary>This is a Mesh that also stores the instructions SkeletonRenderer generated for it.</summary>
 		public class SmartMesh {
 		public class SmartMesh {
 			public Mesh mesh = Spine.Unity.SpineMesh.NewMesh();
 			public Mesh mesh = Spine.Unity.SpineMesh.NewMesh();

+ 26 - 28
spine-unity/Assets/spine-unity/SkeletonUtility/Editor/SkeletonUtilityBoneInspector.cs

@@ -60,21 +60,16 @@ namespace Spine.Unity.Editor {
 			scale = this.serializedObject.FindProperty("scale");
 			scale = this.serializedObject.FindProperty("scale");
 			overrideAlpha = this.serializedObject.FindProperty("overrideAlpha");
 			overrideAlpha = this.serializedObject.FindProperty("overrideAlpha");
 			parentReference = this.serializedObject.FindProperty("parentReference");
 			parentReference = this.serializedObject.FindProperty("parentReference");
-
 			EvaluateFlags();
 			EvaluateFlags();
 
 
-			if (utilityBone.valid == false && skeletonUtility != null && skeletonUtility.skeletonRenderer != null)
+			if (!utilityBone.valid && skeletonUtility != null && skeletonUtility.skeletonRenderer != null)
 				skeletonUtility.skeletonRenderer.Initialize(false);
 				skeletonUtility.skeletonRenderer.Initialize(false);
 
 
 			canCreateHingeChain = CanCreateHingeChain();
 			canCreateHingeChain = CanCreateHingeChain();
-
 			boundingBoxTable.Clear();
 			boundingBoxTable.Clear();
 
 
-			if (multiObject)
-				return;
-
-			if (utilityBone.bone == null)
-				return;
+			if (multiObject) return;
+			if (utilityBone.bone == null) return;
 
 
 			var skeleton = utilityBone.bone.Skeleton;
 			var skeleton = utilityBone.bone.Skeleton;
 			int slotCount = skeleton.Slots.Count;
 			int slotCount = skeleton.Slots.Count;
@@ -95,9 +90,8 @@ namespace Spine.Unity.Editor {
 							boundingBoxes.Add(boundingBoxAttachment);
 							boundingBoxes.Add(boundingBoxAttachment);
 					}
 					}
 
 
-					if (boundingBoxes.Count > 0) {
+					if (boundingBoxes.Count > 0)
 						boundingBoxTable.Add(slot, boundingBoxes);
 						boundingBoxTable.Add(slot, boundingBoxes);
-					}
 				}
 				}
 			}
 			}
 
 
@@ -124,8 +118,7 @@ namespace Spine.Unity.Editor {
 					}
 					}
 				}
 				}
 
 
-				if (boneCount > 1)
-					multiObject = true;
+				multiObject |= (boneCount > 1);
 			}
 			}
 		}
 		}
 
 
@@ -167,45 +160,54 @@ namespace Spine.Unity.Editor {
 			EditorGUILayout.Space();
 			EditorGUILayout.Space();
 
 
 			using (new GUILayout.HorizontalScope()) {
 			using (new GUILayout.HorizontalScope()) {
+				EditorGUILayout.Space();
 				using (new EditorGUI.DisabledGroupScope(multiObject || !utilityBone.valid || utilityBone.bone == null || utilityBone.bone.Children.Count == 0)) {
 				using (new EditorGUI.DisabledGroupScope(multiObject || !utilityBone.valid || utilityBone.bone == null || utilityBone.bone.Children.Count == 0)) {
-					if (GUILayout.Button(new GUIContent("Add Child", SpineEditorUtilities.Icons.bone), GUILayout.Width(150), GUILayout.Height(24)))
+					if (GUILayout.Button(new GUIContent("Add Child", SpineEditorUtilities.Icons.bone), GUILayout.MinWidth(120), GUILayout.Height(24)))
 						BoneSelectorContextMenu("", utilityBone.bone.Children, "<Recursively>", SpawnChildBoneSelected);
 						BoneSelectorContextMenu("", utilityBone.bone.Children, "<Recursively>", SpawnChildBoneSelected);
 				}
 				}
 				using (new EditorGUI.DisabledGroupScope(multiObject || !utilityBone.valid || utilityBone.bone == null || containsOverrides)) {
 				using (new EditorGUI.DisabledGroupScope(multiObject || !utilityBone.valid || utilityBone.bone == null || containsOverrides)) {
-					if (GUILayout.Button(new GUIContent("Add Override", SpineEditorUtilities.Icons.poseBones), GUILayout.Width(150), GUILayout.Height(24)))
+					if (GUILayout.Button(new GUIContent("Add Override", SpineEditorUtilities.Icons.poseBones), GUILayout.MinWidth(120), GUILayout.Height(24)))
 						SpawnOverride();
 						SpawnOverride();
 				}
 				}
+				EditorGUILayout.Space();
+			}
+			EditorGUILayout.Space();
+			using (new GUILayout.HorizontalScope()) {
+				EditorGUILayout.Space();
 				using (new EditorGUI.DisabledGroupScope(multiObject || !utilityBone.valid || !canCreateHingeChain)) {
 				using (new EditorGUI.DisabledGroupScope(multiObject || !utilityBone.valid || !canCreateHingeChain)) {
 					if (GUILayout.Button(new GUIContent("Create Hinge Chain", SpineEditorUtilities.Icons.hingeChain), GUILayout.Width(150), GUILayout.Height(24)))
 					if (GUILayout.Button(new GUIContent("Create Hinge Chain", SpineEditorUtilities.Icons.hingeChain), GUILayout.Width(150), GUILayout.Height(24)))
 						CreateHingeChain();
 						CreateHingeChain();
 				}
 				}
+				EditorGUILayout.Space();
 			}
 			}
 
 
 			using (new EditorGUI.DisabledGroupScope(multiObject || boundingBoxTable.Count == 0)) {
 			using (new EditorGUI.DisabledGroupScope(multiObject || boundingBoxTable.Count == 0)) {
 				EditorGUILayout.LabelField(new GUIContent("Bounding Boxes", SpineEditorUtilities.Icons.boundingBox), EditorStyles.boldLabel);
 				EditorGUILayout.LabelField(new GUIContent("Bounding Boxes", SpineEditorUtilities.Icons.boundingBox), EditorStyles.boldLabel);
 
 
 				foreach(var entry in boundingBoxTable){
 				foreach(var entry in boundingBoxTable){
+					Slot slot = entry.Key;
+					var boundingBoxes = entry.Value;
+
 					EditorGUI.indentLevel++;
 					EditorGUI.indentLevel++;
-					EditorGUILayout.LabelField(entry.Key.Data.Name);
+					EditorGUILayout.LabelField(slot.Data.Name);
 					EditorGUI.indentLevel++;
 					EditorGUI.indentLevel++;
 					{
 					{
-						foreach (var box in entry.Value) {
+						foreach (var box in boundingBoxes) {
 							using (new GUILayout.HorizontalScope()) {
 							using (new GUILayout.HorizontalScope()) {
 								GUILayout.Space(30);
 								GUILayout.Space(30);
 								if (GUILayout.Button(box.Name, GUILayout.Width(200))) {
 								if (GUILayout.Button(box.Name, GUILayout.Width(200))) {
 									var child = utilityBone.transform.FindChild("[BoundingBox]" + box.Name);
 									var child = utilityBone.transform.FindChild("[BoundingBox]" + box.Name);
 									if (child != null) {
 									if (child != null) {
 										var originalCollider = child.GetComponent<PolygonCollider2D>();
 										var originalCollider = child.GetComponent<PolygonCollider2D>();
-										var updatedCollider = SkeletonUtility.AddBoundingBoxAsComponent(box, child.gameObject, originalCollider.isTrigger);
+										var updatedCollider = SkeletonUtility.AddBoundingBoxAsComponent(box, slot, child.gameObject, originalCollider.isTrigger);
 										originalCollider.points = updatedCollider.points;
 										originalCollider.points = updatedCollider.points;
 										if (EditorApplication.isPlaying)
 										if (EditorApplication.isPlaying)
 											Destroy(updatedCollider);
 											Destroy(updatedCollider);
 										else
 										else
 											DestroyImmediate(updatedCollider);
 											DestroyImmediate(updatedCollider);
 									} else {
 									} else {
-										utilityBone.AddBoundingBox(currentSkinName, entry.Key.Data.Name, box.Name);
+										utilityBone.AddBoundingBox(currentSkinName, slot.Data.Name, box.Name);
 									}
 									}
-
 								}
 								}
 							}
 							}
 
 
@@ -220,7 +222,7 @@ namespace Spine.Unity.Editor {
 		}
 		}
 
 
 		static void BoneSelectorContextMenu (string current, ExposedList<Bone> bones, string topValue, GenericMenu.MenuFunction2 callback) {
 		static void BoneSelectorContextMenu (string current, ExposedList<Bone> bones, string topValue, GenericMenu.MenuFunction2 callback) {
-			GenericMenu menu = new GenericMenu();
+			var menu = new GenericMenu();
 
 
 			if (topValue != "")
 			if (topValue != "")
 				menu.AddItem(new GUIContent(topValue), current == topValue, callback, null);
 				menu.AddItem(new GUIContent(topValue), current == topValue, callback, null);
@@ -237,17 +239,16 @@ namespace Spine.Unity.Editor {
 				boneName.stringValue = "";
 				boneName.stringValue = "";
 				serializedObject.ApplyModifiedProperties();
 				serializedObject.ApplyModifiedProperties();
 			} else {
 			} else {
-				Bone bone = (Bone)obj;
+				var bone = (Bone)obj;
 				boneName.stringValue = bone.Data.Name;
 				boneName.stringValue = bone.Data.Name;
 				serializedObject.ApplyModifiedProperties();
 				serializedObject.ApplyModifiedProperties();
-
 				utilityBone.Reset();
 				utilityBone.Reset();
 			}
 			}
 		}
 		}
 
 
 		void SpawnChildBoneSelected (object obj) {
 		void SpawnChildBoneSelected (object obj) {
 			if (obj == null) {
 			if (obj == null) {
-				//add recursively
+				// Add recursively
 				foreach (var bone in utilityBone.bone.Children) {
 				foreach (var bone in utilityBone.bone.Children) {
 					GameObject go = skeletonUtility.SpawnBoneRecursively(bone, utilityBone.transform, utilityBone.mode, utilityBone.position, utilityBone.rotation, utilityBone.scale);
 					GameObject go = skeletonUtility.SpawnBoneRecursively(bone, utilityBone.transform, utilityBone.mode, utilityBone.position, utilityBone.rotation, utilityBone.scale);
 					SkeletonUtilityBone[] newUtilityBones = go.GetComponentsInChildren<SkeletonUtilityBone>();
 					SkeletonUtilityBone[] newUtilityBones = go.GetComponentsInChildren<SkeletonUtilityBone>();
@@ -255,7 +256,7 @@ namespace Spine.Unity.Editor {
 						SkeletonUtilityInspector.AttachIcon(utilBone);
 						SkeletonUtilityInspector.AttachIcon(utilBone);
 				}
 				}
 			} else {
 			} else {
-				Bone bone = (Bone)obj;
+				var bone = (Bone)obj;
 				GameObject go = skeletonUtility.SpawnBone(bone, utilityBone.transform, utilityBone.mode, utilityBone.position, utilityBone.rotation, utilityBone.scale);
 				GameObject go = skeletonUtility.SpawnBone(bone, utilityBone.transform, utilityBone.mode, utilityBone.position, utilityBone.rotation, utilityBone.scale);
 				SkeletonUtilityInspector.AttachIcon(go.GetComponent<SkeletonUtilityBone>());
 				SkeletonUtilityInspector.AttachIcon(go.GetComponent<SkeletonUtilityBone>());
 				Selection.activeGameObject = go;
 				Selection.activeGameObject = go;
@@ -281,10 +282,7 @@ namespace Spine.Unity.Editor {
 
 
 			Rigidbody[] rigidbodies = utilityBone.GetComponentsInChildren<Rigidbody>();
 			Rigidbody[] rigidbodies = utilityBone.GetComponentsInChildren<Rigidbody>();
 
 
-			if (rigidbodies.Length > 0)
-				return false;
-
-			return true;
+			return rigidbodies.Length <= 0;
 		}
 		}
 
 
 		void CreateHingeChain () {
 		void CreateHingeChain () {

+ 124 - 159
spine-unity/Assets/spine-unity/SkeletonUtility/Editor/SkeletonUtilityInspector.cs

@@ -32,74 +32,30 @@
 
 
 using UnityEngine;
 using UnityEngine;
 using UnityEditor;
 using UnityEditor;
-
-#if UNITY_4_3
-//nothing
-#else
 using UnityEditor.AnimatedValues;
 using UnityEditor.AnimatedValues;
-#endif
 using System.Collections.Generic;
 using System.Collections.Generic;
 using Spine;
 using Spine;
-
 using System.Reflection;
 using System.Reflection;
 
 
-
 namespace Spine.Unity.Editor {
 namespace Spine.Unity.Editor {
+	using Icons = SpineEditorUtilities.Icons;
+
 	[CustomEditor(typeof(SkeletonUtility))]
 	[CustomEditor(typeof(SkeletonUtility))]
 	public class SkeletonUtilityInspector : UnityEditor.Editor {
 	public class SkeletonUtilityInspector : UnityEditor.Editor {
 
 
-		public static void AttachIcon (SkeletonUtilityBone utilityBone) {
-			Skeleton skeleton = utilityBone.skeletonUtility.skeletonRenderer.skeleton;
-			Texture2D icon;
-			if (utilityBone.bone.Data.Length == 0)
-				icon = SpineEditorUtilities.Icons.nullBone;
-			else
-				icon = SpineEditorUtilities.Icons.boneNib;
-
-			foreach (IkConstraint c in skeleton.IkConstraints) {
-				if (c.Target == utilityBone.bone) {
-					icon = SpineEditorUtilities.Icons.constraintNib;
-					break;
-				}
-			}
-
-			typeof(EditorGUIUtility).InvokeMember("SetIconForObject", BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.NonPublic, null, null, new object[2] {
-				utilityBone.gameObject,
-				icon
-			});
-		}
-
-		static void AttachIconsToChildren (Transform root) {
-			if (root != null) {
-				var utilityBones = root.GetComponentsInChildren<SkeletonUtilityBone>();
-				foreach (var utilBone in utilityBones) {
-					AttachIcon(utilBone);
-				}
-			}
-		}
-
-		static SkeletonUtilityInspector () {
-			#if UNITY_4_3
-			showSlots = false;
-			#else
-			showSlots = new AnimBool(false);
-			#endif
-		}
-
 		SkeletonUtility skeletonUtility;
 		SkeletonUtility skeletonUtility;
 		Skeleton skeleton;
 		Skeleton skeleton;
 		SkeletonRenderer skeletonRenderer;
 		SkeletonRenderer skeletonRenderer;
 		Transform transform;
 		Transform transform;
 		bool isPrefab;
 		bool isPrefab;
-		Dictionary<Slot, List<Attachment>> attachmentTable = new Dictionary<Slot, List<Attachment>>();
 
 
+		Dictionary<Slot, List<Attachment>> attachmentTable = new Dictionary<Slot, List<Attachment>>();
 
 
-		//GUI stuff
-		#if UNITY_4_3
-		static bool showSlots;
-		#else
-		static AnimBool showSlots;
-		#endif
+		GUIContent SpawnHierarchyButtonLabel = new GUIContent("Spawn Hierarchy", Icons.skeleton);
+		GUIContent SlotsRootLabel = new GUIContent("Slots", Icons.slotRoot);
+		static AnimBool showSlots = new AnimBool(false);
+		static bool showPaths = true;
+		static bool debugSkeleton = false;
 
 
 		void OnEnable () {
 		void OnEnable () {
 			skeletonUtility = (SkeletonUtility)target;
 			skeletonUtility = (SkeletonUtility)target;
@@ -110,7 +66,6 @@ namespace Spine.Unity.Editor {
 			if (skeleton == null) {
 			if (skeleton == null) {
 				skeletonRenderer.Initialize(false);
 				skeletonRenderer.Initialize(false);
 				skeletonRenderer.LateUpdate();
 				skeletonRenderer.LateUpdate();
-
 				skeleton = skeletonRenderer.skeleton;
 				skeleton = skeletonRenderer.skeleton;
 			}
 			}
 
 
@@ -125,150 +80,136 @@ namespace Spine.Unity.Editor {
 				OnEnable();
 				OnEnable();
 				return;
 				return;
 			}
 			}
-				
+
 			var m = transform.localToWorldMatrix;
 			var m = transform.localToWorldMatrix;
 			foreach (Bone b in skeleton.Bones) {
 			foreach (Bone b in skeleton.Bones) {
 				Vector3 pos = new Vector3(b.WorldX, b.WorldY, 0);
 				Vector3 pos = new Vector3(b.WorldX, b.WorldY, 0);
 				Quaternion rot = Quaternion.Euler(0, 0, b.WorldRotationX - 90f);
 				Quaternion rot = Quaternion.Euler(0, 0, b.WorldRotationX - 90f);
 				Vector3 scale = Vector3.one * b.Data.Length * b.WorldScaleX;
 				Vector3 scale = Vector3.one * b.Data.Length * b.WorldScaleX;
-				const float mx = 2.5f;
+				const float mx = 2f;
 				scale.x = Mathf.Clamp(scale.x, -mx, mx);
 				scale.x = Mathf.Clamp(scale.x, -mx, mx);
 				SpineEditorUtilities.DrawBone(m * Matrix4x4.TRS(pos, rot, scale));
 				SpineEditorUtilities.DrawBone(m * Matrix4x4.TRS(pos, rot, scale));
 			}
 			}
 
 
-			foreach (Slot s in skeleton.DrawOrder) {
-				var p = s.Attachment as PathAttachment;
-				if (p != null) SpineEditorUtilities.DrawPath(s, p, transform);
-			}
-		}
-
-		void UpdateAttachments () {
-			attachmentTable = new Dictionary<Slot, List<Attachment>>();
-			Skin skin = skeleton.Skin ?? skeletonRenderer.skeletonDataAsset.GetSkeletonData(true).DefaultSkin;
-			for (int i = skeleton.Slots.Count-1; i >= 0; i--) {
-				List<Attachment> attachments = new List<Attachment>();
-				skin.FindAttachmentsForSlot(i, attachments);
-
-				attachmentTable.Add(skeleton.Slots.Items[i], attachments);
-			}
-		}
-
-		void SpawnHierarchyButton (string label, string tooltip, SkeletonUtilityBone.Mode mode, bool pos, bool rot, bool sca, params GUILayoutOption[] options) {
-			GUIContent content = new GUIContent(label, tooltip);
-			if (GUILayout.Button(content, options)) {
-				if (skeletonUtility.skeletonRenderer == null)
-					skeletonUtility.skeletonRenderer = skeletonUtility.GetComponent<SkeletonRenderer>();
-
-				if (skeletonUtility.boneRoot != null) {
-					return;
+			if (showPaths) {
+				foreach (Slot s in skeleton.DrawOrder) {
+					var p = s.Attachment as PathAttachment;
+					if (p != null) SpineEditorUtilities.DrawPath(s, p, transform);
 				}
 				}
-
-				skeletonUtility.SpawnHierarchy(mode, pos, rot, sca);
-
-				SkeletonUtilityBone[] boneComps = skeletonUtility.GetComponentsInChildren<SkeletonUtilityBone>();
-				foreach (SkeletonUtilityBone b in boneComps) 
-					AttachIcon(b);
 			}
 			}
 		}
 		}
 
 
 		public override void OnInspectorGUI () {
 		public override void OnInspectorGUI () {
+			bool requireRepaint = false;
+
 			if (isPrefab) {
 			if (isPrefab) {
-				GUILayout.Label(new GUIContent("Cannot edit Prefabs", SpineEditorUtilities.Icons.warning));
+				GUILayout.Label(new GUIContent("Cannot edit Prefabs", Icons.warning));
 				return;
 				return;
 			}
 			}
 
 
 			if (!skeletonRenderer.valid) {
 			if (!skeletonRenderer.valid) {
-				GUILayout.Label(new GUIContent("Spine Component invalid. Check Skeleton Data Asset.", SpineEditorUtilities.Icons.warning));
+				GUILayout.Label(new GUIContent("Spine Component invalid. Check Skeleton Data Asset.", Icons.warning));
 				return;	
 				return;	
 			}
 			}
 
 
 			skeletonUtility.boneRoot = (Transform)EditorGUILayout.ObjectField("Bone Root", skeletonUtility.boneRoot, typeof(Transform), true);
 			skeletonUtility.boneRoot = (Transform)EditorGUILayout.ObjectField("Bone Root", skeletonUtility.boneRoot, typeof(Transform), true);
 
 
-			using (new GUILayout.HorizontalScope()) {
-				using (new EditorGUI.DisabledGroupScope(skeletonUtility.boneRoot != null)) {
-					if (GUILayout.Button(new GUIContent("Spawn Hierarchy", SpineEditorUtilities.Icons.skeleton), GUILayout.Width(150), GUILayout.Height(24)))
-						SpawnHierarchyContextMenu();
-				}
-
-				// if (GUILayout.Button(new GUIContent("Spawn Submeshes", SpineEditorUtilities.Icons.subMeshRenderer), GUILayout.Width(150), GUILayout.Height(24)))
-				// skeletonUtility.SpawnSubRenderers(true);
+			using (new EditorGUI.DisabledGroupScope(skeletonUtility.boneRoot != null)) {
+				if (SpineInspectorUtility.LargeCenteredButton(SpawnHierarchyButtonLabel))
+					SpawnHierarchyContextMenu();
 			}
 			}
 
 
-			EditorGUI.BeginChangeCheck();
-			skeleton.FlipX = EditorGUILayout.ToggleLeft("Flip X", skeleton.FlipX);
-			skeleton.FlipY = EditorGUILayout.ToggleLeft("Flip Y", skeleton.FlipY);
-			if (EditorGUI.EndChangeCheck()) {
-				skeletonRenderer.LateUpdate();
-				SceneView.RepaintAll();
-			}
-
-			#if UNITY_4_3
-			showSlots = EditorGUILayout.Foldout(showSlots, "Slots");
-			#else
-			showSlots.target = EditorGUILayout.Foldout(showSlots.target, "Slots");
-			if (EditorGUILayout.BeginFadeGroup(showSlots.faded)) {
-			#endif
-				foreach (KeyValuePair<Slot, List<Attachment>> pair in attachmentTable) {
-
-					Slot slot = pair.Key;
-
-					EditorGUILayout.BeginHorizontal();
-					EditorGUI.indentLevel = 1;
-					EditorGUILayout.LabelField(new GUIContent(slot.Data.Name, SpineEditorUtilities.Icons.slot), GUILayout.ExpandWidth(false));
+			using (new SpineInspectorUtility.BoxScope()) {
+				debugSkeleton = EditorGUILayout.Foldout(debugSkeleton, "Debug Skeleton");
 
 
+				if (debugSkeleton) {
 					EditorGUI.BeginChangeCheck();
 					EditorGUI.BeginChangeCheck();
-					Color c = EditorGUILayout.ColorField(new Color(slot.R, slot.G, slot.B, slot.A), GUILayout.Width(60));
-
-					if (EditorGUI.EndChangeCheck()) {
-						slot.SetColor(c);
-						skeletonRenderer.LateUpdate();
-					}
+					showPaths = EditorGUILayout.Toggle("Show Paths", showPaths);
+					requireRepaint |= EditorGUI.EndChangeCheck();
 
 
-					EditorGUILayout.EndHorizontal();
-
-
-
-					foreach (Attachment attachment in pair.Value) {
-
-						if (slot.Attachment == attachment) {
-							GUI.contentColor = Color.white;
-						} else {
-							GUI.contentColor = Color.grey;
+					EditorGUI.BeginChangeCheck();
+					skeleton.FlipX = EditorGUILayout.ToggleLeft("skeleton.FlipX", skeleton.FlipX);
+					skeleton.FlipY = EditorGUILayout.ToggleLeft("skeleton.FlipY", skeleton.FlipY);
+					requireRepaint |= EditorGUI.EndChangeCheck();
+
+					foreach (var t in skeleton.IkConstraints)
+						EditorGUILayout.LabelField(t.Data.Name + " " + t.Mix + " " + t.Target.Data.Name);
+
+					showSlots.target = EditorGUILayout.Foldout(showSlots.target, SlotsRootLabel);
+					if (showSlots.faded > 0) {
+						using (new EditorGUILayout.FadeGroupScope(showSlots.faded)) {
+							int baseIndent = EditorGUI.indentLevel;
+							foreach (KeyValuePair<Slot, List<Attachment>> pair in attachmentTable) {
+								Slot slot = pair.Key;
+
+								using (new EditorGUILayout.HorizontalScope()) {
+									EditorGUI.indentLevel = baseIndent + 1;
+									EditorGUILayout.LabelField(new GUIContent(slot.Data.Name, Icons.slot), GUILayout.ExpandWidth(false));
+									EditorGUI.BeginChangeCheck();
+									Color c = EditorGUILayout.ColorField(new Color(slot.R, slot.G, slot.B, slot.A), GUILayout.Width(60));
+									if (EditorGUI.EndChangeCheck()) {
+										slot.SetColor(c);
+										requireRepaint = true;
+									}
+								}
+
+								foreach (var attachment in pair.Value) {
+									GUI.contentColor = slot.Attachment == attachment ? Color.white : Color.grey;
+									EditorGUI.indentLevel = baseIndent + 2;
+									var icon = (attachment is MeshAttachment) ? Icons.mesh : Icons.image;
+									bool isAttached = (attachment == slot.Attachment);
+									bool swap = EditorGUILayout.ToggleLeft(new GUIContent(attachment.Name, icon), attachment == slot.Attachment);
+									if (isAttached != swap) {
+										slot.Attachment = isAttached ? null : attachment;
+										requireRepaint = true;
+									}
+									GUI.contentColor = Color.white;
+								}
+							}
 						}
 						}
+					}
 
 
-						EditorGUI.indentLevel = 2;
-						bool isAttached = attachment == slot.Attachment;
-
-						Texture2D icon = null;
-
-						if (attachment is MeshAttachment)
-							icon = SpineEditorUtilities.Icons.mesh;
-						else
-							icon = SpineEditorUtilities.Icons.image;
 
 
-						bool swap = EditorGUILayout.ToggleLeft(new GUIContent(attachment.Name, icon), attachment == slot.Attachment);
+				}
 
 
-						if (!isAttached && swap) {
-							slot.Attachment = attachment;
-							skeletonRenderer.LateUpdate();
-						} else if (isAttached && !swap) {
-							slot.Attachment = null;
-							skeletonRenderer.LateUpdate();
-						}
+				if (showSlots.isAnimating)
+					Repaint();
+			}
 
 
-						GUI.contentColor = Color.white;
-					}
-				}
-			#if UNITY_4_3
+			if (requireRepaint) {
+				skeletonRenderer.LateUpdate();
+				SceneView.RepaintAll();
+			}
+		}
 
 
-			#else
+		void UpdateAttachments () {
+			attachmentTable = new Dictionary<Slot, List<Attachment>>();
+			Skin skin = skeleton.Skin ?? skeletonRenderer.skeletonDataAsset.GetSkeletonData(true).DefaultSkin;
+			for (int i = skeleton.Slots.Count - 1; i >= 0; i--) {
+				var attachments = new List<Attachment>();
+				skin.FindAttachmentsForSlot(i, attachments);
+				attachmentTable.Add(skeleton.Slots.Items[i], attachments);
 			}
 			}
-			EditorGUILayout.EndFadeGroup();
-			if (showSlots.isAnimating)
-				Repaint();
-			#endif
 		}
 		}
 
 
+//		void SpawnHierarchyButton (string label, string tooltip, SkeletonUtilityBone.Mode mode, bool pos, bool rot, bool sca, params GUILayoutOption[] options) {
+//			GUIContent content = new GUIContent(label, tooltip);
+//			if (GUILayout.Button(content, options)) {
+//				if (skeletonUtility.skeletonRenderer == null)
+//					skeletonUtility.skeletonRenderer = skeletonUtility.GetComponent<SkeletonRenderer>();
+//
+//				if (skeletonUtility.boneRoot != null) {
+//					return;
+//				}
+//
+//				skeletonUtility.SpawnHierarchy(mode, pos, rot, sca);
+//
+//				SkeletonUtilityBone[] boneComps = skeletonUtility.GetComponentsInChildren<SkeletonUtilityBone>();
+//				foreach (SkeletonUtilityBone b in boneComps) 
+//					AttachIcon(b);
+//			}
+//		}
+
 		void SpawnHierarchyContextMenu () {
 		void SpawnHierarchyContextMenu () {
 			GenericMenu menu = new GenericMenu();
 			GenericMenu menu = new GenericMenu();
 
 
@@ -281,6 +222,30 @@ namespace Spine.Unity.Editor {
 			menu.ShowAsContext();
 			menu.ShowAsContext();
 		}
 		}
 
 
+		public static void AttachIcon (SkeletonUtilityBone utilityBone) {
+			Skeleton skeleton = utilityBone.skeletonUtility.skeletonRenderer.skeleton;
+			Texture2D icon = utilityBone.bone.Data.Length == 0 ? Icons.nullBone : Icons.boneNib;
+
+			foreach (IkConstraint c in skeleton.IkConstraints)
+				if (c.Target == utilityBone.bone) {
+					icon = Icons.constraintNib;
+					break;
+				}
+
+			typeof(EditorGUIUtility).InvokeMember("SetIconForObject", BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.NonPublic, null, null, new object[2] {
+				utilityBone.gameObject,
+				icon
+			});
+		}
+
+		static void AttachIconsToChildren (Transform root) {
+			if (root != null) {
+				var utilityBones = root.GetComponentsInChildren<SkeletonUtilityBone>();
+				foreach (var utilBone in utilityBones)
+					AttachIcon(utilBone);
+			}
+		}
+
 		void SpawnFollowHierarchy () {
 		void SpawnFollowHierarchy () {
 			Selection.activeGameObject = skeletonUtility.SpawnHierarchy(SkeletonUtilityBone.Mode.Follow, true, true, true);
 			Selection.activeGameObject = skeletonUtility.SpawnHierarchy(SkeletonUtilityBone.Mode.Follow, true, true, true);
 			AttachIconsToChildren(skeletonUtility.boneRoot);
 			AttachIconsToChildren(skeletonUtility.boneRoot);

+ 37 - 97
spine-unity/Assets/spine-unity/SkeletonUtility/SkeletonUtility.cs

@@ -39,85 +39,39 @@ namespace Spine.Unity {
 	[RequireComponent(typeof(ISkeletonAnimation))]
 	[RequireComponent(typeof(ISkeletonAnimation))]
 	[ExecuteInEditMode]
 	[ExecuteInEditMode]
 	public class SkeletonUtility : MonoBehaviour {
 	public class SkeletonUtility : MonoBehaviour {
-
-		public static T GetInParent<T> (Transform origin) where T : Component {
-			#if UNITY_4_3
-			Transform parent = origin.parent;
-			while(parent.GetComponent<T>() == null){
-			parent = parent.parent;
-			if(parent == null)
-			return default(T);
-			}
-
-			return parent.GetComponent<T>();
-			#else
-			return origin.GetComponentInParent<T>();
-			#endif
-		}
-
+	
+		#region BoundingBoxAttachment
 		public static PolygonCollider2D AddBoundingBox (Skeleton skeleton, string skinName, string slotName, string attachmentName, Transform parent, bool isTrigger = true) {
 		public static PolygonCollider2D AddBoundingBox (Skeleton skeleton, string skinName, string slotName, string attachmentName, Transform parent, bool isTrigger = true) {
-			// List<Attachment> attachments = new List<Attachment>();
-			Skin skin;
-
-			if (skinName == "")
-				skinName = skeleton.Data.DefaultSkin.Name;
-
-			skin = skeleton.Data.FindSkin(skinName);
-
+			skinName = string.IsNullOrEmpty(skinName) ? skinName : skeleton.Data.DefaultSkin.Name;
+			Skin skin = skeleton.Data.FindSkin(skinName);
 			if (skin == null) {
 			if (skin == null) {
 				Debug.LogError("Skin " + skinName + " not found!");
 				Debug.LogError("Skin " + skinName + " not found!");
 				return null;
 				return null;
 			}
 			}
 
 
 			var attachment = skin.GetAttachment(skeleton.FindSlotIndex(slotName), attachmentName);
 			var attachment = skin.GetAttachment(skeleton.FindSlotIndex(slotName), attachmentName);
-			if (attachment is BoundingBoxAttachment) {
-				GameObject go = new GameObject("[BoundingBox]" + attachmentName);
-				go.transform.parent = parent;
-				go.transform.localPosition = Vector3.zero;
-				go.transform.localRotation = Quaternion.identity;
-				go.transform.localScale = Vector3.one;
-				var collider = go.AddComponent<PolygonCollider2D>();
-				collider.isTrigger = isTrigger;
-				var boundingBox = (BoundingBoxAttachment)attachment;
-				float[] floats = boundingBox.Vertices;
-				int floatCount = floats.Length;
-				int vertCount = floatCount / 2;
-
-				Vector2[] verts = new Vector2[vertCount];
-				int v = 0;
-				for (int i = 0; i < floatCount; i += 2, v++) {
-					verts[v].x = floats[i];
-					verts[v].y = floats[i + 1];
-				}
-
-				collider.SetPath(0, verts);
-
-				return collider;
-
+			var box = attachment as BoundingBoxAttachment;
+			if (box != null) {
+				var go = new GameObject("[BoundingBox]" + attachmentName);
+				var got = go.transform;
+				got.parent = parent;
+				got.localPosition = Vector3.zero;
+				got.localRotation = Quaternion.identity;
+				got.localScale = Vector3.one;
+				var slot = skeleton.FindSlot(slotName);
+				return AddBoundingBoxAsComponent(box, slot, go, isTrigger);
 			}
 			}
 
 
 			return null;
 			return null;
 		}
 		}
 
 
-		public static PolygonCollider2D AddBoundingBoxAsComponent (BoundingBoxAttachment boundingBox, GameObject gameObject, bool isTrigger = true) {
-			if (boundingBox == null)
-				return null;
-
+		public static PolygonCollider2D AddBoundingBoxAsComponent (BoundingBoxAttachment box, Slot slot, GameObject gameObject, bool isTrigger = true) {
+			if (box == null) return null;
+			if (box.IsWeighted()) Debug.LogWarning("UnityEngine.PolygonCollider2D does not support weighted or animated points. Collider will not be animated. Please remove weights and animations from the bounding box in Spine editor.");
+			var verts = box.GetWorldVertices(slot, null);
 			var collider = gameObject.AddComponent<PolygonCollider2D>();
 			var collider = gameObject.AddComponent<PolygonCollider2D>();
 			collider.isTrigger = isTrigger;
 			collider.isTrigger = isTrigger;
-			float[] floats = boundingBox.Vertices;
-			int floatCount = floats.Length;
-			int vertCount = floatCount / 2;
-
-			Vector2[] verts = new Vector2[vertCount];
-			int v = 0;
-			for (int i = 0; i < floatCount; i += 2, v++) {
-				verts[v].x = floats[i];
-				verts[v].y = floats[i + 1];
-			}
-
 			collider.SetPath(0, verts);
 			collider.SetPath(0, verts);
-
 			return collider;
 			return collider;
 		}
 		}
 
 
@@ -126,22 +80,20 @@ namespace Spine.Unity {
 			int floatCount = floats.Length;
 			int floatCount = floats.Length;
 
 
 			Bounds bounds = new Bounds();
 			Bounds bounds = new Bounds();
-
 			bounds.center = new Vector3(floats[0], floats[1], 0);
 			bounds.center = new Vector3(floats[0], floats[1], 0);
-			for (int i = 2; i < floatCount; i += 2) {
+			for (int i = 2; i < floatCount; i += 2)
 				bounds.Encapsulate(new Vector3(floats[i], floats[i + 1], 0));
 				bounds.Encapsulate(new Vector3(floats[i], floats[i + 1], 0));
-			}
+
 			Vector3 size = bounds.size;
 			Vector3 size = bounds.size;
 			size.z = depth;
 			size.z = depth;
 			bounds.size = size;
 			bounds.size = size;
 
 
 			return bounds;
 			return bounds;
 		}
 		}
+		#endregion
 
 
 		public delegate void SkeletonUtilityDelegate ();
 		public delegate void SkeletonUtilityDelegate ();
-
 		public event SkeletonUtilityDelegate OnReset;
 		public event SkeletonUtilityDelegate OnReset;
-
 		public Transform boneRoot;
 		public Transform boneRoot;
 
 
 		void Update () {
 		void Update () {
@@ -165,7 +117,6 @@ namespace Spine.Unity {
 		public List<SkeletonUtilityBone> utilityBones = new List<SkeletonUtilityBone>();
 		public List<SkeletonUtilityBone> utilityBones = new List<SkeletonUtilityBone>();
 		[System.NonSerialized]
 		[System.NonSerialized]
 		public List<SkeletonUtilityConstraint> utilityConstraints = new List<SkeletonUtilityConstraint>();
 		public List<SkeletonUtilityConstraint> utilityConstraints = new List<SkeletonUtilityConstraint>();
-		//	Dictionary<Bone, SkeletonUtilityBone> utilityBoneTable;
 
 
 		protected bool hasTransformBones;
 		protected bool hasTransformBones;
 		protected bool hasUtilityConstraints;
 		protected bool hasUtilityConstraints;
@@ -190,13 +141,12 @@ namespace Spine.Unity {
 				skeletonAnimation.UpdateLocal += UpdateLocal;
 				skeletonAnimation.UpdateLocal += UpdateLocal;
 			}
 			}
 
 
-
 			CollectBones();
 			CollectBones();
 		}
 		}
 
 
 		void Start () {
 		void Start () {
 			//recollect because order of operations failure when switching between game mode and edit mode...
 			//recollect because order of operations failure when switching between game mode and edit mode...
-			//		CollectBones();
+			CollectBones();
 		}
 		}
 
 
 		void OnDisable () {
 		void OnDisable () {
@@ -244,41 +194,38 @@ namespace Spine.Unity {
 		}
 		}
 
 
 		public void CollectBones () {
 		public void CollectBones () {
-			if (skeletonRenderer.skeleton == null)
-				return;
+			var skeleton = skeletonRenderer.skeleton;
+			if (skeleton == null) return;
 
 
 			if (boneRoot != null) {
 			if (boneRoot != null) {
-				List<string> constraintTargetNames = new List<string>();
-
-				ExposedList<IkConstraint> ikConstraints = skeletonRenderer.skeleton.IkConstraints;
+				var constraintTargets = new List<System.Object>();
+				var ikConstraints = skeleton.IkConstraints;
 				for (int i = 0, n = ikConstraints.Count; i < n; i++)
 				for (int i = 0, n = ikConstraints.Count; i < n; i++)
-					constraintTargetNames.Add(ikConstraints.Items[i].Target.Data.Name);
+					constraintTargets.Add(ikConstraints.Items[i].target);
+				
+				var transformConstraints = skeleton.TransformConstraints;
+				for (int i = 0, n = transformConstraints.Count; i < n; i++)
+					constraintTargets.Add(transformConstraints.Items[i].target);
 
 
 				var utilityBones = this.utilityBones;
 				var utilityBones = this.utilityBones;
 				for (int i = 0, n = utilityBones.Count; i < n; i++) {
 				for (int i = 0, n = utilityBones.Count; i < n; i++) {
 					var b = utilityBones[i];
 					var b = utilityBones[i];
 					if (b.bone == null) return;
 					if (b.bone == null) return;
-					if (b.mode == SkeletonUtilityBone.Mode.Override)
-						hasTransformBones = true;
-
-					if (constraintTargetNames.Contains(b.bone.Data.Name))
-						hasUtilityConstraints = true;
+					hasTransformBones |= (b.mode == SkeletonUtilityBone.Mode.Override);
+					hasUtilityConstraints |= constraintTargets.Contains(b.bone);
 				}
 				}
 
 
-				if (utilityConstraints.Count > 0)
-					hasUtilityConstraints = true;
+				hasUtilityConstraints |= utilityConstraints.Count > 0;
 
 
 				if (skeletonAnimation != null) {
 				if (skeletonAnimation != null) {
 					skeletonAnimation.UpdateWorld -= UpdateWorld;
 					skeletonAnimation.UpdateWorld -= UpdateWorld;
 					skeletonAnimation.UpdateComplete -= UpdateComplete;
 					skeletonAnimation.UpdateComplete -= UpdateComplete;
 
 
-					if (hasTransformBones || hasUtilityConstraints) {
+					if (hasTransformBones || hasUtilityConstraints)
 						skeletonAnimation.UpdateWorld += UpdateWorld;
 						skeletonAnimation.UpdateWorld += UpdateWorld;
-					}
-
-					if (hasUtilityConstraints) {
+					
+					if (hasUtilityConstraints)
 						skeletonAnimation.UpdateComplete += UpdateComplete;
 						skeletonAnimation.UpdateComplete += UpdateComplete;
-					}
 				}
 				}
 
 
 				needToReprocessBones = false;
 				needToReprocessBones = false;
@@ -286,7 +233,6 @@ namespace Spine.Unity {
 				utilityBones.Clear();
 				utilityBones.Clear();
 				utilityConstraints.Clear();
 				utilityConstraints.Clear();
 			}
 			}
-
 		}
 		}
 
 
 		void UpdateLocal (ISkeletonAnimation anim) {
 		void UpdateLocal (ISkeletonAnimation anim) {
@@ -339,21 +285,15 @@ namespace Spine.Unity {
 			Skeleton skeleton = this.skeletonRenderer.skeleton;
 			Skeleton skeleton = this.skeletonRenderer.skeleton;
 
 
 			GameObject go = SpawnBone(skeleton.RootBone, boneRoot, mode, pos, rot, sca);
 			GameObject go = SpawnBone(skeleton.RootBone, boneRoot, mode, pos, rot, sca);
-
 			CollectBones();
 			CollectBones();
-
 			return go;
 			return go;
 		}
 		}
 
 
 		public GameObject SpawnHierarchy (SkeletonUtilityBone.Mode mode, bool pos, bool rot, bool sca) {
 		public GameObject SpawnHierarchy (SkeletonUtilityBone.Mode mode, bool pos, bool rot, bool sca) {
 			GetBoneRoot();
 			GetBoneRoot();
-
 			Skeleton skeleton = this.skeletonRenderer.skeleton;
 			Skeleton skeleton = this.skeletonRenderer.skeleton;
-
 			GameObject go = SpawnBoneRecursively(skeleton.RootBone, boneRoot, mode, pos, rot, sca);
 			GameObject go = SpawnBoneRecursively(skeleton.RootBone, boneRoot, mode, pos, rot, sca);
-
 			CollectBones();
 			CollectBones();
-
 			return go;
 			return go;
 		}
 		}
 
 

+ 38 - 143
spine-unity/Assets/spine-unity/SkeletonUtility/SkeletonUtilityBone.cs

@@ -41,49 +41,30 @@ namespace Spine.Unity {
 	[ExecuteInEditMode]
 	[ExecuteInEditMode]
 	[AddComponentMenu("Spine/SkeletonUtilityBone")]
 	[AddComponentMenu("Spine/SkeletonUtilityBone")]
 	public class SkeletonUtilityBone : MonoBehaviour {
 	public class SkeletonUtilityBone : MonoBehaviour {
-
 		public enum Mode {
 		public enum Mode {
 			Follow,
 			Follow,
 			Override
 			Override
 		}
 		}
 
 
-		[System.NonSerialized]
-		public bool valid;
-		[System.NonSerialized]
-		public SkeletonUtility skeletonUtility;
-		[System.NonSerialized]
-		public Bone bone;
+		#region Inspector
+		/// <summary>If a bone isn't set, boneName is used to find the bone.</summary>
+		public string boneName;
+		public Transform parentReference;
 		public Mode mode;
 		public Mode mode;
-		public bool zPosition = true;
-		public bool position;
-		public bool rotation;
-		public bool scale;
-		// MITCH : remove flipX
-		// Kept these fields public to retain serialization. Probably remove eventually?
-		public bool flip;
-		public bool flipX;
+		public bool position, rotation, scale, zPosition = true;
 		[Range(0f, 1f)]
 		[Range(0f, 1f)]
 		public float overrideAlpha = 1;
 		public float overrideAlpha = 1;
-
-		/// <summary>If a bone isn't set, boneName is used to find the bone.</summary>
-		public String boneName;
-		public Transform parentReference;
-
-		[System.NonSerialized]
-		public bool transformLerpComplete;
-
-		protected Transform cachedTransform;
-		protected Transform skeletonTransform;
-
-		// MITCH : nonuniform scale
-		//	private bool nonUniformScaleWarning;
-		//	public bool NonUniformScaleWarning {
-		//		get { return nonUniformScaleWarning; }
-		//	}
-
-		private bool disableInheritScaleWarning;
-		public bool DisableInheritScaleWarning {
-			get { return disableInheritScaleWarning; }
+		#endregion
+
+		[System.NonSerialized] public SkeletonUtility skeletonUtility;
+		[System.NonSerialized] public Bone bone;
+		[System.NonSerialized] public bool transformLerpComplete;
+		[System.NonSerialized] public bool valid;
+		Transform cachedTransform;
+		Transform skeletonTransform;
+		bool incompatibleTransformMode;
+		public bool IncompatibleTransformMode {
+			get { return incompatibleTransformMode; }
 		}
 		}
 
 
 		public void Reset () {
 		public void Reset () {
@@ -99,7 +80,7 @@ namespace Spine.Unity {
 		}
 		}
 
 
 		void OnEnable () {
 		void OnEnable () {
-			skeletonUtility = SkeletonUtility.GetInParent<SkeletonUtility>(transform);
+			skeletonUtility = transform.GetComponentInParent<SkeletonUtility>();
 
 
 			if (skeletonUtility == null)
 			if (skeletonUtility == null)
 				return;
 				return;
@@ -125,14 +106,11 @@ namespace Spine.Unity {
 				return;
 				return;
 			}
 			}
 
 
-			Spine.Skeleton skeleton = skeletonUtility.skeletonRenderer.skeleton;
+			var skeleton = skeletonUtility.skeletonRenderer.skeleton;
 
 
 			if (bone == null) {
 			if (bone == null) {
-				if (boneName == null || boneName.Length == 0)
-					return;
-
+				if (string.IsNullOrEmpty(boneName)) return;
 				bone = skeleton.FindBone(boneName);
 				bone = skeleton.FindBone(boneName);
-
 				if (bone == null) {
 				if (bone == null) {
 					Debug.LogError("Bone not found: " + boneName, this);
 					Debug.LogError("Bone not found: " + boneName, this);
 					return;
 					return;
@@ -140,29 +118,13 @@ namespace Spine.Unity {
 			}
 			}
 
 
 			float skeletonFlipRotation = (skeleton.flipX ^ skeleton.flipY) ? -1f : 1f;
 			float skeletonFlipRotation = (skeleton.flipX ^ skeleton.flipY) ? -1f : 1f;
-
-			// MITCH : remove flipX
-			//		float flipCompensation = 0;
-			//		if (flip && (flipX || (flipX != bone.flipX)) && bone.parent != null) {
-			//			flipCompensation = bone.parent.WorldRotation * -2;
-			//		}
-
 			if (mode == Mode.Follow) {
 			if (mode == Mode.Follow) {
-				// MITCH : remove flipX
-				//			if (flip)
-				//				flipX = bone.flipX;
-
 				if (position)
 				if (position)
 					cachedTransform.localPosition = new Vector3(bone.x, bone.y, 0);
 					cachedTransform.localPosition = new Vector3(bone.x, bone.y, 0);
-
+				
 				if (rotation) {
 				if (rotation) {
-					if (bone.Data.InheritRotation) {
-						// MITCH : remove flipX
-						//if (bone.FlipX) {
-						//	cachedTransform.localRotation = Quaternion.Euler(0, 180, bone.rotationIK - flipCompensation);
-						//} else {
+					if (!bone.data.transformMode.InheritsRotation()) {
 						cachedTransform.localRotation = Quaternion.Euler(0, 0, bone.AppliedRotation);
 						cachedTransform.localRotation = Quaternion.Euler(0, 0, bone.AppliedRotation);
-						//}
 					} else {
 					} else {
 						Vector3 euler = skeletonTransform.rotation.eulerAngles;
 						Vector3 euler = skeletonTransform.rotation.eulerAngles;
 						cachedTransform.rotation = Quaternion.Euler(euler.x, euler.y, euler.z + (bone.WorldRotationX * skeletonFlipRotation));
 						cachedTransform.rotation = Quaternion.Euler(euler.x, euler.y, euler.z + (bone.WorldRotationX * skeletonFlipRotation));
@@ -170,53 +132,32 @@ namespace Spine.Unity {
 				}
 				}
 
 
 				if (scale) {
 				if (scale) {
-					cachedTransform.localScale = new Vector3(bone.scaleX, bone.scaleY, bone.WorldSignX);
-					// MITCH : nonuniform scale
-					//nonUniformScaleWarning = (bone.scaleX != bone.scaleY);
-					disableInheritScaleWarning = !bone.data.inheritScale;
+					cachedTransform.localScale = new Vector3(bone.scaleX, bone.scaleY, 1f);//, bone.WorldSignX);
+					incompatibleTransformMode = BoneTransformModeIncompatible(bone);
 				}
 				}
-
 			} else if (mode == Mode.Override) {
 			} else if (mode == Mode.Override) {
 				if (transformLerpComplete)
 				if (transformLerpComplete)
 					return;
 					return;
 
 
 				if (parentReference == null) {
 				if (parentReference == null) {
 					if (position) {
 					if (position) {
-						bone.x = Mathf.Lerp(bone.x, cachedTransform.localPosition.x, overrideAlpha);
-						bone.y = Mathf.Lerp(bone.y, cachedTransform.localPosition.y, overrideAlpha);
+						Vector3 clp = cachedTransform.localPosition;
+						bone.x = Mathf.Lerp(bone.x, clp.x, overrideAlpha);
+						bone.y = Mathf.Lerp(bone.y, clp.y, overrideAlpha);
 					}
 					}
 
 
 					if (rotation) {
 					if (rotation) {
 						float angle = Mathf.LerpAngle(bone.Rotation, cachedTransform.localRotation.eulerAngles.z, overrideAlpha);
 						float angle = Mathf.LerpAngle(bone.Rotation, cachedTransform.localRotation.eulerAngles.z, overrideAlpha);
-
-						// MITCH : remove flipX
-						//					float angle = Mathf.LerpAngle(bone.Rotation, cachedTransform.localRotation.eulerAngles.z, overrideAlpha) + flipCompensation;
-						//					if (flip) {
-						//                        
-						//						if ((!flipX && bone.flipX)) {
-						//							angle -= flipCompensation;
-						//						}
-						//
-						//						//TODO fix this...
-						//						if (angle >= 360)
-						//							angle -= 360;
-						//						else if (angle <= -360)
-						//							angle += 360;
-						//					}
 						bone.Rotation = angle;
 						bone.Rotation = angle;
 						bone.AppliedRotation = angle;
 						bone.AppliedRotation = angle;
 					}
 					}
 
 
 					if (scale) {
 					if (scale) {
-						bone.scaleX = Mathf.Lerp(bone.scaleX, cachedTransform.localScale.x, overrideAlpha);
-						bone.scaleY = Mathf.Lerp(bone.scaleY, cachedTransform.localScale.y, overrideAlpha);
-						// MITCH : nonuniform scale
-						//nonUniformScaleWarning = (bone.scaleX != bone.scaleY);
+						Vector3 cls = cachedTransform.localScale;
+						bone.scaleX = Mathf.Lerp(bone.scaleX, cls.x, overrideAlpha);
+						bone.scaleY = Mathf.Lerp(bone.scaleY, cls.y, overrideAlpha);
 					}
 					}
 
 
-					// MITCH : remove flipX
-					//if (flip)
-					//	bone.flipX = flipX;
 				} else {
 				} else {
 					if (transformLerpComplete)
 					if (transformLerpComplete)
 						return;
 						return;
@@ -229,81 +170,35 @@ namespace Spine.Unity {
 
 
 					// MITCH
 					// MITCH
 					if (rotation) {
 					if (rotation) {
-						float angle = Mathf.LerpAngle(bone.Rotation, Quaternion.LookRotation(flipX ? Vector3.forward * -1 : Vector3.forward, parentReference.InverseTransformDirection(cachedTransform.up)).eulerAngles.z, overrideAlpha);
-
-						// MITCH : remove flipX
-						//					float angle = Mathf.LerpAngle(bone.Rotation, Quaternion.LookRotation(flipX ? Vector3.forward * -1 : Vector3.forward, parentReference.InverseTransformDirection(cachedTransform.up)).eulerAngles.z, overrideAlpha) + flipCompensation;
-						//					if (flip) {
-						//                        
-						//						if ((!flipX && bone.flipX)) {
-						//							angle -= flipCompensation;
-						//						}
-						//
-						//						//TODO fix this...
-						//						if (angle >= 360)
-						//							angle -= 360;
-						//						else if (angle <= -360)
-						//							angle += 360;
-						//					}
+						float angle = Mathf.LerpAngle(bone.Rotation, Quaternion.LookRotation(Vector3.forward, parentReference.InverseTransformDirection(cachedTransform.up)).eulerAngles.z, overrideAlpha);
 						bone.Rotation = angle;
 						bone.Rotation = angle;
 						bone.AppliedRotation = angle;
 						bone.AppliedRotation = angle;
 					}
 					}
 
 
 					if (scale) {
 					if (scale) {
-						bone.scaleX = Mathf.Lerp(bone.scaleX, cachedTransform.localScale.x, overrideAlpha);
-						bone.scaleY = Mathf.Lerp(bone.scaleY, cachedTransform.localScale.y, overrideAlpha);
-						// MITCH : nonuniform scale
-						//nonUniformScaleWarning = (bone.scaleX != bone.scaleY);
+						Vector3 cls = cachedTransform.localScale;
+						bone.scaleX = Mathf.Lerp(bone.scaleX, cls.x, overrideAlpha);
+						bone.scaleY = Mathf.Lerp(bone.scaleY, cls.y, overrideAlpha);
 					}
 					}
 
 
-					disableInheritScaleWarning = !bone.data.inheritScale;
-
-					// MITCH : remove flipX
-					//if (flip)
-					//	bone.flipX = flipX;
+					incompatibleTransformMode = BoneTransformModeIncompatible(bone);
 				}
 				}
 
 
 				transformLerpComplete = true;
 				transformLerpComplete = true;
 			}
 			}
 		}
 		}
 
 
-		// MITCH : remove flipX
-		//	public void FlipX (bool state) {
-		//		if (state != flipX) {
-		//			flipX = state;
-		//			if (flipX && Mathf.Abs(transform.localRotation.eulerAngles.y) > 90) {
-		//				skeletonUtility.skeletonAnimation.LateUpdate();
-		//				return;
-		//			} else if (!flipX && Mathf.Abs(transform.localRotation.eulerAngles.y) < 90) {
-		//				skeletonUtility.skeletonAnimation.LateUpdate();
-		//				return;
-		//			}
-		//		}
-		//
-		//        
-		//		bone.FlipX = state;
-		//		transform.RotateAround(transform.position, skeletonUtility.transform.up, 180);
-		//		Vector3 euler = transform.localRotation.eulerAngles;
-		//		euler.x = 0;
-		//        
-		//		euler.y = bone.FlipX ? 180 : 0;
-		//        euler.y = 0;
-		//		transform.localRotation = Quaternion.Euler(euler);
-		//	}
+		public static bool BoneTransformModeIncompatible (Bone bone) {
+			return !bone.data.transformMode.InheritsScale();
+		}
 
 
 		public void AddBoundingBox (string skinName, string slotName, string attachmentName) {
 		public void AddBoundingBox (string skinName, string slotName, string attachmentName) {
 			SkeletonUtility.AddBoundingBox(bone.skeleton, skinName, slotName, attachmentName, transform);
 			SkeletonUtility.AddBoundingBox(bone.skeleton, skinName, slotName, attachmentName, transform);
 		}
 		}
 
 
-
 		#if UNITY_EDITOR
 		#if UNITY_EDITOR
 		void OnDrawGizmos () {
 		void OnDrawGizmos () {
-			// MITCH : nonuniform scale
-			//		if (NonUniformScaleWarning) {
-			//			Gizmos.DrawIcon(transform.position + new Vector3(0, 0.128f, 0), "icon-warning");
-			//		}
-
-			if (DisableInheritScaleWarning)
+			if (IncompatibleTransformMode)
 				Gizmos.DrawIcon(transform.position + new Vector3(0, 0.128f, 0), "icon-warning");		
 				Gizmos.DrawIcon(transform.position + new Vector3(0, 0.128f, 0), "icon-warning");		
 		}
 		}
 		#endif
 		#endif

+ 1 - 1
spine-unity/Assets/spine-unity/SkeletonUtility/SkeletonUtilityConstraint.cs

@@ -40,7 +40,7 @@ namespace Spine.Unity {
 
 
 		protected virtual void OnEnable () {
 		protected virtual void OnEnable () {
 			utilBone = GetComponent<SkeletonUtilityBone>();
 			utilBone = GetComponent<SkeletonUtilityBone>();
-			skeletonUtility = SkeletonUtility.GetInParent<SkeletonUtility>(transform);
+			skeletonUtility = transform.GetComponentInParent<SkeletonUtility>();
 			skeletonUtility.RegisterConstraint(this);
 			skeletonUtility.RegisterConstraint(this);
 		}
 		}
 
 

+ 3 - 1
spine-unity/README.md

@@ -14,10 +14,12 @@ The Spine Runtimes are developed with the intent to be used with data exported f
 
 
 ## Spine version
 ## Spine version
 
 
-spine-unity works with data exported from Spine 3.4.02.
+spine-unity works with data exported from Spine 3.5.x.
 
 
 spine-unity supports all Spine features.
 spine-unity supports all Spine features.
 
 
+Unity's physics components do not support dynamically assigned vertices so they cannot be used to mirror bone-weighted and deformed BoundingBoxAttachments. However, BoundingBoxAttachment vertices at runtime will still deform correctly and can be used to perform manual hit detection.
+
 ## Documentation
 ## Documentation
 
 
 A Spine skeleton GameObject (a GameObject with a SkeletonAnimation component on it) can be used throughout Unity like any other GameObject. It renders through `MeshRenderer`.
 A Spine skeleton GameObject (a GameObject with a SkeletonAnimation component on it) can be used throughout Unity like any other GameObject. It renders through `MeshRenderer`.

+ 16 - 15
spine-xna/LICENSE

@@ -1,16 +1,17 @@
-Spine Runtimes Software License v2.5
+Spine Runtimes Software License
+Version 2.3
 
 
-Copyright (c) 2013-2016, Esoteric Software
+Copyright (c) 2013-2015, Esoteric Software
 All rights reserved.
 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 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 develop new applications using the Spine Runtimes or otherwise
-create derivative works or improvements of the Spine Runtimes or (b) remove,
-delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+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
 or other intellectual property or proprietary rights notices on or in the
 Software, including any copy thereof. Redistributions in binary or source
 Software, including any copy thereof. Redistributions in binary or source
 form must include this license and terms.
 form must include this license and terms.
@@ -20,8 +21,8 @@ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF
-USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
-IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGE.
+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.

+ 1 - 1
spine-xna/README.md

@@ -10,7 +10,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f
 
 
 ## Spine version
 ## Spine version
 
 
-spine-xna works with data exported from Spine 3.4.02.
+spine-xna works with data exported from Spine 3.5.x.
 
 
 spine-xna supports all Spine features.
 spine-xna supports all Spine features.
 
 

+ 8 - 8
spine-xna/example/src/ExampleGame.cs

@@ -179,27 +179,27 @@ namespace Spine {
 			base.Draw(gameTime);
 			base.Draw(gameTime);
 		}
 		}
 
 
-		public void Start (AnimationState state, int trackIndex) {
+		public void Start (TrackEntry entry) {
 #if !WINDOWS_STOREAPP
 #if !WINDOWS_STOREAPP
-			Console.WriteLine(trackIndex + " " + state.GetCurrent(trackIndex) + ": start");
+			Console.WriteLine(entry + ": start");
 #endif
 #endif
 		}
 		}
 
 
-		public void End (AnimationState state, int trackIndex) {
+		public void End (TrackEntry entry) {
 #if !WINDOWS_STOREAPP	
 #if !WINDOWS_STOREAPP	
-			Console.WriteLine(trackIndex + " " + state.GetCurrent(trackIndex) + ": end");
+			Console.WriteLine(entry + ": end");
 #endif
 #endif
 		}
 		}
 
 
-		public void Complete (AnimationState state, int trackIndex, int loopCount) {
+		public void Complete (TrackEntry entry) {
 #if !WINDOWS_STOREAPP	
 #if !WINDOWS_STOREAPP	
-			Console.WriteLine(trackIndex + " " + state.GetCurrent(trackIndex) + ": complete " + loopCount);
+			Console.WriteLine(entry + ": complete ");
 #endif
 #endif
 		}
 		}
 
 
-		public void Event (AnimationState state, int trackIndex, Event e) {
+		public void Event (TrackEntry entry, Event e) {
 #if !WINDOWS_STOREAPP	
 #if !WINDOWS_STOREAPP	
-			Console.WriteLine(trackIndex + " " + state.GetCurrent(trackIndex) + ": event " + e);
+			Console.WriteLine(entry + ": event " + e);
 #endif
 #endif
 		}
 		}
 	}
 	}

+ 0 - 1
spine-xna/example/src/ExampleProgram.cs

@@ -1,5 +1,4 @@
 
 
-
 using System;
 using System;
 
 
 namespace Spine {
 namespace Spine {

+ 20 - 19
spine-xna/src/MeshBatcher.cs

@@ -1,31 +1,32 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, Esoteric Software
+ * Spine Runtimes Software License
+ * Version 2.3
+ * 
+ * Copyright (c) 2013-2015, Esoteric Software
  * All rights reserved.
  * 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 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 develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 
+ * 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
  * or other intellectual property or proprietary rights notices on or in the
  * Software, including any copy thereof. Redistributions in binary or source
  * Software, including any copy thereof. Redistributions in binary or source
  * form must include this license and terms.
  * form must include this license and terms.
- *
+ * 
  * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
  * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
  * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
+ * 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;
@@ -37,7 +38,7 @@ namespace Spine {
 	// #region License
 	// #region License
 	// /*
 	// /*
 	// Microsoft Public License (Ms-PL)
 	// Microsoft Public License (Ms-PL)
-	// MonoGame - Copyright ? 2009 The MonoGame Team
+	// MonoGame - Copyright © 2009 The MonoGame Team
 	//
 	//
 	// All rights reserved.
 	// All rights reserved.
 	//
 	//

+ 20 - 19
spine-xna/src/RegionBatcher.cs

@@ -1,31 +1,32 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, Esoteric Software
+ * Spine Runtimes Software License
+ * Version 2.3
+ * 
+ * Copyright (c) 2013-2015, Esoteric Software
  * All rights reserved.
  * 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 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 develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 
+ * 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
  * or other intellectual property or proprietary rights notices on or in the
  * Software, including any copy thereof. Redistributions in binary or source
  * Software, including any copy thereof. Redistributions in binary or source
  * form must include this license and terms.
  * form must include this license and terms.
- *
+ * 
  * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
  * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
  * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
+ * 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;
@@ -37,7 +38,7 @@ namespace Spine {
 	// #region License
 	// #region License
 	// /*
 	// /*
 	// Microsoft Public License (Ms-PL)
 	// Microsoft Public License (Ms-PL)
-	// MonoGame - Copyright ? 2009 The MonoGame Team
+	// MonoGame - Copyright © 2009 The MonoGame Team
 	//
 	//
 	// All rights reserved.
 	// All rights reserved.
 	//
 	//

+ 19 - 18
spine-xna/src/SkeletonMeshRenderer.cs

@@ -1,31 +1,32 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, Esoteric Software
+ * Spine Runtimes Software License
+ * Version 2.3
+ * 
+ * Copyright (c) 2013-2015, Esoteric Software
  * All rights reserved.
  * 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 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 develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 
+ * 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
  * or other intellectual property or proprietary rights notices on or in the
  * Software, including any copy thereof. Redistributions in binary or source
  * Software, including any copy thereof. Redistributions in binary or source
  * form must include this license and terms.
  * form must include this license and terms.
- *
+ * 
  * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
  * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
  * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
+ * 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;

+ 19 - 18
spine-xna/src/SkeletonRegionRenderer.cs

@@ -1,31 +1,32 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, Esoteric Software
+ * Spine Runtimes Software License
+ * Version 2.3
+ * 
+ * Copyright (c) 2013-2015, Esoteric Software
  * All rights reserved.
  * 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 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 develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 
+ * 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
  * or other intellectual property or proprietary rights notices on or in the
  * Software, including any copy thereof. Redistributions in binary or source
  * Software, including any copy thereof. Redistributions in binary or source
  * form must include this license and terms.
  * form must include this license and terms.
- *
+ * 
  * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
  * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
  * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
+ * 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;

+ 22 - 21
spine-xna/src/Util.cs

@@ -1,33 +1,34 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, Esoteric Software
+ * Spine Runtimes Software License
+ * Version 2.3
+ * 
+ * Copyright (c) 2013-2015, Esoteric Software
  * All rights reserved.
  * 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 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 develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 
+ * 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
  * or other intellectual property or proprietary rights notices on or in the
  * Software, including any copy thereof. Redistributions in binary or source
  * Software, including any copy thereof. Redistributions in binary or source
  * form must include this license and terms.
  * form must include this license and terms.
- *
+ * 
  * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
  * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
  * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
+ * 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;
 using System.IO;
 using System.IO;
 using Microsoft.Xna.Framework;
 using Microsoft.Xna.Framework;
@@ -78,4 +79,4 @@ namespace Spine {
 			return Texture2D.FromStream(device, input);
 			return Texture2D.FromStream(device, input);
 		}
 		}
 	}
 	}
-}
+}

+ 19 - 18
spine-xna/src/XnaTextureLoader.cs

@@ -1,31 +1,32 @@
 /******************************************************************************
 /******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, Esoteric Software
+ * Spine Runtimes Software License
+ * Version 2.3
+ * 
+ * Copyright (c) 2013-2015, Esoteric Software
  * All rights reserved.
  * 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 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 develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 
+ * 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
  * or other intellectual property or proprietary rights notices on or in the
  * Software, including any copy thereof. Redistributions in binary or source
  * Software, including any copy thereof. Redistributions in binary or source
  * form must include this license and terms.
  * form must include this license and terms.
- *
+ * 
  * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
  * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
  * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
+ * 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;

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio