Browse Source

Updated License Headers

Mario Zechner 9 years ago
parent
commit
f84ae17615
48 changed files with 7031 additions and 5726 deletions
  1. 63 32
      spine-as3/spine-as3/src/spine/animation/ShearTimeline.as
  2. 49 49
      spine-csharp/src/Attachments/PathAttachment.cs
  3. 118 118
      spine-csharp/src/Attachments/VertexAttachment.cs
  4. 400 400
      spine-csharp/src/PathConstraint.cs
  5. 74 74
      spine-csharp/src/PathConstraintData.cs
  6. 466 436
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java
  7. 151 121
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java
  8. 186 156
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraint.java
  9. 147 117
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraintData.java
  10. 34 4
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Updatable.java
  11. 96 66
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/SkeletonActor.java
  12. 131 101
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/SkeletonActorPool.java
  13. 56 26
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/SkeletonPool.java
  14. 143 113
      spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/JsonRollback.java
  15. 96 65
      spine-ts/core/src/Texture.ts
  16. 0 18
      spine-ts/webgl/src/LoadingScreen.ts
  17. 91 60
      spine-unity/Assets/Examples/Getting Started/Scripts/SpineBeginnerTwo.cs
  18. 50 19
      spine-unity/Assets/Examples/Getting Started/Scripts/SpineBlinkPlayer.cs
  19. 63 32
      spine-unity/Assets/Examples/Getting Started/Scripts/SpineboyBeginnerInput.cs
  20. 113 82
      spine-unity/Assets/Examples/Getting Started/Scripts/SpineboyBeginnerModel.cs
  21. 136 105
      spine-unity/Assets/Examples/Getting Started/Scripts/SpineboyBeginnerView.cs
  22. 116 85
      spine-unity/Assets/Examples/Scripts/RaggedySpineboy.cs
  23. 73 42
      spine-unity/Assets/Examples/Scripts/SpineGauge.cs
  24. 80 50
      spine-unity/Assets/Examples/Scripts/SpineboyPole.cs
  25. 32 1
      spine-unity/Assets/spine-unity/Editor/AssetDatabaseAvailabilityDetector.cs
  26. 161 130
      spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSimpleMeshGenerator.cs
  27. 237 206
      spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSubmeshSetMeshGenerator.cs
  28. 319 288
      spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSubmeshedMeshGenerator.cs
  29. 44 13
      spine-unity/Assets/spine-unity/Mesh Generation/DoubleBuffered.cs
  30. 44 13
      spine-unity/Assets/spine-unity/Mesh Generation/DoubleBufferedMesh.cs
  31. 46 15
      spine-unity/Assets/spine-unity/Mesh Generation/ISimpleMeshGenerator.cs
  32. 123 92
      spine-unity/Assets/spine-unity/Mesh Generation/ISubmeshedMeshGenerator.cs
  33. 48 17
      spine-unity/Assets/spine-unity/Mesh Generation/SpineMesh.cs
  34. 166 135
      spine-unity/Assets/spine-unity/Modules/CustomMaterials/Editor/SkeletonRendererCustomMaterialsInspector.cs
  35. 201 170
      spine-unity/Assets/spine-unity/Modules/CustomMaterials/SkeletonRendererCustomMaterials.cs
  36. 186 155
      spine-unity/Assets/spine-unity/Modules/Ghost/SkeletonGhost.cs
  37. 40 9
      spine-unity/Assets/spine-unity/Modules/Ragdoll/Editor/SkeletonRagdoll2DInspector.cs
  38. 49 18
      spine-unity/Assets/spine-unity/Modules/Ragdoll/Editor/SkeletonRagdollInspector.cs
  39. 407 376
      spine-unity/Assets/spine-unity/Modules/Ragdoll/SkeletonRagdoll.cs
  40. 440 409
      spine-unity/Assets/spine-unity/Modules/Ragdoll/SkeletonRagdoll2D.cs
  41. 261 230
      spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/Editor/SkeletonGraphicInspector.cs
  42. 249 218
      spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/SkeletonGraphic.cs
  43. 77 46
      spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonPartsRendererInspector.cs
  44. 311 280
      spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonRenderSeparatorInspector.cs
  45. 124 93
      spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonPartsRenderer.cs
  46. 203 172
      spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderSeparator.cs
  47. 124 93
      spine-unity/Assets/spine-unity/Modules/YieldInstructions/WaitForSpineAnimationComplete.cs
  48. 207 176
      spine-unity/Assets/spine-unity/Modules/YieldInstructions/WaitForSpineEvent.cs

+ 63 - 32
spine-as3/spine-as3/src/spine/animation/ShearTimeline.as

@@ -1,34 +1,65 @@
-package spine.animation {
-	import spine.Event;
-	import spine.Skeleton;
-	import spine.Bone;
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
 
-public class ShearTimeline extends TranslateTimeline {
-	public function ShearTimeline (frameCount:int) {
-		super(frameCount);
-	}
-
-	override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number) : void {
-		var frames:Vector.<Number> = this.frames;
-		if (time < frames[0]) return; // Time is before first frame.
-
-		var bone:Bone = skeleton.bones[boneIndex];
-		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.
-		var frame:int = Animation.binarySearch(frames, time, ENTRIES);
-		var prevX:Number = frames[frame + PREV_X];
-		var prevY:Number = frames[frame + PREV_Y];
-		var frameTime:Number = frames[frame];
-		var percent:Number = 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;
-	}
+package spine.animation {
+	import spine.Event;
+	import spine.Skeleton;
+	import spine.Bone;
+
+public class ShearTimeline extends TranslateTimeline {
+	public function ShearTimeline (frameCount:int) {
+		super(frameCount);
+	}
+
+	override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number) : void {
+		var frames:Vector.<Number> = this.frames;
+		if (time < frames[0]) return; // Time is before first frame.
+
+		var bone:Bone = skeleton.bones[boneIndex];
+		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.
+		var frame:int = Animation.binarySearch(frames, time, ENTRIES);
+		var prevX:Number = frames[frame + PREV_X];
+		var prevY:Number = frames[frame + PREV_Y];
+		var frameTime:Number = frames[frame];
+		var percent:Number = 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;
+	}
+}
+
 }
-
-}

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

@@ -1,49 +1,49 @@
-/******************************************************************************
- * Spine Runtimes Software License
- * Version 2.3
- * 
- * Copyright (c) 2013-2015, Esoteric Software
- * All rights reserved.
- * 
- * You are granted a perpetual, non-exclusive, non-sublicensable and
- * non-transferable license to use, install, execute and perform the Spine
- * Runtimes Software (the "Software") and derivative works solely for personal
- * or internal use. Without the written permission of Esoteric Software (see
- * Section 2 of the Spine Software License Agreement), you may not (a) modify,
- * translate, adapt or otherwise create derivative works, improvements of the
- * Software or develop new applications using the Software or (b) remove,
- * delete, alter or obscure any trademarks or any copyright, trademark, patent
- * or other intellectual property or proprietary rights notices on or in the
- * Software, including any copy thereof. Redistributions in binary or source
- * form must include this license and terms.
- * 
- * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
- * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
- * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
- * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-using System;
-using System.Collections.Generic;
-
-namespace Spine {
-	public class PathAttachment : VertexAttachment {
-		internal float[] lengths;
-		internal bool closed, constantSpeed;
-
-		/// <summary>The length in the setup pose from the start of the path to the end of each curve.</summary>
-		public float[] Lengths { get { return lengths; } set { lengths = value; } }
-		public bool Closed { get { return closed; } set { closed = value; } }
-		public bool ConstantSpeed { get { return constantSpeed; } set { constantSpeed = value; } }
-
-		public PathAttachment (String name)
-			: base(name) {
-		}			
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License
+ * Version 2.3
+ * 
+ * Copyright (c) 2013-2015, Esoteric Software
+ * All rights reserved.
+ * 
+ * You are granted a perpetual, non-exclusive, non-sublicensable and
+ * non-transferable license to use, install, execute and perform the Spine
+ * Runtimes Software (the "Software") and derivative works solely for personal
+ * or internal use. Without the written permission of Esoteric Software (see
+ * Section 2 of the Spine Software License Agreement), you may not (a) modify,
+ * translate, adapt or otherwise create derivative works, improvements of the
+ * Software or develop new applications using the Software or (b) remove,
+ * delete, alter or obscure any trademarks or any copyright, trademark, patent
+ * or other intellectual property or proprietary rights notices on or in the
+ * Software, including any copy thereof. Redistributions in binary or source
+ * form must include this license and terms.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+using System.Collections.Generic;
+
+namespace Spine {
+	public class PathAttachment : VertexAttachment {
+		internal float[] lengths;
+		internal bool closed, constantSpeed;
+
+		/// <summary>The length in the setup pose from the start of the path to the end of each curve.</summary>
+		public float[] Lengths { get { return lengths; } set { lengths = value; } }
+		public bool Closed { get { return closed; } set { closed = value; } }
+		public bool ConstantSpeed { get { return constantSpeed; } set { constantSpeed = value; } }
+
+		public PathAttachment (String name)
+			: base(name) {
+		}			
+	}
+}

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

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

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

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

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

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

+ 466 - 436
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraint.java

@@ -1,436 +1,466 @@
-
-package com.esotericsoftware.spine;
-
-import static com.badlogic.gdx.math.MathUtils.*;
-
-import com.badlogic.gdx.utils.Array;
-import com.badlogic.gdx.utils.FloatArray;
-import com.esotericsoftware.spine.PathConstraintData.PositionMode;
-import com.esotericsoftware.spine.PathConstraintData.RotateMode;
-import com.esotericsoftware.spine.PathConstraintData.SpacingMode;
-import com.esotericsoftware.spine.attachments.Attachment;
-import com.esotericsoftware.spine.attachments.PathAttachment;
-
-public class PathConstraint implements Updatable {
-	static private final int NONE = -1, BEFORE = -2, AFTER = -3;
-
-	final PathConstraintData data;
-	final Array<Bone> bones;
-	Slot target;
-	float position, spacing, rotateMix, translateMix;
-
-	private final FloatArray spaces = new FloatArray(), positions = new FloatArray();
-	private final FloatArray world = new FloatArray(), curves = new FloatArray(), lengths = new FloatArray();
-	private final float[] segments = new float[10];
-
-	public PathConstraint (PathConstraintData data, Skeleton skeleton) {
-		if (data == null) throw new IllegalArgumentException("data cannot be null.");
-		if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
-		this.data = data;
-		bones = new Array(data.bones.size);
-		for (BoneData boneData : data.bones)
-			bones.add(skeleton.findBone(boneData.name));
-		target = skeleton.findSlot(data.target.name);
-		position = data.position;
-		spacing = data.spacing;
-		rotateMix = data.rotateMix;
-		translateMix = data.translateMix;
-	}
-
-	/** Copy constructor. */
-	public PathConstraint (PathConstraint constraint, Skeleton skeleton) {
-		if (constraint == null) throw new IllegalArgumentException("constraint cannot be null.");
-		if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
-		data = constraint.data;
-		bones = new Array(constraint.bones.size);
-		for (Bone bone : constraint.bones)
-			bones.add(skeleton.bones.get(bone.data.index));
-		target = skeleton.slots.get(constraint.target.data.index);
-		position = constraint.position;
-		spacing = constraint.spacing;
-		rotateMix = constraint.rotateMix;
-		translateMix = constraint.translateMix;
-	}
-
-	public void apply () {
-		update();
-	}
-
-	@SuppressWarnings("null")
-	public void update () {
-		Attachment attachment = target.attachment;
-		if (!(attachment instanceof PathAttachment)) return;
-
-		float rotateMix = this.rotateMix, translateMix = this.translateMix;
-		boolean translate = translateMix > 0, rotate = rotateMix > 0;
-		if (!translate && !rotate) return;
-
-		PathConstraintData data = this.data;
-		SpacingMode spacingMode = data.spacingMode;
-		boolean lengthSpacing = spacingMode == SpacingMode.length;
-		RotateMode rotateMode = data.rotateMode;
-		boolean tangents = rotateMode == RotateMode.tangent, scale = rotateMode == RotateMode.chainScale;
-		int boneCount = this.bones.size, spacesCount = tangents ? boneCount : boneCount + 1;
-		Object[] bones = this.bones.items;
-		float[] spaces = this.spaces.setSize(spacesCount), lengths = null;
-		float spacing = this.spacing;
-		if (scale || lengthSpacing) {
-			if (scale) lengths = this.lengths.setSize(boneCount);
-			for (int i = 0, n = spacesCount - 1; i < n;) {
-				Bone bone = (Bone)bones[i];
-				float length = bone.data.length, x = length * bone.a, y = length * bone.c;
-				length = (float)Math.sqrt(x * x + y * y);
-				if (scale) lengths[i] = length;
-				spaces[++i] = lengthSpacing ? Math.max(0, length + spacing) : spacing;
-			}
-		} else {
-			for (int i = 1; i < spacesCount; i++)
-				spaces[i] = spacing;
-		}
-
-		float[] positions = computeWorldPositions((PathAttachment)attachment, spacesCount, tangents,
-			data.positionMode == PositionMode.percent, spacingMode == SpacingMode.percent);
-		Skeleton skeleton = target.getSkeleton();
-		float skeletonX = skeleton.x, skeletonY = skeleton.y;
-		float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation;
-		boolean tip = rotateMode == RotateMode.chain && offsetRotation == 0;
-		for (int i = 0, p = 3; i < boneCount; i++, p += 3) {
-			Bone bone = (Bone)bones[i];
-			bone.worldX += (boneX - skeletonX - bone.worldX) * translateMix;
-			bone.worldY += (boneY - skeletonY - bone.worldY) * translateMix;
-			float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY;
-			if (scale) {
-				float length = lengths[i];
-				if (length != 0) {
-					float s = ((float)Math.sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1;
-					bone.a *= s;
-					bone.c *= s;
-				}
-			}
-			boneX = x;
-			boneY = y;
-			if (rotate) {
-				float a = bone.a, b = bone.b, c = bone.c, d = bone.d, r, cos, sin;
-				if (tangents)
-					r = positions[p - 1];
-				else if (spaces[i + 1] == 0)
-					r = positions[p + 2];
-				else
-					r = atan2(dy, dx);
-				r -= atan2(c, a) - offsetRotation * degRad;
-				if (tip) {
-					cos = cos(r);
-					sin = sin(r);
-					float length = bone.data.length;
-					boneX += (length * (cos * a - sin * c) - dx) * rotateMix;
-					boneY += (length * (sin * a + cos * c) - dy) * rotateMix;
-				}
-				if (r > PI)
-					r -= PI2;
-				else if (r < -PI) //
-					r += PI2;
-				r *= rotateMix;
-				cos = cos(r);
-				sin = sin(r);
-				bone.a = cos * a - sin * c;
-				bone.b = cos * b - sin * d;
-				bone.c = sin * a + cos * c;
-				bone.d = sin * b + cos * d;
-			}
-		}
-	}
-
-	float[] computeWorldPositions (PathAttachment path, int spacesCount, boolean tangents, boolean percentPosition,
-		boolean percentSpacing) {
-		Slot target = this.target;
-		float position = this.position;
-		float[] spaces = this.spaces.items, out = this.positions.setSize(spacesCount * 3 + 2), world;
-		boolean closed = path.getClosed();
-		int verticesLength = path.getWorldVerticesLength(), curveCount = verticesLength / 6, prevCurve = NONE;
-
-		if (!path.getConstantSpeed()) {
-			float[] lengths = path.getLengths();
-			curveCount -= closed ? 1 : 2;
-			float pathLength = lengths[curveCount];
-			if (percentPosition) position *= pathLength;
-			if (percentSpacing) {
-				for (int i = 0; i < spacesCount; i++)
-					spaces[i] *= pathLength;
-			}
-			world = this.world.setSize(8);
-			for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) {
-				float space = spaces[i];
-				position += space;
-				float p = position;
-
-				if (closed) {
-					p %= pathLength;
-					if (p < 0) p += pathLength;
-					curve = 0;
-				} else if (p < 0) {
-					if (prevCurve != BEFORE) {
-						prevCurve = BEFORE;
-						path.computeWorldVertices(target, 2, 4, world, 0);
-					}
-					addBeforePosition(p, world, 0, out, o);
-					continue;
-				} else if (p > pathLength) {
-					if (prevCurve != AFTER) {
-						prevCurve = AFTER;
-						path.computeWorldVertices(target, verticesLength - 6, 4, world, 0);
-					}
-					addAfterPosition(p - pathLength, world, 0, out, o);
-					continue;
-				}
-
-				// Determine curve containing position.
-				for (;; curve++) {
-					float length = lengths[curve];
-					if (p > length) continue;
-					if (curve == 0)
-						p /= length;
-					else {
-						float prev = lengths[curve - 1];
-						p = (p - prev) / (length - prev);
-					}
-					break;
-				}
-				if (curve != prevCurve) {
-					prevCurve = curve;
-					if (closed && curve == curveCount) {
-						path.computeWorldVertices(target, verticesLength - 4, 4, world, 0);
-						path.computeWorldVertices(target, 0, 4, world, 4);
-					} else
-						path.computeWorldVertices(target, curve * 6 + 2, 8, world, 0);
-				}
-				addCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], out, o,
-					tangents || (i > 0 && space == 0));
-			}
-			return out;
-		}
-
-		// World vertices.
-		if (closed) {
-			verticesLength += 2;
-			world = this.world.setSize(verticesLength);
-			path.computeWorldVertices(target, 2, verticesLength - 4, world, 0);
-			path.computeWorldVertices(target, 0, 2, world, verticesLength - 4);
-			world[verticesLength - 2] = world[0];
-			world[verticesLength - 1] = world[1];
-		} else {
-			curveCount--;
-			verticesLength -= 4;
-			world = this.world.setSize(verticesLength);
-			path.computeWorldVertices(target, 2, verticesLength, world, 0);
-		}
-
-		// Curve lengths.
-		float[] curves = this.curves.setSize(curveCount);
-		float pathLength = 0;
-		float x1 = world[0], y1 = world[1], cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0, x2 = 0, y2 = 0;
-		float tmpx, tmpy, dddfx, dddfy, ddfx, ddfy, dfx, dfy;
-		for (int i = 0, w = 2; i < curveCount; i++, w += 6) {
-			cx1 = world[w];
-			cy1 = world[w + 1];
-			cx2 = world[w + 2];
-			cy2 = world[w + 3];
-			x2 = world[w + 4];
-			y2 = world[w + 5];
-			tmpx = (x1 - cx1 * 2 + cx2) * 0.1875f;
-			tmpy = (y1 - cy1 * 2 + cy2) * 0.1875f;
-			dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375f;
-			dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375f;
-			ddfx = tmpx * 2 + dddfx;
-			ddfy = tmpy * 2 + dddfy;
-			dfx = (cx1 - x1) * 0.75f + tmpx + dddfx * 0.16666667f;
-			dfy = (cy1 - y1) * 0.75f + tmpy + dddfy * 0.16666667f;
-			pathLength += (float)Math.sqrt(dfx * dfx + dfy * dfy);
-			dfx += ddfx;
-			dfy += ddfy;
-			ddfx += dddfx;
-			ddfy += dddfy;
-			pathLength += (float)Math.sqrt(dfx * dfx + dfy * dfy);
-			dfx += ddfx;
-			dfy += ddfy;
-			pathLength += (float)Math.sqrt(dfx * dfx + dfy * dfy);
-			dfx += ddfx + dddfx;
-			dfy += ddfy + dddfy;
-			pathLength += (float)Math.sqrt(dfx * dfx + dfy * dfy);
-			curves[i] = pathLength;
-			x1 = x2;
-			y1 = y2;
-		}
-		if (percentPosition) position *= pathLength;
-		if (percentSpacing) {
-			for (int i = 0; i < spacesCount; i++)
-				spaces[i] *= pathLength;
-		}
-
-		float[] segments = this.segments;
-		float curveLength = 0;
-		for (int i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) {
-			float space = spaces[i];
-			position += space;
-			float p = position;
-
-			if (closed) {
-				p %= pathLength;
-				if (p < 0) p += pathLength;
-				curve = 0;
-			} else if (p < 0) {
-				addBeforePosition(p, world, 0, out, o);
-				continue;
-			} else if (p > pathLength) {
-				addAfterPosition(p - pathLength, world, verticesLength - 4, out, o);
-				continue;
-			}
-
-			// Determine curve containing position.
-			for (;; curve++) {
-				float length = curves[curve];
-				if (p > length) continue;
-				if (curve == 0)
-					p /= length;
-				else {
-					float prev = curves[curve - 1];
-					p = (p - prev) / (length - prev);
-				}
-				break;
-			}
-
-			// Curve segment lengths.
-			if (curve != prevCurve) {
-				prevCurve = curve;
-				int ii = curve * 6;
-				x1 = world[ii];
-				y1 = world[ii + 1];
-				cx1 = world[ii + 2];
-				cy1 = world[ii + 3];
-				cx2 = world[ii + 4];
-				cy2 = world[ii + 5];
-				x2 = world[ii + 6];
-				y2 = world[ii + 7];
-				tmpx = (x1 - cx1 * 2 + cx2) * 0.03f;
-				tmpy = (y1 - cy1 * 2 + cy2) * 0.03f;
-				dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006f;
-				dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.006f;
-				ddfx = tmpx * 2 + dddfx;
-				ddfy = tmpy * 2 + dddfy;
-				dfx = (cx1 - x1) * 0.3f + tmpx + dddfx * 0.16666667f;
-				dfy = (cy1 - y1) * 0.3f + tmpy + dddfy * 0.16666667f;
-				curveLength = (float)Math.sqrt(dfx * dfx + dfy * dfy);
-				segments[0] = curveLength;
-				for (ii = 1; ii < 8; ii++) {
-					dfx += ddfx;
-					dfy += ddfy;
-					ddfx += dddfx;
-					ddfy += dddfy;
-					curveLength += (float)Math.sqrt(dfx * dfx + dfy * dfy);
-					segments[ii] = curveLength;
-				}
-				dfx += ddfx;
-				dfy += ddfy;
-				curveLength += (float)Math.sqrt(dfx * dfx + dfy * dfy);
-				segments[8] = curveLength;
-				dfx += ddfx + dddfx;
-				dfy += ddfy + dddfy;
-				curveLength += (float)Math.sqrt(dfx * dfx + dfy * dfy);
-				segments[9] = curveLength;
-				segment = 0;
-			}
-
-			// Weight by segment length.
-			p *= curveLength;
-			for (;; segment++) {
-				float length = segments[segment];
-				if (p > length) continue;
-				if (segment == 0)
-					p /= length;
-				else {
-					float prev = segments[segment - 1];
-					p = segment + (p - prev) / (length - prev);
-				}
-				break;
-			}
-			addCurvePosition(p * 0.1f, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents || (i > 0 && space == 0));
-		}
-		return out;
-	}
-
-	private void addBeforePosition (float p, float[] temp, int i, float[] out, int o) {
-		float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = atan2(dy, dx);
-		out[o] = x1 + p * cos(r);
-		out[o + 1] = y1 + p * sin(r);
-		out[o + 2] = r;
-	}
-
-	private void addAfterPosition (float p, float[] temp, int i, float[] out, int o) {
-		float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = atan2(dy, dx);
-		out[o] = x1 + p * cos(r);
-		out[o + 1] = y1 + p * sin(r);
-		out[o + 2] = r;
-	}
-
-	private void addCurvePosition (float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2,
-		float[] out, int o, boolean tangents) {
-		if (p == 0) p = 0.0001f;
-		float tt = p * p, ttt = tt * p, u = 1 - p, uu = u * u, uuu = uu * u;
-		float ut = u * p, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * p;
-		float x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt;
-		out[o] = x;
-		out[o + 1] = y;
-		if (tangents) out[o + 2] = atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt));
-	}
-
-	public float getPosition () {
-		return position;
-	}
-
-	public void setPosition (float position) {
-		this.position = position;
-	}
-
-	public float getSpacing () {
-		return spacing;
-	}
-
-	public void setSpacing (float spacing) {
-		this.spacing = spacing;
-	}
-
-	public float getRotateMix () {
-		return rotateMix;
-	}
-
-	public void setRotateMix (float rotateMix) {
-		this.rotateMix = rotateMix;
-	}
-
-	public float getTranslateMix () {
-		return translateMix;
-	}
-
-	public void setTranslateMix (float translateMix) {
-		this.translateMix = translateMix;
-	}
-
-	public Array<Bone> getBones () {
-		return bones;
-	}
-
-	public Slot getTarget () {
-		return target;
-	}
-
-	public void setTarget (Slot target) {
-		this.target = target;
-	}
-
-	public PathConstraintData getData () {
-		return data;
-	}
-
-	public String toString () {
-		return data.name;
-	}
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+package com.esotericsoftware.spine;
+
+import static com.badlogic.gdx.math.MathUtils.*;
+
+import com.badlogic.gdx.utils.Array;
+import com.badlogic.gdx.utils.FloatArray;
+import com.esotericsoftware.spine.PathConstraintData.PositionMode;
+import com.esotericsoftware.spine.PathConstraintData.RotateMode;
+import com.esotericsoftware.spine.PathConstraintData.SpacingMode;
+import com.esotericsoftware.spine.attachments.Attachment;
+import com.esotericsoftware.spine.attachments.PathAttachment;
+
+public class PathConstraint implements Updatable {
+	static private final int NONE = -1, BEFORE = -2, AFTER = -3;
+
+	final PathConstraintData data;
+	final Array<Bone> bones;
+	Slot target;
+	float position, spacing, rotateMix, translateMix;
+
+	private final FloatArray spaces = new FloatArray(), positions = new FloatArray();
+	private final FloatArray world = new FloatArray(), curves = new FloatArray(), lengths = new FloatArray();
+	private final float[] segments = new float[10];
+
+	public PathConstraint (PathConstraintData data, Skeleton skeleton) {
+		if (data == null) throw new IllegalArgumentException("data cannot be null.");
+		if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
+		this.data = data;
+		bones = new Array(data.bones.size);
+		for (BoneData boneData : data.bones)
+			bones.add(skeleton.findBone(boneData.name));
+		target = skeleton.findSlot(data.target.name);
+		position = data.position;
+		spacing = data.spacing;
+		rotateMix = data.rotateMix;
+		translateMix = data.translateMix;
+	}
+
+	/** Copy constructor. */
+	public PathConstraint (PathConstraint constraint, Skeleton skeleton) {
+		if (constraint == null) throw new IllegalArgumentException("constraint cannot be null.");
+		if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
+		data = constraint.data;
+		bones = new Array(constraint.bones.size);
+		for (Bone bone : constraint.bones)
+			bones.add(skeleton.bones.get(bone.data.index));
+		target = skeleton.slots.get(constraint.target.data.index);
+		position = constraint.position;
+		spacing = constraint.spacing;
+		rotateMix = constraint.rotateMix;
+		translateMix = constraint.translateMix;
+	}
+
+	public void apply () {
+		update();
+	}
+
+	@SuppressWarnings("null")
+	public void update () {
+		Attachment attachment = target.attachment;
+		if (!(attachment instanceof PathAttachment)) return;
+
+		float rotateMix = this.rotateMix, translateMix = this.translateMix;
+		boolean translate = translateMix > 0, rotate = rotateMix > 0;
+		if (!translate && !rotate) return;
+
+		PathConstraintData data = this.data;
+		SpacingMode spacingMode = data.spacingMode;
+		boolean lengthSpacing = spacingMode == SpacingMode.length;
+		RotateMode rotateMode = data.rotateMode;
+		boolean tangents = rotateMode == RotateMode.tangent, scale = rotateMode == RotateMode.chainScale;
+		int boneCount = this.bones.size, spacesCount = tangents ? boneCount : boneCount + 1;
+		Object[] bones = this.bones.items;
+		float[] spaces = this.spaces.setSize(spacesCount), lengths = null;
+		float spacing = this.spacing;
+		if (scale || lengthSpacing) {
+			if (scale) lengths = this.lengths.setSize(boneCount);
+			for (int i = 0, n = spacesCount - 1; i < n;) {
+				Bone bone = (Bone)bones[i];
+				float length = bone.data.length, x = length * bone.a, y = length * bone.c;
+				length = (float)Math.sqrt(x * x + y * y);
+				if (scale) lengths[i] = length;
+				spaces[++i] = lengthSpacing ? Math.max(0, length + spacing) : spacing;
+			}
+		} else {
+			for (int i = 1; i < spacesCount; i++)
+				spaces[i] = spacing;
+		}
+
+		float[] positions = computeWorldPositions((PathAttachment)attachment, spacesCount, tangents,
+			data.positionMode == PositionMode.percent, spacingMode == SpacingMode.percent);
+		Skeleton skeleton = target.getSkeleton();
+		float skeletonX = skeleton.x, skeletonY = skeleton.y;
+		float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation;
+		boolean tip = rotateMode == RotateMode.chain && offsetRotation == 0;
+		for (int i = 0, p = 3; i < boneCount; i++, p += 3) {
+			Bone bone = (Bone)bones[i];
+			bone.worldX += (boneX - skeletonX - bone.worldX) * translateMix;
+			bone.worldY += (boneY - skeletonY - bone.worldY) * translateMix;
+			float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY;
+			if (scale) {
+				float length = lengths[i];
+				if (length != 0) {
+					float s = ((float)Math.sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1;
+					bone.a *= s;
+					bone.c *= s;
+				}
+			}
+			boneX = x;
+			boneY = y;
+			if (rotate) {
+				float a = bone.a, b = bone.b, c = bone.c, d = bone.d, r, cos, sin;
+				if (tangents)
+					r = positions[p - 1];
+				else if (spaces[i + 1] == 0)
+					r = positions[p + 2];
+				else
+					r = atan2(dy, dx);
+				r -= atan2(c, a) - offsetRotation * degRad;
+				if (tip) {
+					cos = cos(r);
+					sin = sin(r);
+					float length = bone.data.length;
+					boneX += (length * (cos * a - sin * c) - dx) * rotateMix;
+					boneY += (length * (sin * a + cos * c) - dy) * rotateMix;
+				}
+				if (r > PI)
+					r -= PI2;
+				else if (r < -PI) //
+					r += PI2;
+				r *= rotateMix;
+				cos = cos(r);
+				sin = sin(r);
+				bone.a = cos * a - sin * c;
+				bone.b = cos * b - sin * d;
+				bone.c = sin * a + cos * c;
+				bone.d = sin * b + cos * d;
+			}
+		}
+	}
+
+	float[] computeWorldPositions (PathAttachment path, int spacesCount, boolean tangents, boolean percentPosition,
+		boolean percentSpacing) {
+		Slot target = this.target;
+		float position = this.position;
+		float[] spaces = this.spaces.items, out = this.positions.setSize(spacesCount * 3 + 2), world;
+		boolean closed = path.getClosed();
+		int verticesLength = path.getWorldVerticesLength(), curveCount = verticesLength / 6, prevCurve = NONE;
+
+		if (!path.getConstantSpeed()) {
+			float[] lengths = path.getLengths();
+			curveCount -= closed ? 1 : 2;
+			float pathLength = lengths[curveCount];
+			if (percentPosition) position *= pathLength;
+			if (percentSpacing) {
+				for (int i = 0; i < spacesCount; i++)
+					spaces[i] *= pathLength;
+			}
+			world = this.world.setSize(8);
+			for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) {
+				float space = spaces[i];
+				position += space;
+				float p = position;
+
+				if (closed) {
+					p %= pathLength;
+					if (p < 0) p += pathLength;
+					curve = 0;
+				} else if (p < 0) {
+					if (prevCurve != BEFORE) {
+						prevCurve = BEFORE;
+						path.computeWorldVertices(target, 2, 4, world, 0);
+					}
+					addBeforePosition(p, world, 0, out, o);
+					continue;
+				} else if (p > pathLength) {
+					if (prevCurve != AFTER) {
+						prevCurve = AFTER;
+						path.computeWorldVertices(target, verticesLength - 6, 4, world, 0);
+					}
+					addAfterPosition(p - pathLength, world, 0, out, o);
+					continue;
+				}
+
+				// Determine curve containing position.
+				for (;; curve++) {
+					float length = lengths[curve];
+					if (p > length) continue;
+					if (curve == 0)
+						p /= length;
+					else {
+						float prev = lengths[curve - 1];
+						p = (p - prev) / (length - prev);
+					}
+					break;
+				}
+				if (curve != prevCurve) {
+					prevCurve = curve;
+					if (closed && curve == curveCount) {
+						path.computeWorldVertices(target, verticesLength - 4, 4, world, 0);
+						path.computeWorldVertices(target, 0, 4, world, 4);
+					} else
+						path.computeWorldVertices(target, curve * 6 + 2, 8, world, 0);
+				}
+				addCurvePosition(p, world[0], world[1], world[2], world[3], world[4], world[5], world[6], world[7], out, o,
+					tangents || (i > 0 && space == 0));
+			}
+			return out;
+		}
+
+		// World vertices.
+		if (closed) {
+			verticesLength += 2;
+			world = this.world.setSize(verticesLength);
+			path.computeWorldVertices(target, 2, verticesLength - 4, world, 0);
+			path.computeWorldVertices(target, 0, 2, world, verticesLength - 4);
+			world[verticesLength - 2] = world[0];
+			world[verticesLength - 1] = world[1];
+		} else {
+			curveCount--;
+			verticesLength -= 4;
+			world = this.world.setSize(verticesLength);
+			path.computeWorldVertices(target, 2, verticesLength, world, 0);
+		}
+
+		// Curve lengths.
+		float[] curves = this.curves.setSize(curveCount);
+		float pathLength = 0;
+		float x1 = world[0], y1 = world[1], cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0, x2 = 0, y2 = 0;
+		float tmpx, tmpy, dddfx, dddfy, ddfx, ddfy, dfx, dfy;
+		for (int i = 0, w = 2; i < curveCount; i++, w += 6) {
+			cx1 = world[w];
+			cy1 = world[w + 1];
+			cx2 = world[w + 2];
+			cy2 = world[w + 3];
+			x2 = world[w + 4];
+			y2 = world[w + 5];
+			tmpx = (x1 - cx1 * 2 + cx2) * 0.1875f;
+			tmpy = (y1 - cy1 * 2 + cy2) * 0.1875f;
+			dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375f;
+			dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375f;
+			ddfx = tmpx * 2 + dddfx;
+			ddfy = tmpy * 2 + dddfy;
+			dfx = (cx1 - x1) * 0.75f + tmpx + dddfx * 0.16666667f;
+			dfy = (cy1 - y1) * 0.75f + tmpy + dddfy * 0.16666667f;
+			pathLength += (float)Math.sqrt(dfx * dfx + dfy * dfy);
+			dfx += ddfx;
+			dfy += ddfy;
+			ddfx += dddfx;
+			ddfy += dddfy;
+			pathLength += (float)Math.sqrt(dfx * dfx + dfy * dfy);
+			dfx += ddfx;
+			dfy += ddfy;
+			pathLength += (float)Math.sqrt(dfx * dfx + dfy * dfy);
+			dfx += ddfx + dddfx;
+			dfy += ddfy + dddfy;
+			pathLength += (float)Math.sqrt(dfx * dfx + dfy * dfy);
+			curves[i] = pathLength;
+			x1 = x2;
+			y1 = y2;
+		}
+		if (percentPosition) position *= pathLength;
+		if (percentSpacing) {
+			for (int i = 0; i < spacesCount; i++)
+				spaces[i] *= pathLength;
+		}
+
+		float[] segments = this.segments;
+		float curveLength = 0;
+		for (int i = 0, o = 0, curve = 0, segment = 0; i < spacesCount; i++, o += 3) {
+			float space = spaces[i];
+			position += space;
+			float p = position;
+
+			if (closed) {
+				p %= pathLength;
+				if (p < 0) p += pathLength;
+				curve = 0;
+			} else if (p < 0) {
+				addBeforePosition(p, world, 0, out, o);
+				continue;
+			} else if (p > pathLength) {
+				addAfterPosition(p - pathLength, world, verticesLength - 4, out, o);
+				continue;
+			}
+
+			// Determine curve containing position.
+			for (;; curve++) {
+				float length = curves[curve];
+				if (p > length) continue;
+				if (curve == 0)
+					p /= length;
+				else {
+					float prev = curves[curve - 1];
+					p = (p - prev) / (length - prev);
+				}
+				break;
+			}
+
+			// Curve segment lengths.
+			if (curve != prevCurve) {
+				prevCurve = curve;
+				int ii = curve * 6;
+				x1 = world[ii];
+				y1 = world[ii + 1];
+				cx1 = world[ii + 2];
+				cy1 = world[ii + 3];
+				cx2 = world[ii + 4];
+				cy2 = world[ii + 5];
+				x2 = world[ii + 6];
+				y2 = world[ii + 7];
+				tmpx = (x1 - cx1 * 2 + cx2) * 0.03f;
+				tmpy = (y1 - cy1 * 2 + cy2) * 0.03f;
+				dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006f;
+				dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.006f;
+				ddfx = tmpx * 2 + dddfx;
+				ddfy = tmpy * 2 + dddfy;
+				dfx = (cx1 - x1) * 0.3f + tmpx + dddfx * 0.16666667f;
+				dfy = (cy1 - y1) * 0.3f + tmpy + dddfy * 0.16666667f;
+				curveLength = (float)Math.sqrt(dfx * dfx + dfy * dfy);
+				segments[0] = curveLength;
+				for (ii = 1; ii < 8; ii++) {
+					dfx += ddfx;
+					dfy += ddfy;
+					ddfx += dddfx;
+					ddfy += dddfy;
+					curveLength += (float)Math.sqrt(dfx * dfx + dfy * dfy);
+					segments[ii] = curveLength;
+				}
+				dfx += ddfx;
+				dfy += ddfy;
+				curveLength += (float)Math.sqrt(dfx * dfx + dfy * dfy);
+				segments[8] = curveLength;
+				dfx += ddfx + dddfx;
+				dfy += ddfy + dddfy;
+				curveLength += (float)Math.sqrt(dfx * dfx + dfy * dfy);
+				segments[9] = curveLength;
+				segment = 0;
+			}
+
+			// Weight by segment length.
+			p *= curveLength;
+			for (;; segment++) {
+				float length = segments[segment];
+				if (p > length) continue;
+				if (segment == 0)
+					p /= length;
+				else {
+					float prev = segments[segment - 1];
+					p = segment + (p - prev) / (length - prev);
+				}
+				break;
+			}
+			addCurvePosition(p * 0.1f, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents || (i > 0 && space == 0));
+		}
+		return out;
+	}
+
+	private void addBeforePosition (float p, float[] temp, int i, float[] out, int o) {
+		float x1 = temp[i], y1 = temp[i + 1], dx = temp[i + 2] - x1, dy = temp[i + 3] - y1, r = atan2(dy, dx);
+		out[o] = x1 + p * cos(r);
+		out[o + 1] = y1 + p * sin(r);
+		out[o + 2] = r;
+	}
+
+	private void addAfterPosition (float p, float[] temp, int i, float[] out, int o) {
+		float x1 = temp[i + 2], y1 = temp[i + 3], dx = x1 - temp[i], dy = y1 - temp[i + 1], r = atan2(dy, dx);
+		out[o] = x1 + p * cos(r);
+		out[o + 1] = y1 + p * sin(r);
+		out[o + 2] = r;
+	}
+
+	private void addCurvePosition (float p, float x1, float y1, float cx1, float cy1, float cx2, float cy2, float x2, float y2,
+		float[] out, int o, boolean tangents) {
+		if (p == 0) p = 0.0001f;
+		float tt = p * p, ttt = tt * p, u = 1 - p, uu = u * u, uuu = uu * u;
+		float ut = u * p, ut3 = ut * 3, uut3 = u * ut3, utt3 = ut3 * p;
+		float x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt;
+		out[o] = x;
+		out[o + 1] = y;
+		if (tangents) out[o + 2] = atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt));
+	}
+
+	public float getPosition () {
+		return position;
+	}
+
+	public void setPosition (float position) {
+		this.position = position;
+	}
+
+	public float getSpacing () {
+		return spacing;
+	}
+
+	public void setSpacing (float spacing) {
+		this.spacing = spacing;
+	}
+
+	public float getRotateMix () {
+		return rotateMix;
+	}
+
+	public void setRotateMix (float rotateMix) {
+		this.rotateMix = rotateMix;
+	}
+
+	public float getTranslateMix () {
+		return translateMix;
+	}
+
+	public void setTranslateMix (float translateMix) {
+		this.translateMix = translateMix;
+	}
+
+	public Array<Bone> getBones () {
+		return bones;
+	}
+
+	public Slot getTarget () {
+		return target;
+	}
+
+	public void setTarget (Slot target) {
+		this.target = target;
+	}
+
+	public PathConstraintData getData () {
+		return data;
+	}
+
+	public String toString () {
+		return data.name;
+	}
+}

+ 151 - 121
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/PathConstraintData.java

@@ -1,122 +1,152 @@
-
-package com.esotericsoftware.spine;
-
-import com.badlogic.gdx.utils.Array;
-
-public class PathConstraintData {
-	final String name;
-	final Array<BoneData> bones = new Array();
-	SlotData target;
-	PositionMode positionMode;
-	SpacingMode spacingMode;
-	RotateMode rotateMode;
-	float offsetRotation;
-	float position, spacing, rotateMix, translateMix;
-
-	public PathConstraintData (String name) {
-		if (name == null) throw new IllegalArgumentException("name cannot be null.");
-		this.name = name;
-	}
-
-	public Array<BoneData> getBones () {
-		return bones;
-	}
-
-	public SlotData getTarget () {
-		return target;
-	}
-
-	public void setTarget (SlotData target) {
-		this.target = target;
-	}
-
-	public PositionMode getPositionMode () {
-		return positionMode;
-	}
-
-	public void setPositionMode (PositionMode positionMode) {
-		this.positionMode = positionMode;
-	}
-
-	public SpacingMode getSpacingMode () {
-		return spacingMode;
-	}
-
-	public void setSpacingMode (SpacingMode spacingMode) {
-		this.spacingMode = spacingMode;
-	}
-
-	public RotateMode getRotateMode () {
-		return rotateMode;
-	}
-
-	public void setRotateMode (RotateMode rotateMode) {
-		this.rotateMode = rotateMode;
-	}
-
-	public float getOffsetRotation () {
-		return offsetRotation;
-	}
-
-	public void setOffsetRotation (float offsetRotation) {
-		this.offsetRotation = offsetRotation;
-	}
-
-	public float getPosition () {
-		return position;
-	}
-
-	public void setPosition (float position) {
-		this.position = position;
-	}
-
-	public float getSpacing () {
-		return spacing;
-	}
-
-	public void setSpacing (float spacing) {
-		this.spacing = spacing;
-	}
-
-	public float getRotateMix () {
-		return rotateMix;
-	}
-
-	public void setRotateMix (float rotateMix) {
-		this.rotateMix = rotateMix;
-	}
-
-	public float getTranslateMix () {
-		return translateMix;
-	}
-
-	public void setTranslateMix (float translateMix) {
-		this.translateMix = translateMix;
-	}
-
-	public String getName () {
-		return name;
-	}
-
-	public String toString () {
-		return name;
-	}
-
-	static public enum PositionMode {
-		fixed, percent;
-
-		static public final PositionMode[] values = PositionMode.values();
-	}
-
-	static public enum SpacingMode {
-		length, fixed, percent;
-
-		static public final SpacingMode[] values = SpacingMode.values();
-	}
-
-	static public enum RotateMode {
-		tangent, chain, chainScale;
-
-		static public final RotateMode[] values = RotateMode.values();
-	}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+package com.esotericsoftware.spine;
+
+import com.badlogic.gdx.utils.Array;
+
+public class PathConstraintData {
+	final String name;
+	final Array<BoneData> bones = new Array();
+	SlotData target;
+	PositionMode positionMode;
+	SpacingMode spacingMode;
+	RotateMode rotateMode;
+	float offsetRotation;
+	float position, spacing, rotateMix, translateMix;
+
+	public PathConstraintData (String name) {
+		if (name == null) throw new IllegalArgumentException("name cannot be null.");
+		this.name = name;
+	}
+
+	public Array<BoneData> getBones () {
+		return bones;
+	}
+
+	public SlotData getTarget () {
+		return target;
+	}
+
+	public void setTarget (SlotData target) {
+		this.target = target;
+	}
+
+	public PositionMode getPositionMode () {
+		return positionMode;
+	}
+
+	public void setPositionMode (PositionMode positionMode) {
+		this.positionMode = positionMode;
+	}
+
+	public SpacingMode getSpacingMode () {
+		return spacingMode;
+	}
+
+	public void setSpacingMode (SpacingMode spacingMode) {
+		this.spacingMode = spacingMode;
+	}
+
+	public RotateMode getRotateMode () {
+		return rotateMode;
+	}
+
+	public void setRotateMode (RotateMode rotateMode) {
+		this.rotateMode = rotateMode;
+	}
+
+	public float getOffsetRotation () {
+		return offsetRotation;
+	}
+
+	public void setOffsetRotation (float offsetRotation) {
+		this.offsetRotation = offsetRotation;
+	}
+
+	public float getPosition () {
+		return position;
+	}
+
+	public void setPosition (float position) {
+		this.position = position;
+	}
+
+	public float getSpacing () {
+		return spacing;
+	}
+
+	public void setSpacing (float spacing) {
+		this.spacing = spacing;
+	}
+
+	public float getRotateMix () {
+		return rotateMix;
+	}
+
+	public void setRotateMix (float rotateMix) {
+		this.rotateMix = rotateMix;
+	}
+
+	public float getTranslateMix () {
+		return translateMix;
+	}
+
+	public void setTranslateMix (float translateMix) {
+		this.translateMix = translateMix;
+	}
+
+	public String getName () {
+		return name;
+	}
+
+	public String toString () {
+		return name;
+	}
+
+	static public enum PositionMode {
+		fixed, percent;
+
+		static public final PositionMode[] values = PositionMode.values();
+	}
+
+	static public enum SpacingMode {
+		length, fixed, percent;
+
+		static public final SpacingMode[] values = SpacingMode.values();
+	}
+
+	static public enum RotateMode {
+		tangent, chain, chainScale;
+
+		static public final RotateMode[] values = RotateMode.values();
+	}
 }

+ 186 - 156
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraint.java

@@ -1,157 +1,187 @@
-
-package com.esotericsoftware.spine;
-
-import static com.badlogic.gdx.math.MathUtils.*;
-
-import com.badlogic.gdx.math.Vector2;
-import com.badlogic.gdx.utils.Array;
-
-public class TransformConstraint implements Updatable {
-	final TransformConstraintData data;
-	final Array<Bone> bones;
-	Bone target;
-	float rotateMix, translateMix, scaleMix, shearMix;
-	final Vector2 temp = new Vector2();
-
-	public TransformConstraint (TransformConstraintData data, Skeleton skeleton) {
-		if (data == null) throw new IllegalArgumentException("data cannot be null.");
-		if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
-		this.data = data;
-		rotateMix = data.rotateMix;
-		translateMix = data.translateMix;
-		scaleMix = data.scaleMix;
-		shearMix = data.shearMix;
-		bones = new Array(data.bones.size);
-		for (BoneData boneData : data.bones)
-			bones.add(skeleton.findBone(boneData.name));
-		target = skeleton.findBone(data.target.name);
-	}
-
-	/** Copy constructor. */
-	public TransformConstraint (TransformConstraint constraint, Skeleton skeleton) {
-		if (constraint == null) throw new IllegalArgumentException("constraint cannot be null.");
-		if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
-		data = constraint.data;
-		bones = new Array(constraint.bones.size);
-		for (Bone bone : constraint.bones)
-			bones.add(skeleton.bones.get(bone.data.index));
-		target = skeleton.bones.get(constraint.target.data.index);
-		rotateMix = constraint.rotateMix;
-		translateMix = constraint.translateMix;
-		scaleMix = constraint.scaleMix;
-		shearMix = constraint.shearMix;
-	}
-
-	public void apply () {
-		update();
-	}
-
-	public void update () {
-		float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix;
-		Bone target = this.target;
-		float ta = target.a, tb = target.b, tc = target.c, td = target.d;
-		Array<Bone> bones = this.bones;
-		for (int i = 0, n = bones.size; i < n; i++) {
-			Bone bone = bones.get(i);
-
-			if (rotateMix > 0) {
-				float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
-				float r = atan2(tc, ta) - atan2(c, a) + data.offsetRotation * degRad;
-				if (r > PI)
-					r -= PI2;
-				else if (r < -PI) r += PI2;
-				r *= rotateMix;
-				float cos = cos(r), sin = sin(r);
-				bone.a = cos * a - sin * c;
-				bone.b = cos * b - sin * d;
-				bone.c = sin * a + cos * c;
-				bone.d = sin * b + cos * d;
-			}
-
-			if (translateMix > 0) {
-				Vector2 temp = this.temp;
-				target.localToWorld(temp.set(data.offsetX, data.offsetY));
-				bone.worldX += (temp.x - bone.worldX) * translateMix;
-				bone.worldY += (temp.y - bone.worldY) * translateMix;
-			}
-
-			if (scaleMix > 0) {
-				float bs = (float)Math.sqrt(bone.a * bone.a + bone.c * bone.c);
-				float ts = (float)Math.sqrt(ta * ta + tc * tc);
-				float s = bs > 0.00001f ? (bs + (ts - bs + data.offsetScaleX) * scaleMix) / bs : 0;
-				bone.a *= s;
-				bone.c *= s;
-				bs = (float)Math.sqrt(bone.b * bone.b + bone.d * bone.d);
-				ts = (float)Math.sqrt(tb * tb + td * td);
-				s = bs > 0.00001f ? (bs + (ts - bs + data.offsetScaleY) * scaleMix) / bs : 0;
-				bone.b *= s;
-				bone.d *= s;
-			}
-
-			if (shearMix > 0) {
-				float b = bone.b, d = bone.d;
-				float by = atan2(d, b);
-				float r = atan2(td, tb) - atan2(tc, ta) - (by - atan2(bone.c, bone.a));
-				if (r > PI)
-					r -= PI2;
-				else if (r < -PI) r += PI2;
-				r = by + (r + data.offsetShearY * degRad) * shearMix;
-				float s = (float)Math.sqrt(b * b + d * d);
-				bone.b = cos(r) * s;
-				bone.d = sin(r) * s;
-			}
-		}
-	}
-
-	public Array<Bone> getBones () {
-		return bones;
-	}
-
-	public Bone getTarget () {
-		return target;
-	}
-
-	public void setTarget (Bone target) {
-		this.target = target;
-	}
-
-	public float getRotateMix () {
-		return rotateMix;
-	}
-
-	public void setRotateMix (float rotateMix) {
-		this.rotateMix = rotateMix;
-	}
-
-	public float getTranslateMix () {
-		return translateMix;
-	}
-
-	public void setTranslateMix (float translateMix) {
-		this.translateMix = translateMix;
-	}
-
-	public float getScaleMix () {
-		return scaleMix;
-	}
-
-	public void setScaleMix (float scaleMix) {
-		this.scaleMix = scaleMix;
-	}
-
-	public float getShearMix () {
-		return shearMix;
-	}
-
-	public void setShearMix (float shearMix) {
-		this.shearMix = shearMix;
-	}
-
-	public TransformConstraintData getData () {
-		return data;
-	}
-
-	public String toString () {
-		return data.name;
-	}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+package com.esotericsoftware.spine;
+
+import static com.badlogic.gdx.math.MathUtils.*;
+
+import com.badlogic.gdx.math.Vector2;
+import com.badlogic.gdx.utils.Array;
+
+public class TransformConstraint implements Updatable {
+	final TransformConstraintData data;
+	final Array<Bone> bones;
+	Bone target;
+	float rotateMix, translateMix, scaleMix, shearMix;
+	final Vector2 temp = new Vector2();
+
+	public TransformConstraint (TransformConstraintData data, Skeleton skeleton) {
+		if (data == null) throw new IllegalArgumentException("data cannot be null.");
+		if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
+		this.data = data;
+		rotateMix = data.rotateMix;
+		translateMix = data.translateMix;
+		scaleMix = data.scaleMix;
+		shearMix = data.shearMix;
+		bones = new Array(data.bones.size);
+		for (BoneData boneData : data.bones)
+			bones.add(skeleton.findBone(boneData.name));
+		target = skeleton.findBone(data.target.name);
+	}
+
+	/** Copy constructor. */
+	public TransformConstraint (TransformConstraint constraint, Skeleton skeleton) {
+		if (constraint == null) throw new IllegalArgumentException("constraint cannot be null.");
+		if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
+		data = constraint.data;
+		bones = new Array(constraint.bones.size);
+		for (Bone bone : constraint.bones)
+			bones.add(skeleton.bones.get(bone.data.index));
+		target = skeleton.bones.get(constraint.target.data.index);
+		rotateMix = constraint.rotateMix;
+		translateMix = constraint.translateMix;
+		scaleMix = constraint.scaleMix;
+		shearMix = constraint.shearMix;
+	}
+
+	public void apply () {
+		update();
+	}
+
+	public void update () {
+		float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix;
+		Bone target = this.target;
+		float ta = target.a, tb = target.b, tc = target.c, td = target.d;
+		Array<Bone> bones = this.bones;
+		for (int i = 0, n = bones.size; i < n; i++) {
+			Bone bone = bones.get(i);
+
+			if (rotateMix > 0) {
+				float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
+				float r = atan2(tc, ta) - atan2(c, a) + data.offsetRotation * degRad;
+				if (r > PI)
+					r -= PI2;
+				else if (r < -PI) r += PI2;
+				r *= rotateMix;
+				float cos = cos(r), sin = sin(r);
+				bone.a = cos * a - sin * c;
+				bone.b = cos * b - sin * d;
+				bone.c = sin * a + cos * c;
+				bone.d = sin * b + cos * d;
+			}
+
+			if (translateMix > 0) {
+				Vector2 temp = this.temp;
+				target.localToWorld(temp.set(data.offsetX, data.offsetY));
+				bone.worldX += (temp.x - bone.worldX) * translateMix;
+				bone.worldY += (temp.y - bone.worldY) * translateMix;
+			}
+
+			if (scaleMix > 0) {
+				float bs = (float)Math.sqrt(bone.a * bone.a + bone.c * bone.c);
+				float ts = (float)Math.sqrt(ta * ta + tc * tc);
+				float s = bs > 0.00001f ? (bs + (ts - bs + data.offsetScaleX) * scaleMix) / bs : 0;
+				bone.a *= s;
+				bone.c *= s;
+				bs = (float)Math.sqrt(bone.b * bone.b + bone.d * bone.d);
+				ts = (float)Math.sqrt(tb * tb + td * td);
+				s = bs > 0.00001f ? (bs + (ts - bs + data.offsetScaleY) * scaleMix) / bs : 0;
+				bone.b *= s;
+				bone.d *= s;
+			}
+
+			if (shearMix > 0) {
+				float b = bone.b, d = bone.d;
+				float by = atan2(d, b);
+				float r = atan2(td, tb) - atan2(tc, ta) - (by - atan2(bone.c, bone.a));
+				if (r > PI)
+					r -= PI2;
+				else if (r < -PI) r += PI2;
+				r = by + (r + data.offsetShearY * degRad) * shearMix;
+				float s = (float)Math.sqrt(b * b + d * d);
+				bone.b = cos(r) * s;
+				bone.d = sin(r) * s;
+			}
+		}
+	}
+
+	public Array<Bone> getBones () {
+		return bones;
+	}
+
+	public Bone getTarget () {
+		return target;
+	}
+
+	public void setTarget (Bone target) {
+		this.target = target;
+	}
+
+	public float getRotateMix () {
+		return rotateMix;
+	}
+
+	public void setRotateMix (float rotateMix) {
+		this.rotateMix = rotateMix;
+	}
+
+	public float getTranslateMix () {
+		return translateMix;
+	}
+
+	public void setTranslateMix (float translateMix) {
+		this.translateMix = translateMix;
+	}
+
+	public float getScaleMix () {
+		return scaleMix;
+	}
+
+	public void setScaleMix (float scaleMix) {
+		this.scaleMix = scaleMix;
+	}
+
+	public float getShearMix () {
+		return shearMix;
+	}
+
+	public void setShearMix (float shearMix) {
+		this.shearMix = shearMix;
+	}
+
+	public TransformConstraintData getData () {
+		return data;
+	}
+
+	public String toString () {
+		return data.name;
+	}
 }

+ 147 - 117
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraintData.java

@@ -1,118 +1,148 @@
-
-package com.esotericsoftware.spine;
-
-import com.badlogic.gdx.utils.Array;
-
-public class TransformConstraintData {
-	final String name;
-	final Array<BoneData> bones = new Array();
-	BoneData target;
-	float rotateMix, translateMix, scaleMix, shearMix;
-	float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY;
-
-	public TransformConstraintData (String name) {
-		if (name == null) throw new IllegalArgumentException("name cannot be null.");
-		this.name = name;
-	}
-
-	public String getName () {
-		return name;
-	}
-
-	public Array<BoneData> getBones () {
-		return bones;
-	}
-
-	public BoneData getTarget () {
-		return target;
-	}
-
-	public void setTarget (BoneData target) {
-		if (target == null) throw new IllegalArgumentException("target cannot be null.");
-		this.target = target;
-	}
-
-	public float getRotateMix () {
-		return rotateMix;
-	}
-
-	public void setRotateMix (float rotateMix) {
-		this.rotateMix = rotateMix;
-	}
-
-	public float getTranslateMix () {
-		return translateMix;
-	}
-
-	public void setTranslateMix (float translateMix) {
-		this.translateMix = translateMix;
-	}
-
-	public float getScaleMix () {
-		return scaleMix;
-	}
-
-	public void setScaleMix (float scaleMix) {
-		this.scaleMix = scaleMix;
-	}
-
-	public float getShearMix () {
-		return shearMix;
-	}
-
-	public void setShearMix (float shearMix) {
-		this.shearMix = shearMix;
-	}
-
-	public float getOffsetRotation () {
-		return offsetRotation;
-	}
-
-	public void setOffsetRotation (float offsetRotation) {
-		this.offsetRotation = offsetRotation;
-	}
-
-	public float getOffsetX () {
-		return offsetX;
-	}
-
-	public void setOffsetX (float offsetX) {
-		this.offsetX = offsetX;
-	}
-
-	public float getOffsetY () {
-		return offsetY;
-	}
-
-	public void setOffsetY (float offsetY) {
-		this.offsetY = offsetY;
-	}
-
-	public float getOffsetScaleX () {
-		return offsetScaleX;
-	}
-
-	public void setOffsetScaleX (float offsetScaleX) {
-		this.offsetScaleX = offsetScaleX;
-	}
-
-	public float getOffsetScaleY () {
-		return offsetScaleY;
-	}
-
-	public void setOffsetScaleY (float offsetScaleY) {
-		this.offsetScaleY = offsetScaleY;
-	}
-
-	public float getOffsetShearY () {
-		return offsetShearY;
-	}
-
-	public void setOffsetShearY (float offsetShearY) {
-		this.offsetShearY = offsetShearY;
-	}
-
-	public String toString () {
-		return name;
-	}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+package com.esotericsoftware.spine;
+
+import com.badlogic.gdx.utils.Array;
+
+public class TransformConstraintData {
+	final String name;
+	final Array<BoneData> bones = new Array();
+	BoneData target;
+	float rotateMix, translateMix, scaleMix, shearMix;
+	float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY;
+
+	public TransformConstraintData (String name) {
+		if (name == null) throw new IllegalArgumentException("name cannot be null.");
+		this.name = name;
+	}
+
+	public String getName () {
+		return name;
+	}
+
+	public Array<BoneData> getBones () {
+		return bones;
+	}
+
+	public BoneData getTarget () {
+		return target;
+	}
+
+	public void setTarget (BoneData target) {
+		if (target == null) throw new IllegalArgumentException("target cannot be null.");
+		this.target = target;
+	}
+
+	public float getRotateMix () {
+		return rotateMix;
+	}
+
+	public void setRotateMix (float rotateMix) {
+		this.rotateMix = rotateMix;
+	}
+
+	public float getTranslateMix () {
+		return translateMix;
+	}
+
+	public void setTranslateMix (float translateMix) {
+		this.translateMix = translateMix;
+	}
+
+	public float getScaleMix () {
+		return scaleMix;
+	}
+
+	public void setScaleMix (float scaleMix) {
+		this.scaleMix = scaleMix;
+	}
+
+	public float getShearMix () {
+		return shearMix;
+	}
+
+	public void setShearMix (float shearMix) {
+		this.shearMix = shearMix;
+	}
+
+	public float getOffsetRotation () {
+		return offsetRotation;
+	}
+
+	public void setOffsetRotation (float offsetRotation) {
+		this.offsetRotation = offsetRotation;
+	}
+
+	public float getOffsetX () {
+		return offsetX;
+	}
+
+	public void setOffsetX (float offsetX) {
+		this.offsetX = offsetX;
+	}
+
+	public float getOffsetY () {
+		return offsetY;
+	}
+
+	public void setOffsetY (float offsetY) {
+		this.offsetY = offsetY;
+	}
+
+	public float getOffsetScaleX () {
+		return offsetScaleX;
+	}
+
+	public void setOffsetScaleX (float offsetScaleX) {
+		this.offsetScaleX = offsetScaleX;
+	}
+
+	public float getOffsetScaleY () {
+		return offsetScaleY;
+	}
+
+	public void setOffsetScaleY (float offsetScaleY) {
+		this.offsetScaleY = offsetScaleY;
+	}
+
+	public float getOffsetShearY () {
+		return offsetShearY;
+	}
+
+	public void setOffsetShearY (float offsetShearY) {
+		this.offsetShearY = offsetShearY;
+	}
+
+	public String toString () {
+		return name;
+	}
 }

+ 34 - 4
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Updatable.java

@@ -1,6 +1,36 @@
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
 
-package com.esotericsoftware.spine;
-
-public interface Updatable {
-	public void update ();
+package com.esotericsoftware.spine;
+
+public interface Updatable {
+	public void update ();
 }

+ 96 - 66
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/SkeletonActor.java

@@ -1,68 +1,98 @@
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
 
-package com.esotericsoftware.spine.utils;
-
-import com.badlogic.gdx.graphics.Color;
-import com.badlogic.gdx.graphics.g2d.Batch;
-import com.badlogic.gdx.scenes.scene2d.Actor;
-import com.esotericsoftware.spine.AnimationState;
-import com.esotericsoftware.spine.Skeleton;
-import com.esotericsoftware.spine.SkeletonRenderer;
-
-/** A scene2d actor that draws a skeleton. */
-public class SkeletonActor extends Actor {
-	private SkeletonRenderer renderer;
-	private Skeleton skeleton;
-	AnimationState state;
-
-	/** Creates an uninitialized SkeletonActor. The renderer, skeleton, and animation state must be set before use. */
-	public SkeletonActor () {
-	}
-
-	public SkeletonActor (SkeletonRenderer renderer, Skeleton skeleton, AnimationState state) {
-		this.renderer = renderer;
-		this.skeleton = skeleton;
-		this.state = state;
-	}
-
-	public void act (float delta) {
-		state.update(delta);
-		state.apply(skeleton);
-		skeleton.updateWorldTransform();
-		super.act(delta);
-	}
-
-	public void draw (Batch batch, float parentAlpha) {
-		Color color = skeleton.getColor();
-		float oldAlpha = color.a;
-		skeleton.getColor().a *= parentAlpha;
-
-		skeleton.setPosition(getX(), getY());
-		renderer.draw(batch, skeleton);
-
-		color.a = oldAlpha;
-	}
-
-	public SkeletonRenderer getRenderer () {
-		return renderer;
-	}
-
-	public void setRenderer (SkeletonRenderer renderer) {
-		this.renderer = renderer;
-	}
-
-	public Skeleton getSkeleton () {
-		return skeleton;
-	}
-
-	public void setSkeleton (Skeleton skeleton) {
-		this.skeleton = skeleton;
-	}
-
-	public AnimationState getAnimationState () {
-		return state;
-	}
-
-	public void setAnimationState (AnimationState state) {
-		this.state = state;
-	}
+package com.esotericsoftware.spine.utils;
+
+import com.badlogic.gdx.graphics.Color;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.scenes.scene2d.Actor;
+import com.esotericsoftware.spine.AnimationState;
+import com.esotericsoftware.spine.Skeleton;
+import com.esotericsoftware.spine.SkeletonRenderer;
+
+/** A scene2d actor that draws a skeleton. */
+public class SkeletonActor extends Actor {
+	private SkeletonRenderer renderer;
+	private Skeleton skeleton;
+	AnimationState state;
+
+	/** Creates an uninitialized SkeletonActor. The renderer, skeleton, and animation state must be set before use. */
+	public SkeletonActor () {
+	}
+
+	public SkeletonActor (SkeletonRenderer renderer, Skeleton skeleton, AnimationState state) {
+		this.renderer = renderer;
+		this.skeleton = skeleton;
+		this.state = state;
+	}
+
+	public void act (float delta) {
+		state.update(delta);
+		state.apply(skeleton);
+		skeleton.updateWorldTransform();
+		super.act(delta);
+	}
+
+	public void draw (Batch batch, float parentAlpha) {
+		Color color = skeleton.getColor();
+		float oldAlpha = color.a;
+		skeleton.getColor().a *= parentAlpha;
+
+		skeleton.setPosition(getX(), getY());
+		renderer.draw(batch, skeleton);
+
+		color.a = oldAlpha;
+	}
+
+	public SkeletonRenderer getRenderer () {
+		return renderer;
+	}
+
+	public void setRenderer (SkeletonRenderer renderer) {
+		this.renderer = renderer;
+	}
+
+	public Skeleton getSkeleton () {
+		return skeleton;
+	}
+
+	public void setSkeleton (Skeleton skeleton) {
+		this.skeleton = skeleton;
+	}
+
+	public AnimationState getAnimationState () {
+		return state;
+	}
+
+	public void setAnimationState (AnimationState state) {
+		this.state = state;
+	}
 }

+ 131 - 101
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/SkeletonActorPool.java

@@ -1,102 +1,132 @@
-
-package com.esotericsoftware.spine.utils;
-
-import com.badlogic.gdx.graphics.Color;
-import com.badlogic.gdx.utils.Array;
-import com.badlogic.gdx.utils.Pool;
-import com.esotericsoftware.spine.AnimationState;
-import com.esotericsoftware.spine.AnimationState.TrackEntry;
-import com.esotericsoftware.spine.AnimationStateData;
-import com.esotericsoftware.spine.Skeleton;
-import com.esotericsoftware.spine.SkeletonData;
-import com.esotericsoftware.spine.SkeletonRenderer;
-import com.esotericsoftware.spine.Skin;
-
-public class SkeletonActorPool extends Pool<SkeletonActor> {
-	private SkeletonRenderer renderer;
-	SkeletonData skeletonData;
-	AnimationStateData stateData;
-	private final Pool<Skeleton> skeletonPool;
-	private final Pool<AnimationState> statePool;
-	private final Array<SkeletonActor> obtained;
-
-	public SkeletonActorPool (SkeletonRenderer renderer, SkeletonData skeletonData, AnimationStateData stateData) {
-		this(renderer, skeletonData, stateData, 16, Integer.MAX_VALUE);
-	}
-
-	public SkeletonActorPool (SkeletonRenderer renderer, SkeletonData skeletonData, AnimationStateData stateData,
-		int initialCapacity, int max) {
-		super(initialCapacity, max);
-
-		this.renderer = renderer;
-		this.skeletonData = skeletonData;
-		this.stateData = stateData;
-
-		obtained = new Array(false, initialCapacity);
-
-		skeletonPool = new Pool<Skeleton>(initialCapacity, max) {
-			protected Skeleton newObject () {
-				return new Skeleton(SkeletonActorPool.this.skeletonData);
-			}
-
-			protected void reset (Skeleton skeleton) {
-				skeleton.setColor(Color.WHITE);
-				skeleton.setFlip(false, false);
-				skeleton.setSkin((Skin)null);
-				skeleton.setSkin(SkeletonActorPool.this.skeletonData.getDefaultSkin());
-				skeleton.setToSetupPose();
-			}
-		};
-
-		statePool = new Pool<AnimationState>(initialCapacity, max) {
-			protected AnimationState newObject () {
-				return new AnimationState(SkeletonActorPool.this.stateData);
-			}
-
-			protected void reset (AnimationState state) {
-				state.clearTracks();
-				state.clearListeners();
-			}
-		};
-	}
-
-	/** Each obtained skeleton actor that is no longer playing an animation is removed from the stage and returned to the pool. */
-	public void freeComplete () {
-		Array<SkeletonActor> obtained = this.obtained;
-		outer:
-		for (int i = obtained.size - 1; i >= 0; i--) {
-			SkeletonActor actor = obtained.get(i);
-			Array<TrackEntry> tracks = actor.state.getTracks();
-			for (int ii = 0, nn = tracks.size; ii < nn; ii++)
-				if (tracks.get(ii) != null) continue outer;
-			free(actor);
-		}
-	}
-
-	protected SkeletonActor newObject () {
-		SkeletonActor actor = new SkeletonActor();
-		actor.setRenderer(renderer);
-		return actor;
-	}
-
-	/** This pool keeps a reference to the obtained instance, so it should be returned to the pool via {@link #free(SkeletonActor)}
-	 * , {@link #freeAll(Array)} or {@link #freeComplete()} to avoid leaking memory. */
-	public SkeletonActor obtain () {
-		SkeletonActor actor = super.obtain();
-		actor.setSkeleton(skeletonPool.obtain());
-		actor.setAnimationState(statePool.obtain());
-		obtained.add(actor);
-		return actor;
-	}
-
-	protected void reset (SkeletonActor actor) {
-		actor.remove();
-		obtained.removeValue(actor, true);
-		skeletonPool.free(actor.getSkeleton());
-		statePool.free(actor.getAnimationState());
-	}
-
-	public Array<SkeletonActor> getObtained () {
-		return obtained;
-	}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+package com.esotericsoftware.spine.utils;
+
+import com.badlogic.gdx.graphics.Color;
+import com.badlogic.gdx.utils.Array;
+import com.badlogic.gdx.utils.Pool;
+import com.esotericsoftware.spine.AnimationState;
+import com.esotericsoftware.spine.AnimationState.TrackEntry;
+import com.esotericsoftware.spine.AnimationStateData;
+import com.esotericsoftware.spine.Skeleton;
+import com.esotericsoftware.spine.SkeletonData;
+import com.esotericsoftware.spine.SkeletonRenderer;
+import com.esotericsoftware.spine.Skin;
+
+public class SkeletonActorPool extends Pool<SkeletonActor> {
+	private SkeletonRenderer renderer;
+	SkeletonData skeletonData;
+	AnimationStateData stateData;
+	private final Pool<Skeleton> skeletonPool;
+	private final Pool<AnimationState> statePool;
+	private final Array<SkeletonActor> obtained;
+
+	public SkeletonActorPool (SkeletonRenderer renderer, SkeletonData skeletonData, AnimationStateData stateData) {
+		this(renderer, skeletonData, stateData, 16, Integer.MAX_VALUE);
+	}
+
+	public SkeletonActorPool (SkeletonRenderer renderer, SkeletonData skeletonData, AnimationStateData stateData,
+		int initialCapacity, int max) {
+		super(initialCapacity, max);
+
+		this.renderer = renderer;
+		this.skeletonData = skeletonData;
+		this.stateData = stateData;
+
+		obtained = new Array(false, initialCapacity);
+
+		skeletonPool = new Pool<Skeleton>(initialCapacity, max) {
+			protected Skeleton newObject () {
+				return new Skeleton(SkeletonActorPool.this.skeletonData);
+			}
+
+			protected void reset (Skeleton skeleton) {
+				skeleton.setColor(Color.WHITE);
+				skeleton.setFlip(false, false);
+				skeleton.setSkin((Skin)null);
+				skeleton.setSkin(SkeletonActorPool.this.skeletonData.getDefaultSkin());
+				skeleton.setToSetupPose();
+			}
+		};
+
+		statePool = new Pool<AnimationState>(initialCapacity, max) {
+			protected AnimationState newObject () {
+				return new AnimationState(SkeletonActorPool.this.stateData);
+			}
+
+			protected void reset (AnimationState state) {
+				state.clearTracks();
+				state.clearListeners();
+			}
+		};
+	}
+
+	/** Each obtained skeleton actor that is no longer playing an animation is removed from the stage and returned to the pool. */
+	public void freeComplete () {
+		Array<SkeletonActor> obtained = this.obtained;
+		outer:
+		for (int i = obtained.size - 1; i >= 0; i--) {
+			SkeletonActor actor = obtained.get(i);
+			Array<TrackEntry> tracks = actor.state.getTracks();
+			for (int ii = 0, nn = tracks.size; ii < nn; ii++)
+				if (tracks.get(ii) != null) continue outer;
+			free(actor);
+		}
+	}
+
+	protected SkeletonActor newObject () {
+		SkeletonActor actor = new SkeletonActor();
+		actor.setRenderer(renderer);
+		return actor;
+	}
+
+	/** This pool keeps a reference to the obtained instance, so it should be returned to the pool via {@link #free(SkeletonActor)}
+	 * , {@link #freeAll(Array)} or {@link #freeComplete()} to avoid leaking memory. */
+	public SkeletonActor obtain () {
+		SkeletonActor actor = super.obtain();
+		actor.setSkeleton(skeletonPool.obtain());
+		actor.setAnimationState(statePool.obtain());
+		obtained.add(actor);
+		return actor;
+	}
+
+	protected void reset (SkeletonActor actor) {
+		actor.remove();
+		obtained.removeValue(actor, true);
+		skeletonPool.free(actor.getSkeleton());
+		statePool.free(actor.getAnimationState());
+	}
+
+	public Array<SkeletonActor> getObtained () {
+		return obtained;
+	}
 }

+ 56 - 26
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/SkeletonPool.java

@@ -1,28 +1,58 @@
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
 
-package com.esotericsoftware.spine.utils;
-
-import com.badlogic.gdx.utils.Pool;
-import com.esotericsoftware.spine.Skeleton;
-import com.esotericsoftware.spine.SkeletonData;
-
-public class SkeletonPool extends Pool<Skeleton> {
-	private SkeletonData skeletonData;
-
-	public SkeletonPool (SkeletonData skeletonData) {
-		this.skeletonData = skeletonData;
-	}
-
-	public SkeletonPool (SkeletonData skeletonData, int initialCapacity) {
-		super(initialCapacity);
-		this.skeletonData = skeletonData;
-	}
-
-	public SkeletonPool (SkeletonData skeletonData, int initialCapacity, int max) {
-		super(initialCapacity, max);
-		this.skeletonData = skeletonData;
-	}
-
-	protected Skeleton newObject () {
-		return new Skeleton(skeletonData);
-	}
+package com.esotericsoftware.spine.utils;
+
+import com.badlogic.gdx.utils.Pool;
+import com.esotericsoftware.spine.Skeleton;
+import com.esotericsoftware.spine.SkeletonData;
+
+public class SkeletonPool extends Pool<Skeleton> {
+	private SkeletonData skeletonData;
+
+	public SkeletonPool (SkeletonData skeletonData) {
+		this.skeletonData = skeletonData;
+	}
+
+	public SkeletonPool (SkeletonData skeletonData, int initialCapacity) {
+		super(initialCapacity);
+		this.skeletonData = skeletonData;
+	}
+
+	public SkeletonPool (SkeletonData skeletonData, int initialCapacity, int max) {
+		super(initialCapacity, max);
+		this.skeletonData = skeletonData;
+	}
+
+	protected Skeleton newObject () {
+		return new Skeleton(skeletonData);
+	}
 }

+ 143 - 113
spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/JsonRollback.java

@@ -1,114 +1,144 @@
-
-package com.esotericsoftware.spine;
-
-import com.badlogic.gdx.files.FileHandle;
-import com.badlogic.gdx.utils.Array;
-import com.badlogic.gdx.utils.Json;
-import com.badlogic.gdx.utils.JsonValue;
-import com.badlogic.gdx.utils.JsonWriter.OutputType;
-
-/** Takes Spine JSON data and transforms it to work with an older version of Spine. It supports going from version 3.3.xx to
- * 2.1.27.
- * <p>
- * Data can be exported from a Spine project, processed with JsonRollback, then imported into an older version of Spine. However,
- * JsonRollback may remove data for features not supported by the older Spine version. Because of this, JsonRollback is only
- * intended for situations were work was accidentally done with a newer Spine version and now needs to be imported into an older
- * Spine version (eg, if runtime support for the new version is not yet available).
- * <p>
- * Animators should freeze their Spine editor version to match the Spine version supported by the runtime being used. Only when
- * the runtime is updated to support a newer Spine version should animators update their Spine editor version to match. */
-public class JsonRollback {
-	static public void main (String[] args) throws Exception {
-		if (args.length == 0) {
-			System.out.println("Usage: <inputFile> [outputFile]");
-			System.exit(0);
-		}
-
-		JsonValue root = new Json().fromJson(null, new FileHandle(args[0]));
-
-		// In 3.2 skinnedmesh was renamed to weightedmesh.
-		setValue(root, "skinnedmesh", "skins", "*", "*", "*", "type", "weightedmesh");
-
-		// In 3.2 shear was added.
-		delete(root, "animations", "*", "bones", "*", "shear");
-
-		// In 3.3 ffd was renamed to deform.
-		rename(root, "ffd", "animations", "*", "deform");
-
-		// In 3.3 mesh is now a single type, previously they were skinnedmesh if they had weights.
-		for (JsonValue value : find(root, new Array(), 0, "skins", "*", "*", "*", "type", "mesh"))
-			if (value.parent.get("uvs").size != value.parent.get("vertices").size) value.set("skinnedmesh");
-
-		// In 3.3 linkedmesh is now a single type, previously they were linkedweightedmesh if they had weights.
-		for (JsonValue value : find(root, new Array(), 0, "skins", "*", "*", "*", "type", "linkedmesh")) {
-			String slot = value.parent.parent.name.replaceAll("", "");
-			String skinName = value.parent.getString("skin", "default");
-			String parentName = value.parent.getString("parent");
-			if (find(root, new Array(), 0,
-				("skins~~" + skinName + "~~" + slot + "~~" + parentName + "~~type~~skinnedmesh").split("~~")).size > 0)
-				value.set("weightedlinkedmesh");
-		}
-
-		// In 3.3 bounding boxes can be weighted.
-		for (JsonValue value : find(root, new Array(), 0, "skins", "*", "*", "*", "type", "boundingbox"))
-			if (value.parent.getInt("vertexCount") * 2 != value.parent.get("vertices").size)
-				value.parent.parent.remove(value.parent.name);
-
-		// In 3.3 paths were added.
-		for (JsonValue value : find(root, new Array(), 0, "skins", "*", "*", "*", "type", "path")) {
-			String attachment = value.parent.name;
-			value.parent.parent.remove(attachment);
-			String slot = value.parent.parent.name;
-			// Also remove path deform timelines.
-			delete(root, "animations", "*", "ffd", "*", slot, attachment);
-		}
-
-		// In 3.3 IK constraint timelines no longer require bendPositive.
-		for (JsonValue value : find(root, new Array(), 0, "animations", "*", "ik", "*"))
-			for (JsonValue child = value.child; child != null; child = child.next)
-				if (!child.has("bendPositive")) child.addChild("bendPositive", new JsonValue(true));
-
-		// In 3.3 transform constraints can have more than 1 bone.
-		for (JsonValue child = root.getChild("transform"); child != null; child = child.next) {
-			JsonValue bones = child.remove("bones");
-			if (bones != null) child.addChild("bone", new JsonValue(bones.child.asString()));
-		}
-
-		if (args.length > 1)
-			new FileHandle(args[1]).writeString(root.prettyPrint(OutputType.json, 130), false, "UTF-8");
-		else
-			System.out.println(root.prettyPrint(OutputType.json, 130));
-	}
-
-	static void setValue (JsonValue root, String newValue, String... path) {
-		for (JsonValue value : find(root, new Array(), 0, path))
-			value.set(newValue);
-	}
-
-	static void rename (JsonValue root, String newName, String... path) {
-		for (JsonValue value : find(root, new Array(), 0, path))
-			value.name = newName;
-	}
-
-	static void delete (JsonValue root, String... path) {
-		for (JsonValue value : find(root, new Array(), 0, path))
-			value.parent.remove(value.name);
-	}
-
-	static Array<JsonValue> find (JsonValue current, Array<JsonValue> values, int index, String... path) {
-		String name = path[index];
-		if (current.name == null) {
-			if (name.equals("*") && index == path.length - 1)
-				values.add(current);
-			else if (current.has(name)) return find(current.get(name), values, index, path);
-		} else if (name.equals("*") || current.name.equals(name)) {
-			if (++index == path.length || (index == path.length - 1 && current.isString() && current.asString().equals(path[index])))
-				values.add(current);
-			else {
-				for (JsonValue child = current.child; child != null; child = child.next)
-					find(child, values, index, path);
-			}
-		}
-		return values;
-	}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+package com.esotericsoftware.spine;
+
+import com.badlogic.gdx.files.FileHandle;
+import com.badlogic.gdx.utils.Array;
+import com.badlogic.gdx.utils.Json;
+import com.badlogic.gdx.utils.JsonValue;
+import com.badlogic.gdx.utils.JsonWriter.OutputType;
+
+/** Takes Spine JSON data and transforms it to work with an older version of Spine. It supports going from version 3.3.xx to
+ * 2.1.27.
+ * <p>
+ * Data can be exported from a Spine project, processed with JsonRollback, then imported into an older version of Spine. However,
+ * JsonRollback may remove data for features not supported by the older Spine version. Because of this, JsonRollback is only
+ * intended for situations were work was accidentally done with a newer Spine version and now needs to be imported into an older
+ * Spine version (eg, if runtime support for the new version is not yet available).
+ * <p>
+ * Animators should freeze their Spine editor version to match the Spine version supported by the runtime being used. Only when
+ * the runtime is updated to support a newer Spine version should animators update their Spine editor version to match. */
+public class JsonRollback {
+	static public void main (String[] args) throws Exception {
+		if (args.length == 0) {
+			System.out.println("Usage: <inputFile> [outputFile]");
+			System.exit(0);
+		}
+
+		JsonValue root = new Json().fromJson(null, new FileHandle(args[0]));
+
+		// In 3.2 skinnedmesh was renamed to weightedmesh.
+		setValue(root, "skinnedmesh", "skins", "*", "*", "*", "type", "weightedmesh");
+
+		// In 3.2 shear was added.
+		delete(root, "animations", "*", "bones", "*", "shear");
+
+		// In 3.3 ffd was renamed to deform.
+		rename(root, "ffd", "animations", "*", "deform");
+
+		// In 3.3 mesh is now a single type, previously they were skinnedmesh if they had weights.
+		for (JsonValue value : find(root, new Array(), 0, "skins", "*", "*", "*", "type", "mesh"))
+			if (value.parent.get("uvs").size != value.parent.get("vertices").size) value.set("skinnedmesh");
+
+		// In 3.3 linkedmesh is now a single type, previously they were linkedweightedmesh if they had weights.
+		for (JsonValue value : find(root, new Array(), 0, "skins", "*", "*", "*", "type", "linkedmesh")) {
+			String slot = value.parent.parent.name.replaceAll("", "");
+			String skinName = value.parent.getString("skin", "default");
+			String parentName = value.parent.getString("parent");
+			if (find(root, new Array(), 0,
+				("skins~~" + skinName + "~~" + slot + "~~" + parentName + "~~type~~skinnedmesh").split("~~")).size > 0)
+				value.set("weightedlinkedmesh");
+		}
+
+		// In 3.3 bounding boxes can be weighted.
+		for (JsonValue value : find(root, new Array(), 0, "skins", "*", "*", "*", "type", "boundingbox"))
+			if (value.parent.getInt("vertexCount") * 2 != value.parent.get("vertices").size)
+				value.parent.parent.remove(value.parent.name);
+
+		// In 3.3 paths were added.
+		for (JsonValue value : find(root, new Array(), 0, "skins", "*", "*", "*", "type", "path")) {
+			String attachment = value.parent.name;
+			value.parent.parent.remove(attachment);
+			String slot = value.parent.parent.name;
+			// Also remove path deform timelines.
+			delete(root, "animations", "*", "ffd", "*", slot, attachment);
+		}
+
+		// In 3.3 IK constraint timelines no longer require bendPositive.
+		for (JsonValue value : find(root, new Array(), 0, "animations", "*", "ik", "*"))
+			for (JsonValue child = value.child; child != null; child = child.next)
+				if (!child.has("bendPositive")) child.addChild("bendPositive", new JsonValue(true));
+
+		// In 3.3 transform constraints can have more than 1 bone.
+		for (JsonValue child = root.getChild("transform"); child != null; child = child.next) {
+			JsonValue bones = child.remove("bones");
+			if (bones != null) child.addChild("bone", new JsonValue(bones.child.asString()));
+		}
+
+		if (args.length > 1)
+			new FileHandle(args[1]).writeString(root.prettyPrint(OutputType.json, 130), false, "UTF-8");
+		else
+			System.out.println(root.prettyPrint(OutputType.json, 130));
+	}
+
+	static void setValue (JsonValue root, String newValue, String... path) {
+		for (JsonValue value : find(root, new Array(), 0, path))
+			value.set(newValue);
+	}
+
+	static void rename (JsonValue root, String newName, String... path) {
+		for (JsonValue value : find(root, new Array(), 0, path))
+			value.name = newName;
+	}
+
+	static void delete (JsonValue root, String... path) {
+		for (JsonValue value : find(root, new Array(), 0, path))
+			value.parent.remove(value.name);
+	}
+
+	static Array<JsonValue> find (JsonValue current, Array<JsonValue> values, int index, String... path) {
+		String name = path[index];
+		if (current.name == null) {
+			if (name.equals("*") && index == path.length - 1)
+				values.add(current);
+			else if (current.has(name)) return find(current.get(name), values, index, path);
+		} else if (name.equals("*") || current.name.equals(name)) {
+			if (++index == path.length || (index == path.length - 1 && current.isString() && current.asString().equals(path[index])))
+				values.add(current);
+			else {
+				for (JsonValue child = current.child; child != null; child = child.next)
+					find(child, values, index, path);
+			}
+		}
+		return values;
+	}
 }

+ 96 - 65
spine-ts/core/src/Texture.ts

@@ -1,66 +1,97 @@
-module spine {
-	export abstract class Texture {
-		protected _image: HTMLImageElement;
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
 
-		constructor (image: HTMLImageElement) {
-			this._image = image;
-		}
-
-		getImage (): HTMLImageElement {
-			return this._image;
-		}
-
-		abstract setFilters (minFilter: TextureFilter, magFilter: TextureFilter): void;
-		abstract setWraps (uWrap: TextureWrap, vWrap: TextureWrap): void;		
-		abstract dispose (): void;
-		
-
-		public static filterFromString (text: string): TextureFilter {
-			switch (text.toLowerCase()) {
-				case "nearest": return TextureFilter.Nearest;
-				case "linear": return TextureFilter.Linear;
-				case "mipmap": return TextureFilter.MipMap;
-				case "mipmapnearestnearest": return TextureFilter.MipMapNearestNearest;
-				case "mipmaplinearnearest": return TextureFilter.MipMapLinearNearest;
-				case "mipmapnearestlinear": return TextureFilter.MipMapNearestLinear;
-				case "mipmaplinearlinear": return TextureFilter.MipMapLinearLinear;
-				default: throw new Error(`Unknown texture filter ${text}`);
-			}
-		}
-
-		public static wrapFromString (text: string): TextureWrap {
-			switch (text.toLowerCase()) {
-				case "mirroredtepeat": return TextureWrap.MirroredRepeat;
-				case "clamptoedge": return TextureWrap.ClampToEdge;
-				case "repeat": return TextureWrap.Repeat;
-				default: throw new Error(`Unknown texture wrap ${text}`);
-			}
-		}
-	}
-
-	export enum TextureFilter {
-		Nearest = 9728, // WebGLRenderingContext.NEAREST
-		Linear = 9729, // WebGLRenderingContext.LINEAR
-		MipMap = 9987, // WebGLRenderingContext.LINEAR_MIPMAP_LINEAR
-		MipMapNearestNearest = 9984, // WebGLRenderingContext.NEAREST_MIPMAP_NEAREST
-		MipMapLinearNearest = 9985, // WebGLRenderingContext.LINEAR_MIPMAP_NEAREST
-		MipMapNearestLinear = 9986, // WebGLRenderingContext.NEAREST_MIPMAP_LINEAR
-		MipMapLinearLinear = 9987 // WebGLRenderingContext.LINEAR_MIPMAP_LINEAR
-	}
-
-	export enum TextureWrap {
-		MirroredRepeat = 33648, // WebGLRenderingContext.MIRRORED_REPEAT
-		ClampToEdge = 33071, // WebGLRenderingContext.CLAMP_TO_EDGE
-		Repeat = 10497 // WebGLRenderingContext.REPEAT
-	}
-
-	export class TextureRegion {
-		renderObject: any;
-		u = 0; v = 0;
-		u2 = 0; v2 = 0;
-		width = 0; height = 0;
-		rotate = false;
-		offsetX = 0; offsetY = 0;
-		originalWidth = 0; originalHeight = 0;
-	}
-}
+module spine {
+	export abstract class Texture {
+		protected _image: HTMLImageElement;
+
+		constructor (image: HTMLImageElement) {
+			this._image = image;
+		}
+
+		getImage (): HTMLImageElement {
+			return this._image;
+		}
+
+		abstract setFilters (minFilter: TextureFilter, magFilter: TextureFilter): void;
+		abstract setWraps (uWrap: TextureWrap, vWrap: TextureWrap): void;		
+		abstract dispose (): void;
+		
+
+		public static filterFromString (text: string): TextureFilter {
+			switch (text.toLowerCase()) {
+				case "nearest": return TextureFilter.Nearest;
+				case "linear": return TextureFilter.Linear;
+				case "mipmap": return TextureFilter.MipMap;
+				case "mipmapnearestnearest": return TextureFilter.MipMapNearestNearest;
+				case "mipmaplinearnearest": return TextureFilter.MipMapLinearNearest;
+				case "mipmapnearestlinear": return TextureFilter.MipMapNearestLinear;
+				case "mipmaplinearlinear": return TextureFilter.MipMapLinearLinear;
+				default: throw new Error(`Unknown texture filter ${text}`);
+			}
+		}
+
+		public static wrapFromString (text: string): TextureWrap {
+			switch (text.toLowerCase()) {
+				case "mirroredtepeat": return TextureWrap.MirroredRepeat;
+				case "clamptoedge": return TextureWrap.ClampToEdge;
+				case "repeat": return TextureWrap.Repeat;
+				default: throw new Error(`Unknown texture wrap ${text}`);
+			}
+		}
+	}
+
+	export enum TextureFilter {
+		Nearest = 9728, // WebGLRenderingContext.NEAREST
+		Linear = 9729, // WebGLRenderingContext.LINEAR
+		MipMap = 9987, // WebGLRenderingContext.LINEAR_MIPMAP_LINEAR
+		MipMapNearestNearest = 9984, // WebGLRenderingContext.NEAREST_MIPMAP_NEAREST
+		MipMapLinearNearest = 9985, // WebGLRenderingContext.LINEAR_MIPMAP_NEAREST
+		MipMapNearestLinear = 9986, // WebGLRenderingContext.NEAREST_MIPMAP_LINEAR
+		MipMapLinearLinear = 9987 // WebGLRenderingContext.LINEAR_MIPMAP_LINEAR
+	}
+
+	export enum TextureWrap {
+		MirroredRepeat = 33648, // WebGLRenderingContext.MIRRORED_REPEAT
+		ClampToEdge = 33071, // WebGLRenderingContext.CLAMP_TO_EDGE
+		Repeat = 10497 // WebGLRenderingContext.REPEAT
+	}
+
+	export class TextureRegion {
+		renderObject: any;
+		u = 0; v = 0;
+		u2 = 0; v2 = 0;
+		width = 0; height = 0;
+		rotate = false;
+		offsetX = 0; offsetY = 0;
+		originalWidth = 0; originalHeight = 0;
+	}
+}

File diff suppressed because it is too large
+ 0 - 18
spine-ts/webgl/src/LoadingScreen.ts


+ 91 - 60
spine-unity/Assets/Examples/Getting Started/Scripts/SpineBeginnerTwo.cs

@@ -1,62 +1,93 @@
-using UnityEngine;
-using System.Collections;
-using Spine.Unity;
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
 
-public class SpineBeginnerTwo : MonoBehaviour {
-
-	#region Inspector
-	// [SpineAnimation] attribute allows an Inspector dropdown of Spine animation names coming form SkeletonAnimation.
-	[SpineAnimation]
-	public string runAnimationName;
-
-	[SpineAnimation]
-	public string idleAnimationName;
-
-	[SpineAnimation]
-	public string walkAnimationName;
-
-	[SpineAnimation]
-	public string shootAnimationName;
-	#endregion
-
-	SkeletonAnimation skeletonAnimation;
-
-	// Spine.AnimationState and Spine.Skeleton are not Unity-serialized objects. You will not see them as fields in the inspector.
-	public Spine.AnimationState spineAnimationState;
-	public Spine.Skeleton skeleton;
-
-	void Start () {
-		// Make sure you get these AnimationState and Skeleton references in Start or Later. Getting and using them in Awake is not guaranteed by default execution order.
-		skeletonAnimation = GetComponent<SkeletonAnimation>();
-		spineAnimationState = skeletonAnimation.state;
-		skeleton = skeletonAnimation.skeleton;
-
-		StartCoroutine(DoDemoRoutine());
-	}
-		
-	/// <summary>This is an infinitely repeating Unity Coroutine. Read the Unity documentation on Coroutines to learn more.</summary>
-	IEnumerator DoDemoRoutine () {
-		
-		while (true) {
-			// SetAnimation is the basic way to set an animation.
-			// SetAnimation sets the animation and starts playing it from the beginning.
-			// Common Mistake: If you keep calling it in Update, it will keep showing the first pose of the animation, do don't do that.
-
-			spineAnimationState.SetAnimation(0, walkAnimationName, true);
-			yield return new WaitForSeconds(1.5f);
-
-			// skeletonAnimation.AnimationName = runAnimationName; // this line also works for quick testing/simple uses.
-			spineAnimationState.SetAnimation(0, runAnimationName, true);
-			yield return new WaitForSeconds(1.5f);
-
-			spineAnimationState.SetAnimation(0, idleAnimationName, true);
-			yield return new WaitForSeconds(1f);
-
-			skeleton.FlipX = true;		// skeleton allows you to flip the skeleton.
-			yield return new WaitForSeconds(0.5f);
-			skeleton.FlipX = false;
-			yield return new WaitForSeconds(0.5f);
-
-		}
-	}
+using UnityEngine;
+using System.Collections;
+using Spine.Unity;
+
+public class SpineBeginnerTwo : MonoBehaviour {
+
+	#region Inspector
+	// [SpineAnimation] attribute allows an Inspector dropdown of Spine animation names coming form SkeletonAnimation.
+	[SpineAnimation]
+	public string runAnimationName;
+
+	[SpineAnimation]
+	public string idleAnimationName;
+
+	[SpineAnimation]
+	public string walkAnimationName;
+
+	[SpineAnimation]
+	public string shootAnimationName;
+	#endregion
+
+	SkeletonAnimation skeletonAnimation;
+
+	// Spine.AnimationState and Spine.Skeleton are not Unity-serialized objects. You will not see them as fields in the inspector.
+	public Spine.AnimationState spineAnimationState;
+	public Spine.Skeleton skeleton;
+
+	void Start () {
+		// Make sure you get these AnimationState and Skeleton references in Start or Later. Getting and using them in Awake is not guaranteed by default execution order.
+		skeletonAnimation = GetComponent<SkeletonAnimation>();
+		spineAnimationState = skeletonAnimation.state;
+		skeleton = skeletonAnimation.skeleton;
+
+		StartCoroutine(DoDemoRoutine());
+	}
+		
+	/// <summary>This is an infinitely repeating Unity Coroutine. Read the Unity documentation on Coroutines to learn more.</summary>
+	IEnumerator DoDemoRoutine () {
+		
+		while (true) {
+			// SetAnimation is the basic way to set an animation.
+			// SetAnimation sets the animation and starts playing it from the beginning.
+			// Common Mistake: If you keep calling it in Update, it will keep showing the first pose of the animation, do don't do that.
+
+			spineAnimationState.SetAnimation(0, walkAnimationName, true);
+			yield return new WaitForSeconds(1.5f);
+
+			// skeletonAnimation.AnimationName = runAnimationName; // this line also works for quick testing/simple uses.
+			spineAnimationState.SetAnimation(0, runAnimationName, true);
+			yield return new WaitForSeconds(1.5f);
+
+			spineAnimationState.SetAnimation(0, idleAnimationName, true);
+			yield return new WaitForSeconds(1f);
+
+			skeleton.FlipX = true;		// skeleton allows you to flip the skeleton.
+			yield return new WaitForSeconds(0.5f);
+			skeleton.FlipX = false;
+			yield return new WaitForSeconds(0.5f);
+
+		}
+	}
 }

+ 50 - 19
spine-unity/Assets/Examples/Getting Started/Scripts/SpineBlinkPlayer.cs

@@ -1,21 +1,52 @@
-using UnityEngine;
-using System.Collections;
-using Spine.Unity;
-
-public class SpineBlinkPlayer : MonoBehaviour {
-	const int BlinkTrack = 1;
-
-	[SpineAnimation]
-	public string blinkAnimation;
-	public float minimumDelay = 0.15f;
-	public float maximumDelay = 3f;
-
-	IEnumerator Start () {
-		var skeletonAnimation = GetComponent<SkeletonAnimation>(); if (skeletonAnimation == null) yield break;
-		while (true) {
-			skeletonAnimation.state.SetAnimation(SpineBlinkPlayer.BlinkTrack, blinkAnimation, false);
-			yield return new WaitForSeconds(Random.Range(minimumDelay, maximumDelay));
-		}
-	}
+/******************************************************************************
+ * 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 System.Collections;
+using Spine.Unity;
+
+public class SpineBlinkPlayer : MonoBehaviour {
+	const int BlinkTrack = 1;
+
+	[SpineAnimation]
+	public string blinkAnimation;
+	public float minimumDelay = 0.15f;
+	public float maximumDelay = 3f;
+
+	IEnumerator Start () {
+		var skeletonAnimation = GetComponent<SkeletonAnimation>(); if (skeletonAnimation == null) yield break;
+		while (true) {
+			skeletonAnimation.state.SetAnimation(SpineBlinkPlayer.BlinkTrack, blinkAnimation, false);
+			yield return new WaitForSeconds(Random.Range(minimumDelay, maximumDelay));
+		}
+	}
+
 }

+ 63 - 32
spine-unity/Assets/Examples/Getting Started/Scripts/SpineboyBeginnerInput.cs

@@ -1,34 +1,65 @@
-using UnityEngine;
-using System.Collections;
-
-public class SpineboyBeginnerInput : MonoBehaviour {
-
-	#region Inspector
-	public string horizontalAxis = "Horizontal";
-	public string attackButton = "Fire1";
-	public string jumpButton = "Jump";
-
-	public SpineboyBeginnerModel model;
-
-	void OnValidate () {
-		if (model == null)
-			model = GetComponent<SpineboyBeginnerModel>();
-	}
-	#endregion
-
-	void Update () {
-		if (model == null) return;
-
-		float currentHorizontal = Input.GetAxisRaw(horizontalAxis);
-		model.TryMove(currentHorizontal);
-
-		if (Input.GetButton(attackButton))
-			model.TryShoot();
-
-		if (Input.GetButtonDown(jumpButton))
-			model.TryJump();
-	
-	}
-
+/******************************************************************************
+ * 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 System.Collections;
+
+public class SpineboyBeginnerInput : MonoBehaviour {
+
+	#region Inspector
+	public string horizontalAxis = "Horizontal";
+	public string attackButton = "Fire1";
+	public string jumpButton = "Jump";
+
+	public SpineboyBeginnerModel model;
+
+	void OnValidate () {
+		if (model == null)
+			model = GetComponent<SpineboyBeginnerModel>();
+	}
+	#endregion
+
+	void Update () {
+		if (model == null) return;
+
+		float currentHorizontal = Input.GetAxisRaw(horizontalAxis);
+		model.TryMove(currentHorizontal);
+
+		if (Input.GetButton(attackButton))
+			model.TryShoot();
+
+		if (Input.GetButtonDown(jumpButton))
+			model.TryJump();
+	
+	}
+
+
 }

+ 113 - 82
spine-unity/Assets/Examples/Getting Started/Scripts/SpineboyBeginnerModel.cs

@@ -1,83 +1,114 @@
-using UnityEngine;
-using System.Collections;
-
-[SelectionBase]
-public class SpineboyBeginnerModel : MonoBehaviour {
-
-	#region Inspector
-	[Header("Current State")]
-	public SpineBeginnerBodyState state;
-	public bool facingLeft;
-	[Range(-1f, 1f)]
-	public float currentSpeed;
-
-	[Header("Balance")]
-	public float shootInterval = 0.12f;
-	#endregion
-
-	float lastShootTime;
-	public event System.Action ShootEvent;	// Lets other scripts know when Spineboy is shooting. Check C# Documentation to learn more about events and delegates.
-
-	#region API
-	public void TryJump () {
-		StartCoroutine(JumpRoutine());
-	}
-
-	public void TryShoot () {
-		float currentTime = Time.time;
-
-		if (currentTime - lastShootTime > shootInterval) {
-			lastShootTime = currentTime;
-			if (ShootEvent != null) ShootEvent();	// Fire the "ShootEvent" event.
-		}
-	}
-
-	public void TryMove (float speed) {
-		currentSpeed = speed; // show the "speed" in the Inspector.
-
-		if (speed != 0) {
-			bool speedIsNegative = (speed < 0f);
-			facingLeft = speedIsNegative; // Change facing direction whenever speed is not 0.
-		}
-			
-		if (state != SpineBeginnerBodyState.Jumping) {
-			state = (speed == 0) ? SpineBeginnerBodyState.Idle : SpineBeginnerBodyState.Running;
-		}
-
-	}
-	#endregion
-
-	IEnumerator JumpRoutine () {
-		if (state == SpineBeginnerBodyState.Jumping) yield break;	// Don't jump when already jumping.
-
-		state = SpineBeginnerBodyState.Jumping;
-
-		// Terribly-coded Fake jumping.
-		{
-			var pos = transform.localPosition;
-			const float jumpTime = 1.2f;
-			const float half = jumpTime * 0.5f;
-			const float jumpPower = 20f;
-			for (float t = 0; t < half; t += Time.deltaTime) {
-				float d = jumpPower * (half - t);
-				transform.Translate((d * Time.deltaTime) * Vector3.up);
-				yield return null;
-			}
-			for (float t = 0; t < half; t += Time.deltaTime) {
-				float d = jumpPower * t;
-				transform.Translate((d * Time.deltaTime) * Vector3.down);
-				yield return null;
-			}
-			transform.localPosition = pos;
-		}
-
-		state = SpineBeginnerBodyState.Idle;
-	}
-
+/******************************************************************************
+ * 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 System.Collections;
+
+[SelectionBase]
+public class SpineboyBeginnerModel : MonoBehaviour {
+
+	#region Inspector
+	[Header("Current State")]
+	public SpineBeginnerBodyState state;
+	public bool facingLeft;
+	[Range(-1f, 1f)]
+	public float currentSpeed;
+
+	[Header("Balance")]
+	public float shootInterval = 0.12f;
+	#endregion
+
+	float lastShootTime;
+	public event System.Action ShootEvent;	// Lets other scripts know when Spineboy is shooting. Check C# Documentation to learn more about events and delegates.
+
+	#region API
+	public void TryJump () {
+		StartCoroutine(JumpRoutine());
+	}
+
+	public void TryShoot () {
+		float currentTime = Time.time;
+
+		if (currentTime - lastShootTime > shootInterval) {
+			lastShootTime = currentTime;
+			if (ShootEvent != null) ShootEvent();	// Fire the "ShootEvent" event.
+		}
+	}
+
+	public void TryMove (float speed) {
+		currentSpeed = speed; // show the "speed" in the Inspector.
+
+		if (speed != 0) {
+			bool speedIsNegative = (speed < 0f);
+			facingLeft = speedIsNegative; // Change facing direction whenever speed is not 0.
+		}
+			
+		if (state != SpineBeginnerBodyState.Jumping) {
+			state = (speed == 0) ? SpineBeginnerBodyState.Idle : SpineBeginnerBodyState.Running;
+		}
+
+	}
+	#endregion
+
+	IEnumerator JumpRoutine () {
+		if (state == SpineBeginnerBodyState.Jumping) yield break;	// Don't jump when already jumping.
+
+		state = SpineBeginnerBodyState.Jumping;
+
+		// Terribly-coded Fake jumping.
+		{
+			var pos = transform.localPosition;
+			const float jumpTime = 1.2f;
+			const float half = jumpTime * 0.5f;
+			const float jumpPower = 20f;
+			for (float t = 0; t < half; t += Time.deltaTime) {
+				float d = jumpPower * (half - t);
+				transform.Translate((d * Time.deltaTime) * Vector3.up);
+				yield return null;
+			}
+			for (float t = 0; t < half; t += Time.deltaTime) {
+				float d = jumpPower * t;
+				transform.Translate((d * Time.deltaTime) * Vector3.down);
+				yield return null;
+			}
+			transform.localPosition = pos;
+		}
+
+		state = SpineBeginnerBodyState.Idle;
+	}
+
+}
+
+public enum SpineBeginnerBodyState {
+	Idle,
+	Running,
+	Jumping
 }
-
-public enum SpineBeginnerBodyState {
-	Idle,
-	Running,
-	Jumping
-}

+ 136 - 105
spine-unity/Assets/Examples/Getting Started/Scripts/SpineboyBeginnerView.cs

@@ -1,106 +1,137 @@
-using UnityEngine;
-using System.Collections;
-using Spine.Unity;
-
-public class SpineboyBeginnerView : MonoBehaviour {
-	
-	#region Inspector
-	[Header("Components")]
-	public SpineboyBeginnerModel model;
-	public SkeletonAnimation skeletonAnimation;
-	//public ParticleSystem gunParticles;
-
-	[SpineAnimation] public string run, idle, shoot, jump;
-	[SpineEvent] public string footstepEventName;
-
-	[Header("Audio")]
-	public float footstepPitchOffset = 0.2f;
-	public float gunsoundPitchOffset = 0.13f;
-	public AudioSource footstepSource, gunSource, jumpSource;
-
-	[Header("Effects")]
-	public ParticleSystem gunParticles;
-	#endregion
-
-	SpineBeginnerBodyState previousViewState;
-
-	void Start () {
-		if (skeletonAnimation == null) return;
-		model.ShootEvent += PlayShoot;
-		skeletonAnimation.state.Event += HandleEvent;
-	}
-
-	void HandleEvent (Spine.AnimationState state, int trackIndex, Spine.Event e) {
-		if (e.Data.Name == footstepEventName) {
-			PlayFootstepSound();
-		}
-	}
-
-	void Update () {
-		if (skeletonAnimation == null) return;
-		if (model == null) return;
-
-		if (skeletonAnimation.skeleton.FlipX != model.facingLeft) {	// Detect changes in model.facingLeft
-			Turn(model.facingLeft);
-		}
-
-		// Detect changes in model.state
-		var currentModelState = model.state;
-
-		if (previousViewState != currentModelState) {
-			PlayNewStableAnimation();
-		}
-		
-		previousViewState = currentModelState;
-	}
-
-	void PlayNewStableAnimation () {
-		var newModelState = model.state;
-		string nextAnimation;
-
-		// Add conditionals to not interrupt transient animations.
-
-		if (previousViewState == SpineBeginnerBodyState.Jumping && newModelState != SpineBeginnerBodyState.Jumping) {
-			PlayFootstepSound();
-		}
-
-		if (newModelState == SpineBeginnerBodyState.Jumping) {
-			jumpSource.Play();
-			nextAnimation = jump;
-		} else {
-			if (newModelState == SpineBeginnerBodyState.Running) {
-				nextAnimation = run;
-			} else {
-				nextAnimation = idle;
-			}
-		}
-
-		skeletonAnimation.state.SetAnimation(0, nextAnimation, true);
-	}
-
-	void PlayFootstepSound () {
-		footstepSource.Play();
-		footstepSource.pitch = GetRandomPitch(footstepPitchOffset);
-	}
-
-	#region Transient Actions
-	public void PlayShoot () {
-		// Play the shoot animation on track 1.
-		skeletonAnimation.state.SetAnimation(1, shoot, false);
-		gunSource.pitch = GetRandomPitch(gunsoundPitchOffset);
-		gunSource.Play();
-		gunParticles.Play();
-	}
-
-	public void Turn (bool facingLeft) {
-		skeletonAnimation.skeleton.FlipX = facingLeft;
-		// Maybe play a transient turning animation too, then call ChangeStableAnimation.
-	}
-	#endregion
-
-	#region Utility
-	public float GetRandomPitch (float maxPitchOffset) {
-		return 1f + Random.Range(-maxPitchOffset, maxPitchOffset);
-	}
-	#endregion
+/******************************************************************************
+ * 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 System.Collections;
+using Spine.Unity;
+
+public class SpineboyBeginnerView : MonoBehaviour {
+	
+	#region Inspector
+	[Header("Components")]
+	public SpineboyBeginnerModel model;
+	public SkeletonAnimation skeletonAnimation;
+	//public ParticleSystem gunParticles;
+
+	[SpineAnimation] public string run, idle, shoot, jump;
+	[SpineEvent] public string footstepEventName;
+
+	[Header("Audio")]
+	public float footstepPitchOffset = 0.2f;
+	public float gunsoundPitchOffset = 0.13f;
+	public AudioSource footstepSource, gunSource, jumpSource;
+
+	[Header("Effects")]
+	public ParticleSystem gunParticles;
+	#endregion
+
+	SpineBeginnerBodyState previousViewState;
+
+	void Start () {
+		if (skeletonAnimation == null) return;
+		model.ShootEvent += PlayShoot;
+		skeletonAnimation.state.Event += HandleEvent;
+	}
+
+	void HandleEvent (Spine.AnimationState state, int trackIndex, Spine.Event e) {
+		if (e.Data.Name == footstepEventName) {
+			PlayFootstepSound();
+		}
+	}
+
+	void Update () {
+		if (skeletonAnimation == null) return;
+		if (model == null) return;
+
+		if (skeletonAnimation.skeleton.FlipX != model.facingLeft) {	// Detect changes in model.facingLeft
+			Turn(model.facingLeft);
+		}
+
+		// Detect changes in model.state
+		var currentModelState = model.state;
+
+		if (previousViewState != currentModelState) {
+			PlayNewStableAnimation();
+		}
+		
+		previousViewState = currentModelState;
+	}
+
+	void PlayNewStableAnimation () {
+		var newModelState = model.state;
+		string nextAnimation;
+
+		// Add conditionals to not interrupt transient animations.
+
+		if (previousViewState == SpineBeginnerBodyState.Jumping && newModelState != SpineBeginnerBodyState.Jumping) {
+			PlayFootstepSound();
+		}
+
+		if (newModelState == SpineBeginnerBodyState.Jumping) {
+			jumpSource.Play();
+			nextAnimation = jump;
+		} else {
+			if (newModelState == SpineBeginnerBodyState.Running) {
+				nextAnimation = run;
+			} else {
+				nextAnimation = idle;
+			}
+		}
+
+		skeletonAnimation.state.SetAnimation(0, nextAnimation, true);
+	}
+
+	void PlayFootstepSound () {
+		footstepSource.Play();
+		footstepSource.pitch = GetRandomPitch(footstepPitchOffset);
+	}
+
+	#region Transient Actions
+	public void PlayShoot () {
+		// Play the shoot animation on track 1.
+		skeletonAnimation.state.SetAnimation(1, shoot, false);
+		gunSource.pitch = GetRandomPitch(gunsoundPitchOffset);
+		gunSource.Play();
+		gunParticles.Play();
+	}
+
+	public void Turn (bool facingLeft) {
+		skeletonAnimation.skeleton.FlipX = facingLeft;
+		// Maybe play a transient turning animation too, then call ChangeStableAnimation.
+	}
+	#endregion
+
+	#region Utility
+	public float GetRandomPitch (float maxPitchOffset) {
+		return 1f + Random.Range(-maxPitchOffset, maxPitchOffset);
+	}
+	#endregion
 }

+ 116 - 85
spine-unity/Assets/Examples/Scripts/RaggedySpineboy.cs

@@ -1,86 +1,117 @@
-using UnityEngine;
-using System.Collections;
-using Spine.Unity;
-
-public class RaggedySpineboy : MonoBehaviour {
-
-	public LayerMask groundMask;
-	public float restoreDuration = 0.5f;
-	public Vector2 launchVelocity = new Vector2(50,100);
-
-	Spine.Unity.Modules.SkeletonRagdoll2D ragdoll;
-	Collider2D naturalCollider;
-
-	void Start () {
-		
-		ragdoll = GetComponent<Spine.Unity.Modules.SkeletonRagdoll2D>();
-		naturalCollider = GetComponent<Collider2D>();
-	}
-
-	void AddRigidbody () {
-		var rb = gameObject.AddComponent<Rigidbody2D>();
-		#if UNITY_5_1 || UNITY_5_2 || UNITY_5_3 || UNITY_5_4 || UNITY_5_5
-        rb.freezeRotation = true;
-		#else
-		rb.fixedAngle = true;
-		#endif
-		naturalCollider.enabled = true;
-	}
-
-	void RemoveRigidbody () {
-		Destroy(GetComponent<Rigidbody2D>());
-		naturalCollider.enabled = false;
-	}
-
-	void Update () {
-		
-	}
-
-	void OnMouseUp () {
-		if (naturalCollider.enabled) {
-			Launch();
-		}
-	}
-
-	void Launch () {
-		RemoveRigidbody();
-		ragdoll.Apply();
-		ragdoll.RootRigidbody.velocity = new Vector2(Random.Range(-launchVelocity.x, launchVelocity.x), launchVelocity.y);
-		StartCoroutine(WaitUntilStopped());
-	}
-
-	IEnumerator Restore () {
-		Vector3 estimatedPos = ragdoll.EstimatedSkeletonPosition;
-		Vector3 rbPosition = ragdoll.RootRigidbody.position;
-
-		Vector3 skeletonPoint = estimatedPos;
-		RaycastHit2D hit = Physics2D.Raycast((Vector2)rbPosition, (Vector2)(estimatedPos - rbPosition), Vector3.Distance(estimatedPos, rbPosition), groundMask);
-		if (hit.collider != null)
-			skeletonPoint = hit.point;
-		
-
-		ragdoll.RootRigidbody.isKinematic = true;
-		ragdoll.SetSkeletonPosition(skeletonPoint);
-
-		yield return ragdoll.SmoothMix(0, restoreDuration);
-		ragdoll.Remove();
-
-		AddRigidbody();
-	}
-
-	IEnumerator WaitUntilStopped () {
-		yield return new WaitForSeconds(0.5f);
-
-		float t = 0;
-		while (t < 0.5f) {
-			if (ragdoll.RootRigidbody.velocity.magnitude > 0.09f)
-				t = 0;
-			else
-				t += Time.deltaTime;
-
-			yield return null;
-		}
-
-		StartCoroutine(Restore());
-	}
+/******************************************************************************
+ * 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 System.Collections;
+using Spine.Unity;
+
+public class RaggedySpineboy : MonoBehaviour {
+
+	public LayerMask groundMask;
+	public float restoreDuration = 0.5f;
+	public Vector2 launchVelocity = new Vector2(50,100);
+
+	Spine.Unity.Modules.SkeletonRagdoll2D ragdoll;
+	Collider2D naturalCollider;
+
+	void Start () {
+		
+		ragdoll = GetComponent<Spine.Unity.Modules.SkeletonRagdoll2D>();
+		naturalCollider = GetComponent<Collider2D>();
+	}
+
+	void AddRigidbody () {
+		var rb = gameObject.AddComponent<Rigidbody2D>();
+		#if UNITY_5_1 || UNITY_5_2 || UNITY_5_3 || UNITY_5_4 || UNITY_5_5
+        rb.freezeRotation = true;
+		#else
+		rb.fixedAngle = true;
+		#endif
+		naturalCollider.enabled = true;
+	}
+
+	void RemoveRigidbody () {
+		Destroy(GetComponent<Rigidbody2D>());
+		naturalCollider.enabled = false;
+	}
+
+	void Update () {
+		
+	}
+
+	void OnMouseUp () {
+		if (naturalCollider.enabled) {
+			Launch();
+		}
+	}
+
+	void Launch () {
+		RemoveRigidbody();
+		ragdoll.Apply();
+		ragdoll.RootRigidbody.velocity = new Vector2(Random.Range(-launchVelocity.x, launchVelocity.x), launchVelocity.y);
+		StartCoroutine(WaitUntilStopped());
+	}
+
+	IEnumerator Restore () {
+		Vector3 estimatedPos = ragdoll.EstimatedSkeletonPosition;
+		Vector3 rbPosition = ragdoll.RootRigidbody.position;
+
+		Vector3 skeletonPoint = estimatedPos;
+		RaycastHit2D hit = Physics2D.Raycast((Vector2)rbPosition, (Vector2)(estimatedPos - rbPosition), Vector3.Distance(estimatedPos, rbPosition), groundMask);
+		if (hit.collider != null)
+			skeletonPoint = hit.point;
+		
+
+		ragdoll.RootRigidbody.isKinematic = true;
+		ragdoll.SetSkeletonPosition(skeletonPoint);
+
+		yield return ragdoll.SmoothMix(0, restoreDuration);
+		ragdoll.Remove();
+
+		AddRigidbody();
+	}
+
+	IEnumerator WaitUntilStopped () {
+		yield return new WaitForSeconds(0.5f);
+
+		float t = 0;
+		while (t < 0.5f) {
+			if (ragdoll.RootRigidbody.velocity.magnitude > 0.09f)
+				t = 0;
+			else
+				t += Time.deltaTime;
+
+			yield return null;
+		}
+
+		StartCoroutine(Restore());
+	}
 }

+ 73 - 42
spine-unity/Assets/Examples/Scripts/SpineGauge.cs

@@ -1,44 +1,75 @@
-using UnityEngine;
-using System.Collections;
-using Spine.Unity;
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
 
-[ExecuteInEditMode]
-[RequireComponent(typeof(SkeletonRenderer))]
-public class SpineGauge : MonoBehaviour {
-
-	#region Inspector
-	[Range(0,1)]
-	public float fillPercent = 0;
-
-	[SpineAnimation]
-	public string fillAnimationName;
-	#endregion
-
-	SkeletonRenderer skeletonRenderer;
-	Spine.Animation fillAnimation;
-
-	void Awake () {
-		skeletonRenderer = GetComponent<SkeletonRenderer>();
-
-	}
-
-	void Update () {
-		SetGaugePercent(fillPercent);
-	}
-
-	public void SetGaugePercent (float x) {
-		if (skeletonRenderer == null) return;
-		var skeleton = skeletonRenderer.skeleton; if (skeleton == null) return;
-
-		// Make super-sure that fillAnimation isn't null. Early exit if it is.
-		if (fillAnimation == null) {
-			fillAnimation = skeleton.Data.FindAnimation(fillAnimationName);
-			if (fillAnimation == null) return;
-		}
-			
-		fillAnimation.Apply(skeleton, 0, x, false, null);
-
-		skeleton.Update(Time.deltaTime);
-		skeleton.UpdateWorldTransform();
-	}
+using UnityEngine;
+using System.Collections;
+using Spine.Unity;
+
+[ExecuteInEditMode]
+[RequireComponent(typeof(SkeletonRenderer))]
+public class SpineGauge : MonoBehaviour {
+
+	#region Inspector
+	[Range(0,1)]
+	public float fillPercent = 0;
+
+	[SpineAnimation]
+	public string fillAnimationName;
+	#endregion
+
+	SkeletonRenderer skeletonRenderer;
+	Spine.Animation fillAnimation;
+
+	void Awake () {
+		skeletonRenderer = GetComponent<SkeletonRenderer>();
+
+	}
+
+	void Update () {
+		SetGaugePercent(fillPercent);
+	}
+
+	public void SetGaugePercent (float x) {
+		if (skeletonRenderer == null) return;
+		var skeleton = skeletonRenderer.skeleton; if (skeleton == null) return;
+
+		// Make super-sure that fillAnimation isn't null. Early exit if it is.
+		if (fillAnimation == null) {
+			fillAnimation = skeleton.Data.FindAnimation(fillAnimationName);
+			if (fillAnimation == null) return;
+		}
+			
+		fillAnimation.Apply(skeleton, 0, x, false, null);
+
+		skeleton.Update(Time.deltaTime);
+		skeleton.UpdateWorldTransform();
+	}
 }

+ 80 - 50
spine-unity/Assets/Examples/Scripts/SpineboyPole.cs

@@ -1,52 +1,82 @@
-using UnityEngine;
-using System.Collections;
-using Spine.Unity;
+/******************************************************************************
+ * 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 Spine.Unity.Modules;
-
-public class SpineboyPole : MonoBehaviour {
-	public SkeletonAnimation skeletonAnimation;
-	public SkeletonRenderSeparator separator;
-
-	[Space(18)]
-	[SpineAnimation]
-	public string run;
-	[SpineAnimation]
-	public string pole;
-	public float startX;
-	public float endX;
-
-	const float Speed = 18f;
-	const float RunTimeScale = 1.5f;
-
-	IEnumerator Start () {
-		var state = skeletonAnimation.state;
-
-		while (true) {
-			// Run phase
-			SetXPosition(startX);
-			separator.enabled = false; // Disable Separator during run.
-			state.SetAnimation(0, run, true);
-			state.TimeScale = RunTimeScale;
-
-			while (transform.localPosition.x < endX) {
-				transform.Translate(Vector3.right * Speed * Time.deltaTime);
-				yield return null;
-			}
-
-			// Hit phase
-			SetXPosition(endX);
-			separator.enabled = true; // Enable Separator when hit
-			var poleTrack = state.SetAnimation(0, pole, false);
-			yield return new WaitForSpineAnimationComplete(poleTrack);
-			yield return new WaitForSeconds(1f);
-		}
-	}
-
-	void SetXPosition (float x) {
-		var tp = transform.localPosition;
-		tp.x = x;
-		transform.localPosition = tp;
-	}
+using UnityEngine;
+using System.Collections;
+using Spine.Unity;
+
+using Spine.Unity.Modules;
+
+public class SpineboyPole : MonoBehaviour {
+	public SkeletonAnimation skeletonAnimation;
+	public SkeletonRenderSeparator separator;
+
+	[Space(18)]
+	[SpineAnimation]
+	public string run;
+	[SpineAnimation]
+	public string pole;
+	public float startX;
+	public float endX;
+
+	const float Speed = 18f;
+	const float RunTimeScale = 1.5f;
+
+	IEnumerator Start () {
+		var state = skeletonAnimation.state;
+
+		while (true) {
+			// Run phase
+			SetXPosition(startX);
+			separator.enabled = false; // Disable Separator during run.
+			state.SetAnimation(0, run, true);
+			state.TimeScale = RunTimeScale;
+
+			while (transform.localPosition.x < endX) {
+				transform.Translate(Vector3.right * Speed * Time.deltaTime);
+				yield return null;
+			}
+
+			// Hit phase
+			SetXPosition(endX);
+			separator.enabled = true; // Enable Separator when hit
+			var poleTrack = state.SetAnimation(0, pole, false);
+			yield return new WaitForSpineAnimationComplete(poleTrack);
+			yield return new WaitForSeconds(1f);
+		}
+	}
+
+	void SetXPosition (float x) {
+		var tp = transform.localPosition;
+		tp.x = x;
+		transform.localPosition = tp;
+	}
 }
-

+ 32 - 1
spine-unity/Assets/spine-unity/Editor/AssetDatabaseAvailabilityDetector.cs

@@ -1,3 +1,34 @@
+/******************************************************************************
+ * 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;
 
 namespace Spine.Unity.Editor {
@@ -18,4 +49,4 @@ namespace Spine.Unity.Editor {
 			return _isMarkerLoaded;
 		}
 	}
-}
+}

+ 161 - 130
spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSimpleMeshGenerator.cs

@@ -1,133 +1,164 @@
-/******************************************************************************
- * 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.
+/******************************************************************************
+ * 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;
-
-namespace Spine.Unity.MeshGeneration {
-	public class ArraysSimpleMeshGenerator : ArraysMeshGenerator, ISimpleMeshGenerator {
-		#region Settings
-		protected float scale = 1f;
-		public float Scale { get { return scale; } set { scale = value; } }
-		public float ZSpacing { get; set; }
-		#endregion
-
-		protected Mesh lastGeneratedMesh;
-		public Mesh LastGeneratedMesh {	get { return lastGeneratedMesh; } }
-
-		readonly DoubleBufferedMesh doubleBufferedMesh = new DoubleBufferedMesh();
-		int[] triangles;
-
-		public Mesh GenerateMesh (Skeleton skeleton) {
-			int totalVertexCount = 0; // size of vertex arrays
-			int totalTriangleCount = 0; // size of index array
-
-			// STEP 1 : GenerateInstruction(). Count verts and tris to determine array sizes.
-			var drawOrderItems = skeleton.drawOrder.Items;
-			int drawOrderCount = skeleton.drawOrder.Count;
-			for (int i = 0; i < drawOrderCount; i++) {
-				Slot slot = drawOrderItems[i];
-				Attachment attachment = slot.attachment;
-				int attachmentVertexCount, attachmentTriangleCount;
-				var regionAttachment = attachment as RegionAttachment;
-				if (regionAttachment != null) {					
-					attachmentVertexCount = 4;
-					attachmentTriangleCount = 6;
-				} else {
-					var meshAttachment = attachment as MeshAttachment;
-					if (meshAttachment != null) {
-						attachmentVertexCount = meshAttachment.worldVerticesLength >> 1;
-						attachmentTriangleCount = meshAttachment.triangles.Length;
-					} else {
-						continue;
-					}
-				}
-				totalTriangleCount += attachmentTriangleCount;
-				totalVertexCount += attachmentVertexCount;
-			}
-
-			// STEP 2 : Ensure buffers are the correct size
-			ArraysMeshGenerator.EnsureSize(totalVertexCount, ref this.meshVertices, ref this.meshUVs, ref this.meshColors32);
-			this.triangles = this.triangles ?? new int[totalTriangleCount];
-				
-			// STEP 3 : Update vertex buffer
-			const float zFauxHalfThickness = 0.01f;	// Somehow needs this thickness for bounds to work properly in some cases (eg, Unity UI clipping)
-			Vector3 meshBoundsMin;
-			Vector3 meshBoundsMax;
-			if (totalVertexCount == 0) {
-				meshBoundsMin = new Vector3(0, 0, 0);
-				meshBoundsMax = new Vector3(0, 0, 0);
-			} else {
-				meshBoundsMin.x = int.MaxValue;
-				meshBoundsMin.y = int.MaxValue;
-				meshBoundsMax.x = int.MinValue;
-				meshBoundsMax.y = int.MinValue;
-				meshBoundsMin.z = -zFauxHalfThickness * scale;
-				meshBoundsMax.z = zFauxHalfThickness * scale;
-
-				int vertexIndex = 0;
-				ArraysMeshGenerator.FillVerts(skeleton, 0, drawOrderCount, this.ZSpacing, this.PremultiplyVertexColors, this.meshVertices, this.meshUVs, this.meshColors32, ref vertexIndex, ref this.attachmentVertexBuffer, ref meshBoundsMin, ref meshBoundsMax);
-
-				// Apply scale to vertices
-				meshBoundsMax.x *= scale; meshBoundsMax.y *= scale;
-				meshBoundsMin.x *= scale; meshBoundsMax.y *= scale;
-				var vertices = this.meshVertices;
-				for (int i = 0; i < totalVertexCount; i++) {
-					Vector3 p = vertices[i];
-					p.x *= scale;
-					p.y *= scale;
-					vertices[i] = p;
-				}
-			}
-				
-			// Step 4 : Update Triangles buffer
-			ArraysMeshGenerator.FillTriangles(ref this.triangles, skeleton, totalTriangleCount, 0, 0, drawOrderCount, true);
-
-			// Step 5 : Update Mesh with buffers
-			var mesh = doubleBufferedMesh.GetNextMesh();
-			mesh.vertices = this.meshVertices;
-			mesh.colors32 = meshColors32;
-			mesh.uv = meshUVs;
-			mesh.bounds = ArraysMeshGenerator.ToBounds(meshBoundsMin, meshBoundsMax);
-			mesh.triangles = triangles;
-			TryAddNormalsTo(mesh, totalVertexCount);
-
-			if (addTangents) { 
-				SolveTangents2DEnsureSize(ref this.meshTangents, ref this.tempTanBuffer, totalVertexCount);
-				SolveTangents2DTriangles(this.tempTanBuffer, triangles, totalTriangleCount, meshVertices, meshUVs, totalVertexCount);
-				SolveTangents2DBuffer(this.meshTangents, this.tempTanBuffer, totalVertexCount);
-			}
-
-			lastGeneratedMesh = mesh;
-			return mesh;
-		}
-
-	}
 
+/******************************************************************************
+ * 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;
+
+namespace Spine.Unity.MeshGeneration {
+	public class ArraysSimpleMeshGenerator : ArraysMeshGenerator, ISimpleMeshGenerator {
+		#region Settings
+		protected float scale = 1f;
+		public float Scale { get { return scale; } set { scale = value; } }
+		public float ZSpacing { get; set; }
+		#endregion
+
+		protected Mesh lastGeneratedMesh;
+		public Mesh LastGeneratedMesh {	get { return lastGeneratedMesh; } }
+
+		readonly DoubleBufferedMesh doubleBufferedMesh = new DoubleBufferedMesh();
+		int[] triangles;
+
+		public Mesh GenerateMesh (Skeleton skeleton) {
+			int totalVertexCount = 0; // size of vertex arrays
+			int totalTriangleCount = 0; // size of index array
+
+			// STEP 1 : GenerateInstruction(). Count verts and tris to determine array sizes.
+			var drawOrderItems = skeleton.drawOrder.Items;
+			int drawOrderCount = skeleton.drawOrder.Count;
+			for (int i = 0; i < drawOrderCount; i++) {
+				Slot slot = drawOrderItems[i];
+				Attachment attachment = slot.attachment;
+				int attachmentVertexCount, attachmentTriangleCount;
+				var regionAttachment = attachment as RegionAttachment;
+				if (regionAttachment != null) {					
+					attachmentVertexCount = 4;
+					attachmentTriangleCount = 6;
+				} else {
+					var meshAttachment = attachment as MeshAttachment;
+					if (meshAttachment != null) {
+						attachmentVertexCount = meshAttachment.worldVerticesLength >> 1;
+						attachmentTriangleCount = meshAttachment.triangles.Length;
+					} else {
+						continue;
+					}
+				}
+				totalTriangleCount += attachmentTriangleCount;
+				totalVertexCount += attachmentVertexCount;
+			}
+
+			// STEP 2 : Ensure buffers are the correct size
+			ArraysMeshGenerator.EnsureSize(totalVertexCount, ref this.meshVertices, ref this.meshUVs, ref this.meshColors32);
+			this.triangles = this.triangles ?? new int[totalTriangleCount];
+				
+			// STEP 3 : Update vertex buffer
+			const float zFauxHalfThickness = 0.01f;	// Somehow needs this thickness for bounds to work properly in some cases (eg, Unity UI clipping)
+			Vector3 meshBoundsMin;
+			Vector3 meshBoundsMax;
+			if (totalVertexCount == 0) {
+				meshBoundsMin = new Vector3(0, 0, 0);
+				meshBoundsMax = new Vector3(0, 0, 0);
+			} else {
+				meshBoundsMin.x = int.MaxValue;
+				meshBoundsMin.y = int.MaxValue;
+				meshBoundsMax.x = int.MinValue;
+				meshBoundsMax.y = int.MinValue;
+				meshBoundsMin.z = -zFauxHalfThickness * scale;
+				meshBoundsMax.z = zFauxHalfThickness * scale;
+
+				int vertexIndex = 0;
+				ArraysMeshGenerator.FillVerts(skeleton, 0, drawOrderCount, this.ZSpacing, this.PremultiplyVertexColors, this.meshVertices, this.meshUVs, this.meshColors32, ref vertexIndex, ref this.attachmentVertexBuffer, ref meshBoundsMin, ref meshBoundsMax);
+
+				// Apply scale to vertices
+				meshBoundsMax.x *= scale; meshBoundsMax.y *= scale;
+				meshBoundsMin.x *= scale; meshBoundsMax.y *= scale;
+				var vertices = this.meshVertices;
+				for (int i = 0; i < totalVertexCount; i++) {
+					Vector3 p = vertices[i];
+					p.x *= scale;
+					p.y *= scale;
+					vertices[i] = p;
+				}
+			}
+				
+			// Step 4 : Update Triangles buffer
+			ArraysMeshGenerator.FillTriangles(ref this.triangles, skeleton, totalTriangleCount, 0, 0, drawOrderCount, true);
+
+			// Step 5 : Update Mesh with buffers
+			var mesh = doubleBufferedMesh.GetNextMesh();
+			mesh.vertices = this.meshVertices;
+			mesh.colors32 = meshColors32;
+			mesh.uv = meshUVs;
+			mesh.bounds = ArraysMeshGenerator.ToBounds(meshBoundsMin, meshBoundsMax);
+			mesh.triangles = triangles;
+			TryAddNormalsTo(mesh, totalVertexCount);
+
+			if (addTangents) { 
+				SolveTangents2DEnsureSize(ref this.meshTangents, ref this.tempTanBuffer, totalVertexCount);
+				SolveTangents2DTriangles(this.tempTanBuffer, triangles, totalTriangleCount, meshVertices, meshUVs, totalVertexCount);
+				SolveTangents2DBuffer(this.meshTangents, this.tempTanBuffer, totalVertexCount);
+			}
+
+			lastGeneratedMesh = mesh;
+			return mesh;
+		}
+
+	}
+
 }

+ 237 - 206
spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSubmeshSetMeshGenerator.cs

@@ -1,209 +1,240 @@
-/******************************************************************************
- * 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.
+/******************************************************************************
+ * 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;
-
-namespace Spine.Unity.MeshGeneration {
-	public class ArraysSubmeshSetMeshGenerator : ArraysMeshGenerator, ISubmeshSetMeshGenerator {
-		#region Settings
-		public float ZSpacing { get; set; }
-		#endregion
-
-		readonly DoubleBuffered<SmartMesh> doubleBufferedSmartMesh = new DoubleBuffered<SmartMesh>();
-		readonly ExposedList<SubmeshInstruction> currentInstructions = new ExposedList<SubmeshInstruction>();
-		readonly ExposedList<Attachment> attachmentBuffer = new ExposedList<Attachment>();
-		readonly ExposedList<SubmeshTriangleBuffer> submeshBuffers = new ExposedList<SubmeshTriangleBuffer>();
-		Material[] sharedMaterials = new Material[0];
-
-		public MeshAndMaterials GenerateMesh (ExposedList<SubmeshInstruction> instructions, int startSubmesh, int endSubmesh) {
-			// STEP 0: Prepare instructions.
-			var paramItems = instructions.Items;
-			currentInstructions.Clear(false);
-			for (int i = startSubmesh, n = endSubmesh; i < n; i++) {
-				this.currentInstructions.Add(paramItems[i]);
-			}
-			var smartMesh = doubleBufferedSmartMesh.GetNext();
-			var mesh = smartMesh.mesh;
-			int submeshCount = currentInstructions.Count;
-			var currentInstructionsItems = currentInstructions.Items;
-			int vertexCount = 0;
-			for (int i = 0; i < submeshCount; i++) {
-				currentInstructionsItems[i].firstVertexIndex = vertexCount;// Ensure current instructions have correct cached values.
-				vertexCount += currentInstructionsItems[i].vertexCount; // vertexCount will also be used for the rest of this method.
-			}
-
-			// STEP 1: Ensure correct buffer sizes.
-			bool vertBufferResized = ArraysMeshGenerator.EnsureSize(vertexCount, ref this.meshVertices, ref this.meshUVs, ref this.meshColors32); 
-			bool submeshBuffersResized = ArraysMeshGenerator.EnsureTriangleBuffersSize(submeshBuffers, submeshCount, currentInstructionsItems);
-
-			// STEP 2: Update buffers based on Skeleton.
-
-			// Initial values for manual Mesh Bounds calculation
-			Vector3 meshBoundsMin;
-			Vector3 meshBoundsMax;
-			float zSpacing = this.ZSpacing;
-			if (vertexCount <= 0) {
-				meshBoundsMin = new Vector3(0, 0, 0);
-				meshBoundsMax = new Vector3(0, 0, 0);
-			} else {
-				meshBoundsMin.x = int.MaxValue;
-				meshBoundsMin.y = int.MaxValue;
-				meshBoundsMax.x = int.MinValue;
-				meshBoundsMax.y = int.MinValue;
-
-				int endSlot = currentInstructionsItems[submeshCount - 1].endSlot;
-				if (zSpacing > 0f) {
-					meshBoundsMin.z = 0f;
-					meshBoundsMax.z = zSpacing * endSlot;
-				} else {
-					meshBoundsMin.z = zSpacing * endSlot;
-					meshBoundsMax.z = 0f;
-				}
-			}
-				
-			// For each submesh, add vertex data from attachments.
-			var workingAttachments = this.attachmentBuffer;
-			workingAttachments.Clear(false);
-			int vertexIndex = 0; // modified by FillVerts
-			for (int submeshIndex = 0; submeshIndex < submeshCount; submeshIndex++) {
-				var currentInstruction = currentInstructionsItems[submeshIndex];
-				int startSlot = currentInstruction.startSlot;
-				int endSlot = currentInstruction.endSlot;
-				var skeleton = currentInstruction.skeleton;
-				var skeletonDrawOrderItems = skeleton.DrawOrder.Items;
-				for (int i = startSlot; i < endSlot; i++) {
-					var ca = skeletonDrawOrderItems[i].attachment;
-					if (ca != null) workingAttachments.Add(ca); // Includes BoundingBoxes. This is ok.
-				}
-				ArraysMeshGenerator.FillVerts(skeleton, startSlot, endSlot, zSpacing, this.PremultiplyVertexColors, this.meshVertices, this.meshUVs, this.meshColors32, ref vertexIndex, ref this.attachmentVertexBuffer, ref meshBoundsMin, ref meshBoundsMax);
-			}
-
-			bool structureDoesntMatch = vertBufferResized || submeshBuffersResized || smartMesh.StructureDoesntMatch(workingAttachments, currentInstructions);
-			for (int submeshIndex = 0; submeshIndex < submeshCount; submeshIndex++) {
-				var currentInstruction = currentInstructionsItems[submeshIndex];
-				if (structureDoesntMatch) {
-					var currentBuffer = submeshBuffers.Items[submeshIndex];
-					bool isLastSubmesh = (submeshIndex == submeshCount - 1);
-					ArraysMeshGenerator.FillTriangles(ref currentBuffer.triangles, currentInstruction.skeleton, currentInstruction.triangleCount, currentInstruction.firstVertexIndex, currentInstruction.startSlot, currentInstruction.endSlot, isLastSubmesh);
-					currentBuffer.triangleCount = currentInstruction.triangleCount;
-					currentBuffer.firstVertex = currentInstruction.firstVertexIndex;
-				}
-			}
-
-			if (structureDoesntMatch) {
-				mesh.Clear();
-				this.sharedMaterials = currentInstructions.GetUpdatedMaterialArray(this.sharedMaterials);
-			}
-
-			// STEP 3: Assign the buffers into the Mesh.
-			smartMesh.Set(this.meshVertices, this.meshUVs, this.meshColors32, workingAttachments, currentInstructions);
-			mesh.bounds = ArraysMeshGenerator.ToBounds(meshBoundsMin, meshBoundsMax);
-
-
-			if (structureDoesntMatch) {
-				// Push new triangles if doesn't match.
-				mesh.subMeshCount = submeshCount;
-				for (int i = 0; i < submeshCount; i++)
-					mesh.SetTriangles(submeshBuffers.Items[i].triangles, i);			
-
-				this.TryAddNormalsTo(mesh, vertexCount);
-			}
-
-			if (addTangents) { 
-				SolveTangents2DEnsureSize(ref this.meshTangents, ref this.tempTanBuffer, vertexCount);
-
-				for (int i = 0, n = submeshCount; i < n; i++) {
-					var submesh = submeshBuffers.Items[i];
-					SolveTangents2DTriangles(this.tempTanBuffer, submesh.triangles, submesh.triangleCount, meshVertices, meshUVs, vertexCount);
-				}
-					
-				SolveTangents2DBuffer(this.meshTangents, this.tempTanBuffer, vertexCount);
-			}
-				
-			return new MeshAndMaterials(smartMesh.mesh, sharedMaterials);
-		}
-
-		#region Types
-		// A SmartMesh is a Mesh (with submeshes) that knows what attachments and instructions were used to generate it.
-		class SmartMesh {
-			public readonly Mesh mesh = SpineMesh.NewMesh();
-			readonly ExposedList<Attachment> attachmentsUsed = new ExposedList<Attachment>();
-			readonly ExposedList<SubmeshInstruction> instructionsUsed = new ExposedList<SubmeshInstruction>();
-
-			public void Set (Vector3[] verts, Vector2[] uvs, Color32[] colors, ExposedList<Attachment> attachments, ExposedList<SubmeshInstruction> instructions) {
-				mesh.vertices = verts;
-				mesh.uv = uvs;
-				mesh.colors32 = colors;
-
-				attachmentsUsed.Clear(false);
-				attachmentsUsed.GrowIfNeeded(attachments.Capacity);
-				attachmentsUsed.Count = attachments.Count;
-				attachments.CopyTo(attachmentsUsed.Items);
-
-				instructionsUsed.Clear(false);
-				instructionsUsed.GrowIfNeeded(instructions.Capacity);
-				instructionsUsed.Count = instructions.Count;
-				instructions.CopyTo(instructionsUsed.Items);
-			}
-
-			public bool StructureDoesntMatch (ExposedList<Attachment> attachments, ExposedList<SubmeshInstruction> instructions) {
-				// Check count inequality.
-				if (attachments.Count != this.attachmentsUsed.Count) return true;
-				if (instructions.Count != this.instructionsUsed.Count) return true;
-
-				// Check each attachment.
-				var attachmentsPassed = attachments.Items;
-				var myAttachments = this.attachmentsUsed.Items;
-				for (int i = 0, n = attachmentsUsed.Count; i < n; i++)
-					if (attachmentsPassed[i] != myAttachments[i]) return true;
-
-				// Check each submesh for equal arrangement.
-				var instructionListItems = instructions.Items;
-				var myInstructions = this.instructionsUsed.Items;
-				for (int i = 0, n = this.instructionsUsed.Count; i < n; i++) {
-					var lhs = instructionListItems[i];
-					var rhs = myInstructions[i];
-					if (
-						lhs.material.GetInstanceID() != rhs.material.GetInstanceID() ||
-						lhs.startSlot != rhs.startSlot ||
-						lhs.endSlot != rhs.endSlot ||
-						lhs.triangleCount != rhs.triangleCount ||
-						lhs.vertexCount != rhs.vertexCount ||
-						lhs.firstVertexIndex != rhs.firstVertexIndex
-					) return true;
-				}
-
-				return false;
-			}
-		}
-		#endregion
-	}
 
+/******************************************************************************
+ * 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;
+
+namespace Spine.Unity.MeshGeneration {
+	public class ArraysSubmeshSetMeshGenerator : ArraysMeshGenerator, ISubmeshSetMeshGenerator {
+		#region Settings
+		public float ZSpacing { get; set; }
+		#endregion
+
+		readonly DoubleBuffered<SmartMesh> doubleBufferedSmartMesh = new DoubleBuffered<SmartMesh>();
+		readonly ExposedList<SubmeshInstruction> currentInstructions = new ExposedList<SubmeshInstruction>();
+		readonly ExposedList<Attachment> attachmentBuffer = new ExposedList<Attachment>();
+		readonly ExposedList<SubmeshTriangleBuffer> submeshBuffers = new ExposedList<SubmeshTriangleBuffer>();
+		Material[] sharedMaterials = new Material[0];
+
+		public MeshAndMaterials GenerateMesh (ExposedList<SubmeshInstruction> instructions, int startSubmesh, int endSubmesh) {
+			// STEP 0: Prepare instructions.
+			var paramItems = instructions.Items;
+			currentInstructions.Clear(false);
+			for (int i = startSubmesh, n = endSubmesh; i < n; i++) {
+				this.currentInstructions.Add(paramItems[i]);
+			}
+			var smartMesh = doubleBufferedSmartMesh.GetNext();
+			var mesh = smartMesh.mesh;
+			int submeshCount = currentInstructions.Count;
+			var currentInstructionsItems = currentInstructions.Items;
+			int vertexCount = 0;
+			for (int i = 0; i < submeshCount; i++) {
+				currentInstructionsItems[i].firstVertexIndex = vertexCount;// Ensure current instructions have correct cached values.
+				vertexCount += currentInstructionsItems[i].vertexCount; // vertexCount will also be used for the rest of this method.
+			}
+
+			// STEP 1: Ensure correct buffer sizes.
+			bool vertBufferResized = ArraysMeshGenerator.EnsureSize(vertexCount, ref this.meshVertices, ref this.meshUVs, ref this.meshColors32); 
+			bool submeshBuffersResized = ArraysMeshGenerator.EnsureTriangleBuffersSize(submeshBuffers, submeshCount, currentInstructionsItems);
+
+			// STEP 2: Update buffers based on Skeleton.
+
+			// Initial values for manual Mesh Bounds calculation
+			Vector3 meshBoundsMin;
+			Vector3 meshBoundsMax;
+			float zSpacing = this.ZSpacing;
+			if (vertexCount <= 0) {
+				meshBoundsMin = new Vector3(0, 0, 0);
+				meshBoundsMax = new Vector3(0, 0, 0);
+			} else {
+				meshBoundsMin.x = int.MaxValue;
+				meshBoundsMin.y = int.MaxValue;
+				meshBoundsMax.x = int.MinValue;
+				meshBoundsMax.y = int.MinValue;
+
+				int endSlot = currentInstructionsItems[submeshCount - 1].endSlot;
+				if (zSpacing > 0f) {
+					meshBoundsMin.z = 0f;
+					meshBoundsMax.z = zSpacing * endSlot;
+				} else {
+					meshBoundsMin.z = zSpacing * endSlot;
+					meshBoundsMax.z = 0f;
+				}
+			}
+				
+			// For each submesh, add vertex data from attachments.
+			var workingAttachments = this.attachmentBuffer;
+			workingAttachments.Clear(false);
+			int vertexIndex = 0; // modified by FillVerts
+			for (int submeshIndex = 0; submeshIndex < submeshCount; submeshIndex++) {
+				var currentInstruction = currentInstructionsItems[submeshIndex];
+				int startSlot = currentInstruction.startSlot;
+				int endSlot = currentInstruction.endSlot;
+				var skeleton = currentInstruction.skeleton;
+				var skeletonDrawOrderItems = skeleton.DrawOrder.Items;
+				for (int i = startSlot; i < endSlot; i++) {
+					var ca = skeletonDrawOrderItems[i].attachment;
+					if (ca != null) workingAttachments.Add(ca); // Includes BoundingBoxes. This is ok.
+				}
+				ArraysMeshGenerator.FillVerts(skeleton, startSlot, endSlot, zSpacing, this.PremultiplyVertexColors, this.meshVertices, this.meshUVs, this.meshColors32, ref vertexIndex, ref this.attachmentVertexBuffer, ref meshBoundsMin, ref meshBoundsMax);
+			}
+
+			bool structureDoesntMatch = vertBufferResized || submeshBuffersResized || smartMesh.StructureDoesntMatch(workingAttachments, currentInstructions);
+			for (int submeshIndex = 0; submeshIndex < submeshCount; submeshIndex++) {
+				var currentInstruction = currentInstructionsItems[submeshIndex];
+				if (structureDoesntMatch) {
+					var currentBuffer = submeshBuffers.Items[submeshIndex];
+					bool isLastSubmesh = (submeshIndex == submeshCount - 1);
+					ArraysMeshGenerator.FillTriangles(ref currentBuffer.triangles, currentInstruction.skeleton, currentInstruction.triangleCount, currentInstruction.firstVertexIndex, currentInstruction.startSlot, currentInstruction.endSlot, isLastSubmesh);
+					currentBuffer.triangleCount = currentInstruction.triangleCount;
+					currentBuffer.firstVertex = currentInstruction.firstVertexIndex;
+				}
+			}
+
+			if (structureDoesntMatch) {
+				mesh.Clear();
+				this.sharedMaterials = currentInstructions.GetUpdatedMaterialArray(this.sharedMaterials);
+			}
+
+			// STEP 3: Assign the buffers into the Mesh.
+			smartMesh.Set(this.meshVertices, this.meshUVs, this.meshColors32, workingAttachments, currentInstructions);
+			mesh.bounds = ArraysMeshGenerator.ToBounds(meshBoundsMin, meshBoundsMax);
+
+
+			if (structureDoesntMatch) {
+				// Push new triangles if doesn't match.
+				mesh.subMeshCount = submeshCount;
+				for (int i = 0; i < submeshCount; i++)
+					mesh.SetTriangles(submeshBuffers.Items[i].triangles, i);			
+
+				this.TryAddNormalsTo(mesh, vertexCount);
+			}
+
+			if (addTangents) { 
+				SolveTangents2DEnsureSize(ref this.meshTangents, ref this.tempTanBuffer, vertexCount);
+
+				for (int i = 0, n = submeshCount; i < n; i++) {
+					var submesh = submeshBuffers.Items[i];
+					SolveTangents2DTriangles(this.tempTanBuffer, submesh.triangles, submesh.triangleCount, meshVertices, meshUVs, vertexCount);
+				}
+					
+				SolveTangents2DBuffer(this.meshTangents, this.tempTanBuffer, vertexCount);
+			}
+				
+			return new MeshAndMaterials(smartMesh.mesh, sharedMaterials);
+		}
+
+		#region Types
+		// A SmartMesh is a Mesh (with submeshes) that knows what attachments and instructions were used to generate it.
+		class SmartMesh {
+			public readonly Mesh mesh = SpineMesh.NewMesh();
+			readonly ExposedList<Attachment> attachmentsUsed = new ExposedList<Attachment>();
+			readonly ExposedList<SubmeshInstruction> instructionsUsed = new ExposedList<SubmeshInstruction>();
+
+			public void Set (Vector3[] verts, Vector2[] uvs, Color32[] colors, ExposedList<Attachment> attachments, ExposedList<SubmeshInstruction> instructions) {
+				mesh.vertices = verts;
+				mesh.uv = uvs;
+				mesh.colors32 = colors;
+
+				attachmentsUsed.Clear(false);
+				attachmentsUsed.GrowIfNeeded(attachments.Capacity);
+				attachmentsUsed.Count = attachments.Count;
+				attachments.CopyTo(attachmentsUsed.Items);
+
+				instructionsUsed.Clear(false);
+				instructionsUsed.GrowIfNeeded(instructions.Capacity);
+				instructionsUsed.Count = instructions.Count;
+				instructions.CopyTo(instructionsUsed.Items);
+			}
+
+			public bool StructureDoesntMatch (ExposedList<Attachment> attachments, ExposedList<SubmeshInstruction> instructions) {
+				// Check count inequality.
+				if (attachments.Count != this.attachmentsUsed.Count) return true;
+				if (instructions.Count != this.instructionsUsed.Count) return true;
+
+				// Check each attachment.
+				var attachmentsPassed = attachments.Items;
+				var myAttachments = this.attachmentsUsed.Items;
+				for (int i = 0, n = attachmentsUsed.Count; i < n; i++)
+					if (attachmentsPassed[i] != myAttachments[i]) return true;
+
+				// Check each submesh for equal arrangement.
+				var instructionListItems = instructions.Items;
+				var myInstructions = this.instructionsUsed.Items;
+				for (int i = 0, n = this.instructionsUsed.Count; i < n; i++) {
+					var lhs = instructionListItems[i];
+					var rhs = myInstructions[i];
+					if (
+						lhs.material.GetInstanceID() != rhs.material.GetInstanceID() ||
+						lhs.startSlot != rhs.startSlot ||
+						lhs.endSlot != rhs.endSlot ||
+						lhs.triangleCount != rhs.triangleCount ||
+						lhs.vertexCount != rhs.vertexCount ||
+						lhs.firstVertexIndex != rhs.firstVertexIndex
+					) return true;
+				}
+
+				return false;
+			}
+		}
+		#endregion
+	}
+
 }

+ 319 - 288
spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSubmeshedMeshGenerator.cs

@@ -1,290 +1,321 @@
-/******************************************************************************
- * 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.
+/******************************************************************************
+ * 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 System.Collections.Generic;
-
-namespace Spine.Unity.MeshGeneration {
-	/// <summary>
-	/// Arrays submeshed mesh generator.
-	/// </summary>
-	public class ArraysSubmeshedMeshGenerator : ArraysMeshGenerator, ISubmeshedMeshGenerator {
-
-		readonly List<Slot> separators = new List<Slot>();
-		public List<Slot> Separators { get { return this.separators; } }
-
-		#region Settings
-		public float ZSpacing { get; set; }
-		#endregion
-
-		readonly DoubleBuffered<SmartMesh> doubleBufferedSmartMesh = new DoubleBuffered<SmartMesh>();
-		readonly SubmeshedMeshInstruction currentInstructions = new SubmeshedMeshInstruction();
-		readonly ExposedList<SubmeshTriangleBuffer> submeshBuffers = new ExposedList<SubmeshTriangleBuffer>();
-		Material[] sharedMaterials = new Material[0];
-
-		public SubmeshedMeshInstruction GenerateInstruction (Skeleton skeleton) {
-			if (skeleton == null) throw new System.ArgumentNullException("skeleton");
-
-			// Count vertices and submesh triangles.
-			int runningVertexCount = 0;
-
-			int submeshTriangleCount = 0;
-			int submeshFirstVertex = 0;
-			int submeshVertexCount = 0;
-			int submeshStartSlotIndex = 0;
-			Material lastMaterial = null;
-
-			var drawOrder = skeleton.drawOrder;
-			var drawOrderItems = drawOrder.Items;
-			int drawOrderCount = drawOrder.Count;
-			int separatorCount = separators.Count;
-
-			var instructionList = this.currentInstructions.submeshInstructions;
-			instructionList.Clear(false);
-
-			currentInstructions.attachmentList.Clear(false);
-
-			for (int i = 0; i < drawOrderCount; i++) {
-				var slot = drawOrderItems[i];
-				var attachment = slot.attachment;
-
-				object rendererObject; // An AtlasRegion in plain Spine-Unity. eventual source of Material object.
-				int attachmentVertexCount, attachmentTriangleCount;
-
-				var regionAttachment = attachment as RegionAttachment;
-				if (regionAttachment != null) {
-					rendererObject = regionAttachment.RendererObject;
-					attachmentVertexCount = 4;
-					attachmentTriangleCount = 6;
-				} else {
-					var meshAttachment = attachment as MeshAttachment;
-					if (meshAttachment != null) {
-						rendererObject = meshAttachment.RendererObject;
-						attachmentVertexCount = meshAttachment.worldVerticesLength >> 1;
-						attachmentTriangleCount = meshAttachment.triangles.Length;
-					} else {
-						continue;
-					}
-				}
-
-				var attachmentMaterial = (Material)((AtlasRegion)rendererObject).page.rendererObject;
-
-				// Populate submesh when material changes. (or when forced to separate by a submeshSeparator)
-				bool separatedBySlot  = ( separatorCount > 0 && separators.Contains(slot) );
-				if (( runningVertexCount > 0 && lastMaterial.GetInstanceID() != attachmentMaterial.GetInstanceID() ) ||	separatedBySlot) {
-
-					instructionList.Add(
-						new SubmeshInstruction {
-							skeleton = skeleton,
-							material = lastMaterial,
-							triangleCount = submeshTriangleCount,
-							vertexCount = submeshVertexCount,
-							startSlot = submeshStartSlotIndex,
-							endSlot = i,
-							firstVertexIndex = submeshFirstVertex,
-							forceSeparate = separatedBySlot
-						}
-					);
-
-					// Prepare for next submesh
-					submeshTriangleCount = 0;
-					submeshVertexCount = 0;
-					submeshFirstVertex = runningVertexCount;
-					submeshStartSlotIndex = i;
-				}
-				lastMaterial = attachmentMaterial;
-
-				submeshTriangleCount += attachmentTriangleCount;
-				submeshVertexCount += attachmentVertexCount;
-				runningVertexCount += attachmentVertexCount;
-
-				currentInstructions.attachmentList.Add(attachment);
-			}
-
-			instructionList.Add(
-				new SubmeshInstruction {
-					skeleton = skeleton,
-					material = lastMaterial,
-					triangleCount = submeshTriangleCount,
-					vertexCount = submeshVertexCount,
-					startSlot = submeshStartSlotIndex,
-					endSlot = drawOrderCount,
-					firstVertexIndex = submeshFirstVertex,
-					forceSeparate = false
-				}
-			);
-
-			currentInstructions.vertexCount = runningVertexCount;
-			return currentInstructions;
-		}
-
-		// ISubmeshedMeshGenerator.GenerateMesh
-		/// <summary>Generates a mesh based on SubmeshedMeshInstructions</summary>
-		public MeshAndMaterials GenerateMesh (SubmeshedMeshInstruction meshInstructions) {
-			var smartMesh = doubleBufferedSmartMesh.GetNext();
-			var mesh = smartMesh.mesh;
-			int submeshCount = meshInstructions.submeshInstructions.Count;
-			var instructionList = meshInstructions.submeshInstructions;
-
-			// STEP 1: Ensure correct buffer sizes.
-			int vertexCount = meshInstructions.vertexCount;
-			bool submeshBuffersResized = ArraysMeshGenerator.EnsureTriangleBuffersSize(submeshBuffers, submeshCount, instructionList.Items);
-			bool vertBufferResized = ArraysMeshGenerator.EnsureSize(vertexCount, ref this.meshVertices, ref this.meshUVs, ref this.meshColors32);
-			Vector3[] vertices = this.meshVertices;
-
-			// STEP 2: Update buffers based on Skeleton.
-			float zSpacing = this.ZSpacing;
-			Vector3 meshBoundsMin;
-			Vector3 meshBoundsMax;
-			int attachmentCount = meshInstructions.attachmentList.Count;
-			if (attachmentCount <= 0) {
-				meshBoundsMin = new Vector3(0, 0, 0);
-				meshBoundsMax = new Vector3(0, 0, 0);
-			} else {
-				meshBoundsMin.x = int.MaxValue;
-				meshBoundsMin.y = int.MaxValue;
-				meshBoundsMax.x = int.MinValue;
-				meshBoundsMax.y = int.MinValue;
-
-				if (zSpacing > 0f) {
-					meshBoundsMin.z = 0f;
-					meshBoundsMax.z = zSpacing * (attachmentCount - 1);
-				} else {
-					meshBoundsMin.z = zSpacing * (attachmentCount - 1);
-					meshBoundsMax.z = 0f;
-				}
-			}
-			bool structureDoesntMatch = vertBufferResized || submeshBuffersResized || smartMesh.StructureDoesntMatch(meshInstructions);
-			// For each submesh, add vertex data from attachments. Also triangles, but only if needed.
-			int vertexIndex = 0; // modified by FillVerts
-			for (int submeshIndex = 0; submeshIndex < submeshCount; submeshIndex++) {
-				var submeshInstruction = instructionList.Items[submeshIndex];
-				int start = submeshInstruction.startSlot;
-				int end = submeshInstruction.endSlot;
-				var skeleton = submeshInstruction.skeleton;
-				ArraysMeshGenerator.FillVerts(skeleton, start, end, zSpacing, this.PremultiplyVertexColors, vertices, this.meshUVs, this.meshColors32, ref vertexIndex, ref this.attachmentVertexBuffer, ref meshBoundsMin, ref meshBoundsMax);
-				if (structureDoesntMatch) {
-					var currentBuffer = submeshBuffers.Items[submeshIndex];
-					bool isLastSubmesh = (submeshIndex == submeshCount - 1);
-					ArraysMeshGenerator.FillTriangles(ref currentBuffer.triangles, skeleton, submeshInstruction.triangleCount, submeshInstruction.firstVertexIndex, start, end, isLastSubmesh);
-					currentBuffer.triangleCount = submeshInstruction.triangleCount;
-					currentBuffer.firstVertex = submeshInstruction.firstVertexIndex;
-				}
-			}
-
-			if (structureDoesntMatch) {
-				mesh.Clear();
-				this.sharedMaterials = meshInstructions.GetUpdatedMaterialArray(this.sharedMaterials);
-			}
-
-			// STEP 3: Assign the buffers into the Mesh.
-			smartMesh.Set(this.meshVertices, this.meshUVs, this.meshColors32, meshInstructions);
-			mesh.bounds = ArraysMeshGenerator.ToBounds(meshBoundsMin, meshBoundsMax);
-
-			if (structureDoesntMatch) {
-				// Push new triangles if doesn't match.
-				mesh.subMeshCount = submeshCount;
-				for (int i = 0; i < submeshCount; i++)
-					mesh.SetTriangles(submeshBuffers.Items[i].triangles, i);			
-
-				TryAddNormalsTo(mesh, vertexCount);
-			}
-
-			if (addTangents) { 
-				SolveTangents2DEnsureSize(ref this.meshTangents, ref this.tempTanBuffer, vertexCount);
-				for (int i = 0, n = submeshCount; i < n; i++) {
-					var submesh = submeshBuffers.Items[i];
-					SolveTangents2DTriangles(this.tempTanBuffer, submesh.triangles, submesh.triangleCount, meshVertices, meshUVs, vertexCount);
-				}
-				SolveTangents2DBuffer(this.meshTangents, this.tempTanBuffer, vertexCount);
-			}
-				
-			return new MeshAndMaterials(smartMesh.mesh, sharedMaterials);
-		}
-
-		#region Types
-		// A SmartMesh is a Mesh (with submeshes) that knows what attachments and instructions were used to generate it.
-		class SmartMesh {
-			public readonly Mesh mesh = SpineMesh.NewMesh();
-			readonly ExposedList<Attachment> attachmentsUsed = new ExposedList<Attachment>();
-			readonly ExposedList<SubmeshInstruction> instructionsUsed = new ExposedList<SubmeshInstruction>();
-
-			public void Set (Vector3[] verts, Vector2[] uvs, Color32[] colors, SubmeshedMeshInstruction instruction) {
-				mesh.vertices = verts;
-				mesh.uv = uvs;
-				mesh.colors32 = colors;
-
-				attachmentsUsed.Clear(false);
-				attachmentsUsed.GrowIfNeeded(instruction.attachmentList.Capacity);
-				attachmentsUsed.Count = instruction.attachmentList.Count;
-				instruction.attachmentList.CopyTo(attachmentsUsed.Items);
-
-				instructionsUsed.Clear(false);
-				instructionsUsed.GrowIfNeeded(instruction.submeshInstructions.Capacity);
-				instructionsUsed.Count = instruction.submeshInstructions.Count;
-				instruction.submeshInstructions.CopyTo(instructionsUsed.Items);
-			}
-
-			public bool StructureDoesntMatch (SubmeshedMeshInstruction instructions) {
-				// Check count inequality.
-				if (instructions.attachmentList.Count != this.attachmentsUsed.Count) return true;
-				if (instructions.submeshInstructions.Count != this.instructionsUsed.Count) return true;
-
-				// Check each attachment.
-				var attachmentsPassed = instructions.attachmentList.Items;
-				var myAttachments = this.attachmentsUsed.Items;
-				for (int i = 0, n = attachmentsUsed.Count; i < n; i++)
-					if (attachmentsPassed[i] != myAttachments[i]) return true;
-
-				// Check each submesh for equal arrangement.
-				var instructionListItems = instructions.submeshInstructions.Items;
-				var myInstructions = this.instructionsUsed.Items;
-				for (int i = 0, n = this.instructionsUsed.Count; i < n; i++) {
-					var lhs = instructionListItems[i];
-					var rhs = myInstructions[i];
-					if (
-						lhs.material.GetInstanceID() != rhs.material.GetInstanceID() ||
-						lhs.startSlot != rhs.startSlot ||
-						lhs.endSlot != rhs.endSlot ||
-						lhs.triangleCount != rhs.triangleCount ||
-						lhs.vertexCount != rhs.vertexCount ||
-						lhs.firstVertexIndex != rhs.firstVertexIndex
-					) return true;
-				}
-
-				//Debug.Log("structure matched");
-				return false;
-			}
-		}
-		#endregion
-	}
-
-}
+/******************************************************************************
+ * 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 System.Collections.Generic;
+
+namespace Spine.Unity.MeshGeneration {
+	/// <summary>
+	/// Arrays submeshed mesh generator.
+	/// </summary>
+	public class ArraysSubmeshedMeshGenerator : ArraysMeshGenerator, ISubmeshedMeshGenerator {
+
+		readonly List<Slot> separators = new List<Slot>();
+		public List<Slot> Separators { get { return this.separators; } }
+
+		#region Settings
+		public float ZSpacing { get; set; }
+		#endregion
+
+		readonly DoubleBuffered<SmartMesh> doubleBufferedSmartMesh = new DoubleBuffered<SmartMesh>();
+		readonly SubmeshedMeshInstruction currentInstructions = new SubmeshedMeshInstruction();
+		readonly ExposedList<SubmeshTriangleBuffer> submeshBuffers = new ExposedList<SubmeshTriangleBuffer>();
+		Material[] sharedMaterials = new Material[0];
+
+		public SubmeshedMeshInstruction GenerateInstruction (Skeleton skeleton) {
+			if (skeleton == null) throw new System.ArgumentNullException("skeleton");
+
+			// Count vertices and submesh triangles.
+			int runningVertexCount = 0;
+
+			int submeshTriangleCount = 0;
+			int submeshFirstVertex = 0;
+			int submeshVertexCount = 0;
+			int submeshStartSlotIndex = 0;
+			Material lastMaterial = null;
+
+			var drawOrder = skeleton.drawOrder;
+			var drawOrderItems = drawOrder.Items;
+			int drawOrderCount = drawOrder.Count;
+			int separatorCount = separators.Count;
+
+			var instructionList = this.currentInstructions.submeshInstructions;
+			instructionList.Clear(false);
+
+			currentInstructions.attachmentList.Clear(false);
+
+			for (int i = 0; i < drawOrderCount; i++) {
+				var slot = drawOrderItems[i];
+				var attachment = slot.attachment;
+
+				object rendererObject; // An AtlasRegion in plain Spine-Unity. eventual source of Material object.
+				int attachmentVertexCount, attachmentTriangleCount;
+
+				var regionAttachment = attachment as RegionAttachment;
+				if (regionAttachment != null) {
+					rendererObject = regionAttachment.RendererObject;
+					attachmentVertexCount = 4;
+					attachmentTriangleCount = 6;
+				} else {
+					var meshAttachment = attachment as MeshAttachment;
+					if (meshAttachment != null) {
+						rendererObject = meshAttachment.RendererObject;
+						attachmentVertexCount = meshAttachment.worldVerticesLength >> 1;
+						attachmentTriangleCount = meshAttachment.triangles.Length;
+					} else {
+						continue;
+					}
+				}
+
+				var attachmentMaterial = (Material)((AtlasRegion)rendererObject).page.rendererObject;
+
+				// Populate submesh when material changes. (or when forced to separate by a submeshSeparator)
+				bool separatedBySlot  = ( separatorCount > 0 && separators.Contains(slot) );
+				if (( runningVertexCount > 0 && lastMaterial.GetInstanceID() != attachmentMaterial.GetInstanceID() ) ||	separatedBySlot) {
+
+					instructionList.Add(
+						new SubmeshInstruction {
+							skeleton = skeleton,
+							material = lastMaterial,
+							triangleCount = submeshTriangleCount,
+							vertexCount = submeshVertexCount,
+							startSlot = submeshStartSlotIndex,
+							endSlot = i,
+							firstVertexIndex = submeshFirstVertex,
+							forceSeparate = separatedBySlot
+						}
+					);
+
+					// Prepare for next submesh
+					submeshTriangleCount = 0;
+					submeshVertexCount = 0;
+					submeshFirstVertex = runningVertexCount;
+					submeshStartSlotIndex = i;
+				}
+				lastMaterial = attachmentMaterial;
+
+				submeshTriangleCount += attachmentTriangleCount;
+				submeshVertexCount += attachmentVertexCount;
+				runningVertexCount += attachmentVertexCount;
+
+				currentInstructions.attachmentList.Add(attachment);
+			}
+
+			instructionList.Add(
+				new SubmeshInstruction {
+					skeleton = skeleton,
+					material = lastMaterial,
+					triangleCount = submeshTriangleCount,
+					vertexCount = submeshVertexCount,
+					startSlot = submeshStartSlotIndex,
+					endSlot = drawOrderCount,
+					firstVertexIndex = submeshFirstVertex,
+					forceSeparate = false
+				}
+			);
+
+			currentInstructions.vertexCount = runningVertexCount;
+			return currentInstructions;
+		}
+
+		// ISubmeshedMeshGenerator.GenerateMesh
+		/// <summary>Generates a mesh based on SubmeshedMeshInstructions</summary>
+		public MeshAndMaterials GenerateMesh (SubmeshedMeshInstruction meshInstructions) {
+			var smartMesh = doubleBufferedSmartMesh.GetNext();
+			var mesh = smartMesh.mesh;
+			int submeshCount = meshInstructions.submeshInstructions.Count;
+			var instructionList = meshInstructions.submeshInstructions;
+
+			// STEP 1: Ensure correct buffer sizes.
+			int vertexCount = meshInstructions.vertexCount;
+			bool submeshBuffersResized = ArraysMeshGenerator.EnsureTriangleBuffersSize(submeshBuffers, submeshCount, instructionList.Items);
+			bool vertBufferResized = ArraysMeshGenerator.EnsureSize(vertexCount, ref this.meshVertices, ref this.meshUVs, ref this.meshColors32);
+			Vector3[] vertices = this.meshVertices;
+
+			// STEP 2: Update buffers based on Skeleton.
+			float zSpacing = this.ZSpacing;
+			Vector3 meshBoundsMin;
+			Vector3 meshBoundsMax;
+			int attachmentCount = meshInstructions.attachmentList.Count;
+			if (attachmentCount <= 0) {
+				meshBoundsMin = new Vector3(0, 0, 0);
+				meshBoundsMax = new Vector3(0, 0, 0);
+			} else {
+				meshBoundsMin.x = int.MaxValue;
+				meshBoundsMin.y = int.MaxValue;
+				meshBoundsMax.x = int.MinValue;
+				meshBoundsMax.y = int.MinValue;
+
+				if (zSpacing > 0f) {
+					meshBoundsMin.z = 0f;
+					meshBoundsMax.z = zSpacing * (attachmentCount - 1);
+				} else {
+					meshBoundsMin.z = zSpacing * (attachmentCount - 1);
+					meshBoundsMax.z = 0f;
+				}
+			}
+			bool structureDoesntMatch = vertBufferResized || submeshBuffersResized || smartMesh.StructureDoesntMatch(meshInstructions);
+			// For each submesh, add vertex data from attachments. Also triangles, but only if needed.
+			int vertexIndex = 0; // modified by FillVerts
+			for (int submeshIndex = 0; submeshIndex < submeshCount; submeshIndex++) {
+				var submeshInstruction = instructionList.Items[submeshIndex];
+				int start = submeshInstruction.startSlot;
+				int end = submeshInstruction.endSlot;
+				var skeleton = submeshInstruction.skeleton;
+				ArraysMeshGenerator.FillVerts(skeleton, start, end, zSpacing, this.PremultiplyVertexColors, vertices, this.meshUVs, this.meshColors32, ref vertexIndex, ref this.attachmentVertexBuffer, ref meshBoundsMin, ref meshBoundsMax);
+				if (structureDoesntMatch) {
+					var currentBuffer = submeshBuffers.Items[submeshIndex];
+					bool isLastSubmesh = (submeshIndex == submeshCount - 1);
+					ArraysMeshGenerator.FillTriangles(ref currentBuffer.triangles, skeleton, submeshInstruction.triangleCount, submeshInstruction.firstVertexIndex, start, end, isLastSubmesh);
+					currentBuffer.triangleCount = submeshInstruction.triangleCount;
+					currentBuffer.firstVertex = submeshInstruction.firstVertexIndex;
+				}
+			}
+
+			if (structureDoesntMatch) {
+				mesh.Clear();
+				this.sharedMaterials = meshInstructions.GetUpdatedMaterialArray(this.sharedMaterials);
+			}
+
+			// STEP 3: Assign the buffers into the Mesh.
+			smartMesh.Set(this.meshVertices, this.meshUVs, this.meshColors32, meshInstructions);
+			mesh.bounds = ArraysMeshGenerator.ToBounds(meshBoundsMin, meshBoundsMax);
+
+			if (structureDoesntMatch) {
+				// Push new triangles if doesn't match.
+				mesh.subMeshCount = submeshCount;
+				for (int i = 0; i < submeshCount; i++)
+					mesh.SetTriangles(submeshBuffers.Items[i].triangles, i);			
+
+				TryAddNormalsTo(mesh, vertexCount);
+			}
+
+			if (addTangents) { 
+				SolveTangents2DEnsureSize(ref this.meshTangents, ref this.tempTanBuffer, vertexCount);
+				for (int i = 0, n = submeshCount; i < n; i++) {
+					var submesh = submeshBuffers.Items[i];
+					SolveTangents2DTriangles(this.tempTanBuffer, submesh.triangles, submesh.triangleCount, meshVertices, meshUVs, vertexCount);
+				}
+				SolveTangents2DBuffer(this.meshTangents, this.tempTanBuffer, vertexCount);
+			}
+				
+			return new MeshAndMaterials(smartMesh.mesh, sharedMaterials);
+		}
+
+		#region Types
+		// A SmartMesh is a Mesh (with submeshes) that knows what attachments and instructions were used to generate it.
+		class SmartMesh {
+			public readonly Mesh mesh = SpineMesh.NewMesh();
+			readonly ExposedList<Attachment> attachmentsUsed = new ExposedList<Attachment>();
+			readonly ExposedList<SubmeshInstruction> instructionsUsed = new ExposedList<SubmeshInstruction>();
+
+			public void Set (Vector3[] verts, Vector2[] uvs, Color32[] colors, SubmeshedMeshInstruction instruction) {
+				mesh.vertices = verts;
+				mesh.uv = uvs;
+				mesh.colors32 = colors;
+
+				attachmentsUsed.Clear(false);
+				attachmentsUsed.GrowIfNeeded(instruction.attachmentList.Capacity);
+				attachmentsUsed.Count = instruction.attachmentList.Count;
+				instruction.attachmentList.CopyTo(attachmentsUsed.Items);
+
+				instructionsUsed.Clear(false);
+				instructionsUsed.GrowIfNeeded(instruction.submeshInstructions.Capacity);
+				instructionsUsed.Count = instruction.submeshInstructions.Count;
+				instruction.submeshInstructions.CopyTo(instructionsUsed.Items);
+			}
+
+			public bool StructureDoesntMatch (SubmeshedMeshInstruction instructions) {
+				// Check count inequality.
+				if (instructions.attachmentList.Count != this.attachmentsUsed.Count) return true;
+				if (instructions.submeshInstructions.Count != this.instructionsUsed.Count) return true;
+
+				// Check each attachment.
+				var attachmentsPassed = instructions.attachmentList.Items;
+				var myAttachments = this.attachmentsUsed.Items;
+				for (int i = 0, n = attachmentsUsed.Count; i < n; i++)
+					if (attachmentsPassed[i] != myAttachments[i]) return true;
+
+				// Check each submesh for equal arrangement.
+				var instructionListItems = instructions.submeshInstructions.Items;
+				var myInstructions = this.instructionsUsed.Items;
+				for (int i = 0, n = this.instructionsUsed.Count; i < n; i++) {
+					var lhs = instructionListItems[i];
+					var rhs = myInstructions[i];
+					if (
+						lhs.material.GetInstanceID() != rhs.material.GetInstanceID() ||
+						lhs.startSlot != rhs.startSlot ||
+						lhs.endSlot != rhs.endSlot ||
+						lhs.triangleCount != rhs.triangleCount ||
+						lhs.vertexCount != rhs.vertexCount ||
+						lhs.firstVertexIndex != rhs.firstVertexIndex
+					) return true;
+				}
+
+				//Debug.Log("structure matched");
+				return false;
+			}
+		}
+		#endregion
+	}
+
+}

+ 44 - 13
spine-unity/Assets/spine-unity/Mesh Generation/DoubleBuffered.cs

@@ -1,15 +1,46 @@
-using UnityEngine;
-using System.Collections;
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
 
-namespace Spine.Unity {
-	public class DoubleBuffered<T> where T : new() {
-		readonly T a = new T();
-		readonly T b = new T();
-		bool usingA;
-
-		public T GetNext () {
-			usingA = !usingA;
-			return usingA ? a : b;
-		}
-	}
+using UnityEngine;
+using System.Collections;
+
+namespace Spine.Unity {
+	public class DoubleBuffered<T> where T : new() {
+		readonly T a = new T();
+		readonly T b = new T();
+		bool usingA;
+
+		public T GetNext () {
+			usingA = !usingA;
+			return usingA ? a : b;
+		}
+	}
 }

+ 44 - 13
spine-unity/Assets/spine-unity/Mesh Generation/DoubleBufferedMesh.cs

@@ -1,15 +1,46 @@
-using UnityEngine;
-using System.Collections;
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
 
-namespace Spine.Unity {
-	public class DoubleBufferedMesh {
-		readonly Mesh mesh1 = SpineMesh.NewMesh();
-		readonly Mesh mesh2 = SpineMesh.NewMesh();
-		bool usingMesh1;
-			
-		public Mesh GetNextMesh () {
-			usingMesh1 = !usingMesh1;
-			return usingMesh1 ? mesh1 : mesh2;
-		}
-	}
+using UnityEngine;
+using System.Collections;
+
+namespace Spine.Unity {
+	public class DoubleBufferedMesh {
+		readonly Mesh mesh1 = SpineMesh.NewMesh();
+		readonly Mesh mesh2 = SpineMesh.NewMesh();
+		bool usingMesh1;
+			
+		public Mesh GetNextMesh () {
+			usingMesh1 = !usingMesh1;
+			return usingMesh1 ? mesh1 : mesh2;
+		}
+	}
 }

+ 46 - 15
spine-unity/Assets/spine-unity/Mesh Generation/ISimpleMeshGenerator.cs

@@ -1,17 +1,48 @@
-namespace Spine.Unity.MeshGeneration { 
-	// Typically, each ISpineMeshGenerator implementation will handle double-buffering meshes, handling any other optimization behavior
-	// and operating on assumptions (eg, only handling one skeleton, not updating triangles all the time).
-	// The Scale property allows generated mesh to match external systems like Canvas referencePixelsPerUnit
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
 
-	public interface ISimpleMeshGenerator {
-		UnityEngine.Mesh GenerateMesh (Spine.Skeleton skeleton);
-		UnityEngine.Mesh LastGeneratedMesh { get; }
-
-		float Scale { set; }
-		float ZSpacing { get; set; }
-		bool PremultiplyVertexColors { get; set; }
-
-		bool AddNormals { get; set; }
-		bool AddTangents { get; set; }
-	}
+namespace Spine.Unity.MeshGeneration { 
+	// Typically, each ISpineMeshGenerator implementation will handle double-buffering meshes, handling any other optimization behavior
+	// and operating on assumptions (eg, only handling one skeleton, not updating triangles all the time).
+	// The Scale property allows generated mesh to match external systems like Canvas referencePixelsPerUnit
+
+	public interface ISimpleMeshGenerator {
+		UnityEngine.Mesh GenerateMesh (Spine.Skeleton skeleton);
+		UnityEngine.Mesh LastGeneratedMesh { get; }
+
+		float Scale { set; }
+		float ZSpacing { get; set; }
+		bool PremultiplyVertexColors { get; set; }
+
+		bool AddNormals { get; set; }
+		bool AddTangents { get; set; }
+	}
 }

+ 123 - 92
spine-unity/Assets/spine-unity/Mesh Generation/ISubmeshedMeshGenerator.cs

@@ -1,92 +1,123 @@
-using UnityEngine;
-using System.Collections.Generic;
-
-namespace Spine.Unity.MeshGeneration {
-	// ISubmeshedMeshGenerator:
-	// How to use:
-	// Step 1: Have a SubmeshedMeshGenerator instance, and a Spine.Skeleton
-	// Step 2: Call GenerateInstruction. Pass it your Skeleton. Keep the return value (a SubmeshedMeshInstruction, you can use it in other classes too).
-	// Step 3: Pass the SubmeshedMeshInstruction into GenerateMesh. You'll get a Mesh and Materials.
-	// Step 4: Put the Mesh in MeshFilter. Put the Materials in MeshRenderer.sharedMaterials.
-	public interface ISubmeshedMeshGenerator {
-		SubmeshedMeshInstruction GenerateInstruction (Skeleton skeleton);
-		MeshAndMaterials GenerateMesh (SubmeshedMeshInstruction wholeMeshInstruction);
-		List<Slot> Separators { get; }
-
-		float ZSpacing { get; set; }
-		bool AddNormals { get; set; }
-		bool AddTangents { get; set; }
-	}
-
-	// ISubmeshSetMeshGenerator
-	// How to use:
-	// Step 1: Get a list of SubmeshInstruction. You can get this from SkeletonRenderer or an ISubmeshedMeshGenerator's returned SubmeshedMeshInstruction.
-	// Step 2: Call AddInstruction one by one, or AddInstructions once.
-	// Step 3: Call GenerateMesh. You'll get a Mesh and Materials.
-	// Step 4: Put the Mesh in MeshFilter. Put the Materials in MeshRenderer.sharedMaterials.
-	public interface ISubmeshSetMeshGenerator {
-		MeshAndMaterials GenerateMesh (ExposedList<SubmeshInstruction> instructions, int startSubmesh, int endSubmesh);
-
-		float ZSpacing { get; set; }
-		bool PremultiplyVertexColors { get; set; }
-		bool AddNormals { get; set; }
-		bool AddTangents { get; set; }
-	}
-
-	/// <summary>Primarily a collection of Submesh Instructions. This constitutes instructions for how to construct a mesh containing submeshes.</summary>
-	public class SubmeshedMeshInstruction {
-		public readonly ExposedList<SubmeshInstruction> submeshInstructions = new ExposedList<SubmeshInstruction>();
-		public readonly ExposedList<Attachment> attachmentList = new ExposedList<Attachment>();
-		public int vertexCount = -1;
-
-		/// <summary>Returns a material array of the SubmeshedMeshInstruction. Fills the passed array if it's the correct size. Creates a new array if it's a different size.</summary>
-		public Material[] GetUpdatedMaterialArray (Material[] materials) {
-			return submeshInstructions.GetUpdatedMaterialArray(materials);
-		}
-	}
-
-	/// <summary>Instructions for how to generate a mesh or submesh out of a range of slots in a given skeleton.</summary>
-	public struct SubmeshInstruction {
-		public Skeleton skeleton;
-		public int startSlot;
-		public int endSlot;
-
-		// Cached values because they are determined in the process of generating instructions,
-		// but could otherwise be pulled from accessing attachments, checking materials and counting tris and verts.
-		public Material material;
-		public int triangleCount;
-		public int vertexCount;
-
-		// Vertex index offset. Used by submesh generation if part of a bigger mesh.
-		public int firstVertexIndex;
-		public bool forceSeparate;
-
-		/// <summary>The number of slots in this SubmeshInstruction's range. Not necessarily the number of attachments.</summary>
-		public int SlotCount { get { return endSlot - startSlot; } }
-	}
-
-	public static class SubmeshInstructionExtensions {
-		/// <summary>Returns a material array of the instructions. Fills the passed array if it's the correct size. Creates a new array if it's a different size.</summary>
-		public static Material[] GetUpdatedMaterialArray (this ExposedList<SubmeshInstruction> instructions, Material[] materials) {
-			int submeshCount = instructions.Count;
-
-			if (submeshCount != materials.Length)
-				materials = new Material[submeshCount];
-
-			for (int i = 0, n = materials.Length; i < n; i++)
-				materials[i] = instructions.Items[i].material;
-
-			return materials;
-		}
-	}
-
-	public struct MeshAndMaterials {
-		public readonly Mesh mesh;
-		public readonly Material[] materials;
-
-		public MeshAndMaterials (Mesh mesh, Material[] materials) {
-			this.mesh = mesh;
-			this.materials = materials;
-		}
-	}
-}
+/******************************************************************************
+ * 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 System.Collections.Generic;
+
+namespace Spine.Unity.MeshGeneration {
+	// ISubmeshedMeshGenerator:
+	// How to use:
+	// Step 1: Have a SubmeshedMeshGenerator instance, and a Spine.Skeleton
+	// Step 2: Call GenerateInstruction. Pass it your Skeleton. Keep the return value (a SubmeshedMeshInstruction, you can use it in other classes too).
+	// Step 3: Pass the SubmeshedMeshInstruction into GenerateMesh. You'll get a Mesh and Materials.
+	// Step 4: Put the Mesh in MeshFilter. Put the Materials in MeshRenderer.sharedMaterials.
+	public interface ISubmeshedMeshGenerator {
+		SubmeshedMeshInstruction GenerateInstruction (Skeleton skeleton);
+		MeshAndMaterials GenerateMesh (SubmeshedMeshInstruction wholeMeshInstruction);
+		List<Slot> Separators { get; }
+
+		float ZSpacing { get; set; }
+		bool AddNormals { get; set; }
+		bool AddTangents { get; set; }
+	}
+
+	// ISubmeshSetMeshGenerator
+	// How to use:
+	// Step 1: Get a list of SubmeshInstruction. You can get this from SkeletonRenderer or an ISubmeshedMeshGenerator's returned SubmeshedMeshInstruction.
+	// Step 2: Call AddInstruction one by one, or AddInstructions once.
+	// Step 3: Call GenerateMesh. You'll get a Mesh and Materials.
+	// Step 4: Put the Mesh in MeshFilter. Put the Materials in MeshRenderer.sharedMaterials.
+	public interface ISubmeshSetMeshGenerator {
+		MeshAndMaterials GenerateMesh (ExposedList<SubmeshInstruction> instructions, int startSubmesh, int endSubmesh);
+
+		float ZSpacing { get; set; }
+		bool PremultiplyVertexColors { get; set; }
+		bool AddNormals { get; set; }
+		bool AddTangents { get; set; }
+	}
+
+	/// <summary>Primarily a collection of Submesh Instructions. This constitutes instructions for how to construct a mesh containing submeshes.</summary>
+	public class SubmeshedMeshInstruction {
+		public readonly ExposedList<SubmeshInstruction> submeshInstructions = new ExposedList<SubmeshInstruction>();
+		public readonly ExposedList<Attachment> attachmentList = new ExposedList<Attachment>();
+		public int vertexCount = -1;
+
+		/// <summary>Returns a material array of the SubmeshedMeshInstruction. Fills the passed array if it's the correct size. Creates a new array if it's a different size.</summary>
+		public Material[] GetUpdatedMaterialArray (Material[] materials) {
+			return submeshInstructions.GetUpdatedMaterialArray(materials);
+		}
+	}
+
+	/// <summary>Instructions for how to generate a mesh or submesh out of a range of slots in a given skeleton.</summary>
+	public struct SubmeshInstruction {
+		public Skeleton skeleton;
+		public int startSlot;
+		public int endSlot;
+
+		// Cached values because they are determined in the process of generating instructions,
+		// but could otherwise be pulled from accessing attachments, checking materials and counting tris and verts.
+		public Material material;
+		public int triangleCount;
+		public int vertexCount;
+
+		// Vertex index offset. Used by submesh generation if part of a bigger mesh.
+		public int firstVertexIndex;
+		public bool forceSeparate;
+
+		/// <summary>The number of slots in this SubmeshInstruction's range. Not necessarily the number of attachments.</summary>
+		public int SlotCount { get { return endSlot - startSlot; } }
+	}
+
+	public static class SubmeshInstructionExtensions {
+		/// <summary>Returns a material array of the instructions. Fills the passed array if it's the correct size. Creates a new array if it's a different size.</summary>
+		public static Material[] GetUpdatedMaterialArray (this ExposedList<SubmeshInstruction> instructions, Material[] materials) {
+			int submeshCount = instructions.Count;
+
+			if (submeshCount != materials.Length)
+				materials = new Material[submeshCount];
+
+			for (int i = 0, n = materials.Length; i < n; i++)
+				materials[i] = instructions.Items[i].material;
+
+			return materials;
+		}
+	}
+
+	public struct MeshAndMaterials {
+		public readonly Mesh mesh;
+		public readonly Material[] materials;
+
+		public MeshAndMaterials (Mesh mesh, Material[] materials) {
+			this.mesh = mesh;
+			this.materials = materials;
+		}
+	}
+}

+ 48 - 17
spine-unity/Assets/spine-unity/Mesh Generation/SpineMesh.cs

@@ -1,19 +1,50 @@
-using UnityEngine;
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
 
-namespace Spine.Unity {
-	public static class SpineMesh {
-#if UNITY_5
-		internal const HideFlags MeshHideflags = HideFlags.DontSaveInBuild | HideFlags.DontSaveInEditor;
-#else
-		internal const HideFlags MeshHideflags = HideFlags.DontSave; 
-#endif
-		/// <summary>Factory method for creating a new mesh for use in Spine components. This can be called in field initializers.</summary>
-		public static Mesh NewMesh () {
-			var m = new Mesh();
-			m.MarkDynamic();
-			m.name = "Skeleton Mesh";
-			m.hideFlags = SpineMesh.MeshHideflags;
-			return m;
-		}
-	}
+using UnityEngine;
+
+namespace Spine.Unity {
+	public static class SpineMesh {
+#if UNITY_5
+		internal const HideFlags MeshHideflags = HideFlags.DontSaveInBuild | HideFlags.DontSaveInEditor;
+#else
+		internal const HideFlags MeshHideflags = HideFlags.DontSave; 
+#endif
+		/// <summary>Factory method for creating a new mesh for use in Spine components. This can be called in field initializers.</summary>
+		public static Mesh NewMesh () {
+			var m = new Mesh();
+			m.MarkDynamic();
+			m.name = "Skeleton Mesh";
+			m.hideFlags = SpineMesh.MeshHideflags;
+			return m;
+		}
+	}
 }

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

@@ -1,135 +1,166 @@
-/*****************************************************************************
- * SkeletonRendererCustomMaterialsInspector created by Lost Polygon
- * Full irrevocable rights and permissions granted to Esoteric Software
-*****************************************************************************/
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using UnityEditor;
-using UnityEngine;
-using Spine.Unity.Modules;
-
-namespace Spine.Unity.Editor {
-
-	// This script is not intended for use with code. See the readme.txt file in SkeletonRendererCustomMaterials folder to learn more.
-	[CustomEditor(typeof(SkeletonRendererCustomMaterials))]
-	public class SkeletonRendererCustomMaterialsInspector : UnityEditor.Editor {
-		List<SkeletonRendererCustomMaterials.AtlasMaterialOverride> componentCustomMaterialOverrides, _customMaterialOverridesPrev;
-		List<SkeletonRendererCustomMaterials.SlotMaterialOverride> componentCustomSlotMaterials, _customSlotMaterialsPrev;
-		SkeletonRendererCustomMaterials component;
-
-		const BindingFlags PrivateInstance = BindingFlags.Instance | BindingFlags.NonPublic;
-		MethodInfo RemoveCustomMaterialOverrides, RemoveCustomSlotMaterials, SetCustomMaterialOverrides, SetCustomSlotMaterials;
-
-		#region SkeletonRenderer context menu
-		[MenuItem("CONTEXT/SkeletonRenderer/Add Basic Serialized Custom Materials")]
-		static void AddSkeletonRendererCustomMaterials (MenuCommand menuCommand) {
-			var skeletonRenderer = (SkeletonRenderer)menuCommand.context;
-			var newComponent = skeletonRenderer.gameObject.AddComponent<SkeletonRendererCustomMaterials>();
-			Undo.RegisterCreatedObjectUndo(newComponent, "Add Basic Serialized Custom Materials");
-		}
-
-		[MenuItem("CONTEXT/SkeletonRenderer/Add Basic Serialized Custom Materials", true)]
-		static bool AddSkeletonRendererCustomMaterials_Validate (MenuCommand menuCommand) {
-			var skeletonRenderer = (SkeletonRenderer)menuCommand.context;
-			return (skeletonRenderer.GetComponent<SkeletonRendererCustomMaterials>() == null);
-		}
-		#endregion
-
-		void OnEnable () {
-			Type cm = typeof(SkeletonRendererCustomMaterials);
-			RemoveCustomMaterialOverrides = cm.GetMethod("RemoveCustomMaterialOverrides", PrivateInstance);
-			RemoveCustomSlotMaterials = cm.GetMethod("RemoveCustomSlotMaterials", PrivateInstance);
-			SetCustomMaterialOverrides = cm.GetMethod("SetCustomMaterialOverrides", PrivateInstance);
-			SetCustomSlotMaterials = cm.GetMethod("SetCustomSlotMaterials", PrivateInstance);
-		}
-
-		public override void OnInspectorGUI () {
-			component = (SkeletonRendererCustomMaterials)target;
-			var skeletonRenderer = component.skeletonRenderer;
-
-			// Draw the default inspector
-			DrawDefaultInspector();
-
-			if (serializedObject.isEditingMultipleObjects)
-				return;
-
-			if (componentCustomMaterialOverrides == null) {
-				Type cm = typeof(SkeletonRendererCustomMaterials);
-				componentCustomMaterialOverrides = cm.GetField("customMaterialOverrides", PrivateInstance).GetValue(component) as List<SkeletonRendererCustomMaterials.AtlasMaterialOverride>;
-				componentCustomSlotMaterials = cm.GetField("customSlotMaterials", PrivateInstance).GetValue(component) as List<SkeletonRendererCustomMaterials.SlotMaterialOverride>;
-				if (componentCustomMaterialOverrides == null) {
-					Debug.Log("Reflection failed.");
-					return;
-				}
-			}
-
-			// Fill with current values at start
-			if (_customMaterialOverridesPrev == null || _customSlotMaterialsPrev == null) {
-				_customMaterialOverridesPrev = CopyList(componentCustomMaterialOverrides);
-				_customSlotMaterialsPrev = CopyList(componentCustomSlotMaterials);
-			}
-
-			// Compare new values with saved. If change is detected: 
-			// store new values, restore old values, remove overrides, restore new values, restore overrides.
-
-			// 1. Store new values
-			var customMaterialOverridesNew = CopyList(componentCustomMaterialOverrides);
-			var customSlotMaterialsNew = CopyList(componentCustomSlotMaterials);
-			
-			// Detect changes
-			if (!_customMaterialOverridesPrev.SequenceEqual(customMaterialOverridesNew) ||
-				!_customSlotMaterialsPrev.SequenceEqual(customSlotMaterialsNew)) {
-				// 2. Restore old values
-				componentCustomMaterialOverrides.Clear();
-				componentCustomSlotMaterials.Clear();
-				componentCustomMaterialOverrides.AddRange(_customMaterialOverridesPrev);
-				componentCustomSlotMaterials.AddRange(_customSlotMaterialsPrev);
-
-				// 3. Remove overrides
-				RemoveCustomMaterials();
-
-				// 4. Restore new values
-				componentCustomMaterialOverrides.Clear();
-				componentCustomSlotMaterials.Clear();
-				componentCustomMaterialOverrides.AddRange(customMaterialOverridesNew);
-				componentCustomSlotMaterials.AddRange(customSlotMaterialsNew);
-
-				// 5. Restore overrides
-				SetCustomMaterials();
-
-				if (skeletonRenderer != null)
-					skeletonRenderer.LateUpdate();
-			}
-
-			_customMaterialOverridesPrev = CopyList(componentCustomMaterialOverrides);
-			_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 (skeletonRenderer != null) {
-					skeletonRenderer.CustomMaterialOverride.Clear();
-					skeletonRenderer.CustomSlotMaterials.Clear();
-					RemoveCustomMaterials();
-					SetCustomMaterials();
-					skeletonRenderer.LateUpdate();
-				}
-			}
-		}
-
-		void RemoveCustomMaterials () {
-			RemoveCustomMaterialOverrides.Invoke(component, null);
-			RemoveCustomSlotMaterials.Invoke(component, null);
-		}
-
-		void SetCustomMaterials () {
-			SetCustomMaterialOverrides.Invoke(component, null);
-			SetCustomSlotMaterials.Invoke(component, null);
-		}
-
-		static List<T> CopyList<T> (List<T> list) {
-			return list.GetRange(0, list.Count);
-		} 
-	}
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * SkeletonRendererCustomMaterialsInspector created by Lost Polygon
+ * Full irrevocable rights and permissions granted to Esoteric Software
+*****************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using UnityEditor;
+using UnityEngine;
+using Spine.Unity.Modules;
+
+namespace Spine.Unity.Editor {
+
+	// This script is not intended for use with code. See the readme.txt file in SkeletonRendererCustomMaterials folder to learn more.
+	[CustomEditor(typeof(SkeletonRendererCustomMaterials))]
+	public class SkeletonRendererCustomMaterialsInspector : UnityEditor.Editor {
+		List<SkeletonRendererCustomMaterials.AtlasMaterialOverride> componentCustomMaterialOverrides, _customMaterialOverridesPrev;
+		List<SkeletonRendererCustomMaterials.SlotMaterialOverride> componentCustomSlotMaterials, _customSlotMaterialsPrev;
+		SkeletonRendererCustomMaterials component;
+
+		const BindingFlags PrivateInstance = BindingFlags.Instance | BindingFlags.NonPublic;
+		MethodInfo RemoveCustomMaterialOverrides, RemoveCustomSlotMaterials, SetCustomMaterialOverrides, SetCustomSlotMaterials;
+
+		#region SkeletonRenderer context menu
+		[MenuItem("CONTEXT/SkeletonRenderer/Add Basic Serialized Custom Materials")]
+		static void AddSkeletonRendererCustomMaterials (MenuCommand menuCommand) {
+			var skeletonRenderer = (SkeletonRenderer)menuCommand.context;
+			var newComponent = skeletonRenderer.gameObject.AddComponent<SkeletonRendererCustomMaterials>();
+			Undo.RegisterCreatedObjectUndo(newComponent, "Add Basic Serialized Custom Materials");
+		}
+
+		[MenuItem("CONTEXT/SkeletonRenderer/Add Basic Serialized Custom Materials", true)]
+		static bool AddSkeletonRendererCustomMaterials_Validate (MenuCommand menuCommand) {
+			var skeletonRenderer = (SkeletonRenderer)menuCommand.context;
+			return (skeletonRenderer.GetComponent<SkeletonRendererCustomMaterials>() == null);
+		}
+		#endregion
+
+		void OnEnable () {
+			Type cm = typeof(SkeletonRendererCustomMaterials);
+			RemoveCustomMaterialOverrides = cm.GetMethod("RemoveCustomMaterialOverrides", PrivateInstance);
+			RemoveCustomSlotMaterials = cm.GetMethod("RemoveCustomSlotMaterials", PrivateInstance);
+			SetCustomMaterialOverrides = cm.GetMethod("SetCustomMaterialOverrides", PrivateInstance);
+			SetCustomSlotMaterials = cm.GetMethod("SetCustomSlotMaterials", PrivateInstance);
+		}
+
+		public override void OnInspectorGUI () {
+			component = (SkeletonRendererCustomMaterials)target;
+			var skeletonRenderer = component.skeletonRenderer;
+
+			// Draw the default inspector
+			DrawDefaultInspector();
+
+			if (serializedObject.isEditingMultipleObjects)
+				return;
+
+			if (componentCustomMaterialOverrides == null) {
+				Type cm = typeof(SkeletonRendererCustomMaterials);
+				componentCustomMaterialOverrides = cm.GetField("customMaterialOverrides", PrivateInstance).GetValue(component) as List<SkeletonRendererCustomMaterials.AtlasMaterialOverride>;
+				componentCustomSlotMaterials = cm.GetField("customSlotMaterials", PrivateInstance).GetValue(component) as List<SkeletonRendererCustomMaterials.SlotMaterialOverride>;
+				if (componentCustomMaterialOverrides == null) {
+					Debug.Log("Reflection failed.");
+					return;
+				}
+			}
+
+			// Fill with current values at start
+			if (_customMaterialOverridesPrev == null || _customSlotMaterialsPrev == null) {
+				_customMaterialOverridesPrev = CopyList(componentCustomMaterialOverrides);
+				_customSlotMaterialsPrev = CopyList(componentCustomSlotMaterials);
+			}
+
+			// Compare new values with saved. If change is detected: 
+			// store new values, restore old values, remove overrides, restore new values, restore overrides.
+
+			// 1. Store new values
+			var customMaterialOverridesNew = CopyList(componentCustomMaterialOverrides);
+			var customSlotMaterialsNew = CopyList(componentCustomSlotMaterials);
+			
+			// Detect changes
+			if (!_customMaterialOverridesPrev.SequenceEqual(customMaterialOverridesNew) ||
+				!_customSlotMaterialsPrev.SequenceEqual(customSlotMaterialsNew)) {
+				// 2. Restore old values
+				componentCustomMaterialOverrides.Clear();
+				componentCustomSlotMaterials.Clear();
+				componentCustomMaterialOverrides.AddRange(_customMaterialOverridesPrev);
+				componentCustomSlotMaterials.AddRange(_customSlotMaterialsPrev);
+
+				// 3. Remove overrides
+				RemoveCustomMaterials();
+
+				// 4. Restore new values
+				componentCustomMaterialOverrides.Clear();
+				componentCustomSlotMaterials.Clear();
+				componentCustomMaterialOverrides.AddRange(customMaterialOverridesNew);
+				componentCustomSlotMaterials.AddRange(customSlotMaterialsNew);
+
+				// 5. Restore overrides
+				SetCustomMaterials();
+
+				if (skeletonRenderer != null)
+					skeletonRenderer.LateUpdate();
+			}
+
+			_customMaterialOverridesPrev = CopyList(componentCustomMaterialOverrides);
+			_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 (skeletonRenderer != null) {
+					skeletonRenderer.CustomMaterialOverride.Clear();
+					skeletonRenderer.CustomSlotMaterials.Clear();
+					RemoveCustomMaterials();
+					SetCustomMaterials();
+					skeletonRenderer.LateUpdate();
+				}
+			}
+		}
+
+		void RemoveCustomMaterials () {
+			RemoveCustomMaterialOverrides.Invoke(component, null);
+			RemoveCustomSlotMaterials.Invoke(component, null);
+		}
+
+		void SetCustomMaterials () {
+			SetCustomMaterialOverrides.Invoke(component, null);
+			SetCustomSlotMaterials.Invoke(component, null);
+		}
+
+		static List<T> CopyList<T> (List<T> list) {
+			return list.GetRange(0, list.Count);
+		} 
+	}
+}

+ 201 - 170
spine-unity/Assets/spine-unity/Modules/CustomMaterials/SkeletonRendererCustomMaterials.cs

@@ -1,170 +1,201 @@
-/*****************************************************************************
- * SkeletonRendererCustomMaterials created by Lost Polygon
- * Full irrevocable rights and permissions granted to Esoteric Software
-*****************************************************************************/
-
-using System;
-using System.Collections.Generic;
-using UnityEngine;
-
-namespace Spine.Unity.Modules {
-	[ExecuteInEditMode]
-	public class SkeletonRendererCustomMaterials : MonoBehaviour {
-
-		#region Inspector
-		public SkeletonRenderer skeletonRenderer;
-		[SerializeField] List<SlotMaterialOverride> customSlotMaterials = new List<SlotMaterialOverride>();
-		[SerializeField] List<AtlasMaterialOverride> customMaterialOverrides = new List<AtlasMaterialOverride>();
-
-		#if UNITY_EDITOR
-		void Reset () {
-			skeletonRenderer = GetComponent<SkeletonRenderer>();
-
-			// Populate atlas list 
-			if (skeletonRenderer != null && skeletonRenderer.skeletonDataAsset != null) {
-				AtlasAsset[] atlasAssets = skeletonRenderer.skeletonDataAsset.atlasAssets;
-
-				var initialAtlasMaterialOverrides = new List<AtlasMaterialOverride>();
-				foreach (AtlasAsset atlasAsset in atlasAssets) {
-					foreach (Material atlasMaterial in atlasAsset.materials) {
-						var atlasMaterialOverride = new AtlasMaterialOverride();
-						atlasMaterialOverride.overrideDisabled = true;
-						atlasMaterialOverride.originalMaterial = atlasMaterial;
-
-						initialAtlasMaterialOverrides.Add(atlasMaterialOverride);
-					}
-				}
-
-				customMaterialOverrides = initialAtlasMaterialOverrides;
-			}
-		}
-		#endif
-		#endregion
-
-		void SetCustomSlotMaterials () {
-			if (skeletonRenderer == null) {
-				Debug.LogError("skeletonRenderer == null");
-				return;
-			}
-
-			for (int i = 0; i < customSlotMaterials.Count; i++) {
-				SlotMaterialOverride slotMaterialOverride = customSlotMaterials[i];
-				if (slotMaterialOverride.overrideDisabled || string.IsNullOrEmpty(slotMaterialOverride.slotName))
-					continue;
-
-				Slot slotObject = skeletonRenderer.skeleton.FindSlot(slotMaterialOverride.slotName);
-				skeletonRenderer.CustomSlotMaterials[slotObject] = slotMaterialOverride.material;
-			}
-		}
-
-		void RemoveCustomSlotMaterials () {
-			if (skeletonRenderer == null) {
-				Debug.LogError("skeletonRenderer == null");
-				return;
-			}
-
-			for (int i = 0; i < customSlotMaterials.Count; i++) {
-				SlotMaterialOverride slotMaterialOverride = customSlotMaterials[i];
-				if (string.IsNullOrEmpty(slotMaterialOverride.slotName))
-					continue;
-
-				Slot slotObject = skeletonRenderer.skeleton.FindSlot(slotMaterialOverride.slotName);
-
-				Material currentMaterial;
-				if (!skeletonRenderer.CustomSlotMaterials.TryGetValue(slotObject, out currentMaterial))
-					continue;
-
-				// Do not revert the material if it was changed by something else
-				if (currentMaterial != slotMaterialOverride.material)
-					continue;
-
-				skeletonRenderer.CustomSlotMaterials.Remove(slotObject);
-			}
-		}
-
-		void SetCustomMaterialOverrides () {
-			if (skeletonRenderer == null) {
-				Debug.LogError("skeletonRenderer == null");
-				return;
-			}
-
-			for (int i = 0; i < customMaterialOverrides.Count; i++) {
-				AtlasMaterialOverride atlasMaterialOverride = customMaterialOverrides[i];
-				if (atlasMaterialOverride.overrideDisabled)
-					continue;
-
-				skeletonRenderer.CustomMaterialOverride[atlasMaterialOverride.originalMaterial] = atlasMaterialOverride.replacementMaterial;
-			}
-		}
-
-		void RemoveCustomMaterialOverrides () {
-			if (skeletonRenderer == null) {
-				Debug.LogError("skeletonRenderer == null");
-				return;
-			}
-
-			for (int i = 0; i < customMaterialOverrides.Count; i++) {
-				AtlasMaterialOverride atlasMaterialOverride = customMaterialOverrides[i];
-				Material currentMaterial;
-				if (!skeletonRenderer.CustomMaterialOverride.TryGetValue(atlasMaterialOverride.originalMaterial, out currentMaterial))
-					continue;
-
-				// Do not revert the material if it was changed by something else
-				if (currentMaterial != atlasMaterialOverride.replacementMaterial)
-					continue;
-
-				skeletonRenderer.CustomMaterialOverride.Remove(atlasMaterialOverride.originalMaterial);
-			}
-		}
-			
-		// OnEnable applies the overrides at runtime, and when the editor loads.
-		void OnEnable () {
-			if (skeletonRenderer == null)
-				skeletonRenderer = GetComponent<SkeletonRenderer>();
-
-			if (skeletonRenderer == null) {
-				Debug.LogError("skeletonRenderer == null");
-				return;
-			}
-
-			skeletonRenderer.Initialize(false);
-			SetCustomMaterialOverrides();
-			SetCustomSlotMaterials();
-		}
-
-		// OnDisable removes the overrides at runtime, and in the editor when the component is disabled or destroyed.
-		void OnDisable () {
-			if (skeletonRenderer == null) {
-				Debug.LogError("skeletonRenderer == null");
-				return;
-			}
-
-			RemoveCustomMaterialOverrides();
-			RemoveCustomSlotMaterials();
-		}
-
-		[Serializable]
-		public struct SlotMaterialOverride : IEquatable<SlotMaterialOverride> {
-			public bool overrideDisabled;
-
-			[SpineSlot]
-			public string slotName;
-			public Material material;
-
-			public bool Equals (SlotMaterialOverride other) {
-				return overrideDisabled == other.overrideDisabled && slotName == other.slotName && material == other.material;
-			}
-		}
-
-		[Serializable]
-		public struct AtlasMaterialOverride : IEquatable<AtlasMaterialOverride> {
-			public bool overrideDisabled;
-			public Material originalMaterial;
-			public Material replacementMaterial;
-
-			public bool Equals (AtlasMaterialOverride other) {
-				return overrideDisabled == other.overrideDisabled && originalMaterial == other.originalMaterial && replacementMaterial == other.replacementMaterial;
-			}
-		}
-	}
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * SkeletonRendererCustomMaterials created by Lost Polygon
+ * Full irrevocable rights and permissions granted to Esoteric Software
+*****************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace Spine.Unity.Modules {
+	[ExecuteInEditMode]
+	public class SkeletonRendererCustomMaterials : MonoBehaviour {
+
+		#region Inspector
+		public SkeletonRenderer skeletonRenderer;
+		[SerializeField] List<SlotMaterialOverride> customSlotMaterials = new List<SlotMaterialOverride>();
+		[SerializeField] List<AtlasMaterialOverride> customMaterialOverrides = new List<AtlasMaterialOverride>();
+
+		#if UNITY_EDITOR
+		void Reset () {
+			skeletonRenderer = GetComponent<SkeletonRenderer>();
+
+			// Populate atlas list 
+			if (skeletonRenderer != null && skeletonRenderer.skeletonDataAsset != null) {
+				AtlasAsset[] atlasAssets = skeletonRenderer.skeletonDataAsset.atlasAssets;
+
+				var initialAtlasMaterialOverrides = new List<AtlasMaterialOverride>();
+				foreach (AtlasAsset atlasAsset in atlasAssets) {
+					foreach (Material atlasMaterial in atlasAsset.materials) {
+						var atlasMaterialOverride = new AtlasMaterialOverride();
+						atlasMaterialOverride.overrideDisabled = true;
+						atlasMaterialOverride.originalMaterial = atlasMaterial;
+
+						initialAtlasMaterialOverrides.Add(atlasMaterialOverride);
+					}
+				}
+
+				customMaterialOverrides = initialAtlasMaterialOverrides;
+			}
+		}
+		#endif
+		#endregion
+
+		void SetCustomSlotMaterials () {
+			if (skeletonRenderer == null) {
+				Debug.LogError("skeletonRenderer == null");
+				return;
+			}
+
+			for (int i = 0; i < customSlotMaterials.Count; i++) {
+				SlotMaterialOverride slotMaterialOverride = customSlotMaterials[i];
+				if (slotMaterialOverride.overrideDisabled || string.IsNullOrEmpty(slotMaterialOverride.slotName))
+					continue;
+
+				Slot slotObject = skeletonRenderer.skeleton.FindSlot(slotMaterialOverride.slotName);
+				skeletonRenderer.CustomSlotMaterials[slotObject] = slotMaterialOverride.material;
+			}
+		}
+
+		void RemoveCustomSlotMaterials () {
+			if (skeletonRenderer == null) {
+				Debug.LogError("skeletonRenderer == null");
+				return;
+			}
+
+			for (int i = 0; i < customSlotMaterials.Count; i++) {
+				SlotMaterialOverride slotMaterialOverride = customSlotMaterials[i];
+				if (string.IsNullOrEmpty(slotMaterialOverride.slotName))
+					continue;
+
+				Slot slotObject = skeletonRenderer.skeleton.FindSlot(slotMaterialOverride.slotName);
+
+				Material currentMaterial;
+				if (!skeletonRenderer.CustomSlotMaterials.TryGetValue(slotObject, out currentMaterial))
+					continue;
+
+				// Do not revert the material if it was changed by something else
+				if (currentMaterial != slotMaterialOverride.material)
+					continue;
+
+				skeletonRenderer.CustomSlotMaterials.Remove(slotObject);
+			}
+		}
+
+		void SetCustomMaterialOverrides () {
+			if (skeletonRenderer == null) {
+				Debug.LogError("skeletonRenderer == null");
+				return;
+			}
+
+			for (int i = 0; i < customMaterialOverrides.Count; i++) {
+				AtlasMaterialOverride atlasMaterialOverride = customMaterialOverrides[i];
+				if (atlasMaterialOverride.overrideDisabled)
+					continue;
+
+				skeletonRenderer.CustomMaterialOverride[atlasMaterialOverride.originalMaterial] = atlasMaterialOverride.replacementMaterial;
+			}
+		}
+
+		void RemoveCustomMaterialOverrides () {
+			if (skeletonRenderer == null) {
+				Debug.LogError("skeletonRenderer == null");
+				return;
+			}
+
+			for (int i = 0; i < customMaterialOverrides.Count; i++) {
+				AtlasMaterialOverride atlasMaterialOverride = customMaterialOverrides[i];
+				Material currentMaterial;
+				if (!skeletonRenderer.CustomMaterialOverride.TryGetValue(atlasMaterialOverride.originalMaterial, out currentMaterial))
+					continue;
+
+				// Do not revert the material if it was changed by something else
+				if (currentMaterial != atlasMaterialOverride.replacementMaterial)
+					continue;
+
+				skeletonRenderer.CustomMaterialOverride.Remove(atlasMaterialOverride.originalMaterial);
+			}
+		}
+			
+		// OnEnable applies the overrides at runtime, and when the editor loads.
+		void OnEnable () {
+			if (skeletonRenderer == null)
+				skeletonRenderer = GetComponent<SkeletonRenderer>();
+
+			if (skeletonRenderer == null) {
+				Debug.LogError("skeletonRenderer == null");
+				return;
+			}
+
+			skeletonRenderer.Initialize(false);
+			SetCustomMaterialOverrides();
+			SetCustomSlotMaterials();
+		}
+
+		// OnDisable removes the overrides at runtime, and in the editor when the component is disabled or destroyed.
+		void OnDisable () {
+			if (skeletonRenderer == null) {
+				Debug.LogError("skeletonRenderer == null");
+				return;
+			}
+
+			RemoveCustomMaterialOverrides();
+			RemoveCustomSlotMaterials();
+		}
+
+		[Serializable]
+		public struct SlotMaterialOverride : IEquatable<SlotMaterialOverride> {
+			public bool overrideDisabled;
+
+			[SpineSlot]
+			public string slotName;
+			public Material material;
+
+			public bool Equals (SlotMaterialOverride other) {
+				return overrideDisabled == other.overrideDisabled && slotName == other.slotName && material == other.material;
+			}
+		}
+
+		[Serializable]
+		public struct AtlasMaterialOverride : IEquatable<AtlasMaterialOverride> {
+			public bool overrideDisabled;
+			public Material originalMaterial;
+			public Material replacementMaterial;
+
+			public bool Equals (AtlasMaterialOverride other) {
+				return overrideDisabled == other.overrideDisabled && originalMaterial == other.originalMaterial && replacementMaterial == other.replacementMaterial;
+			}
+		}
+	}
+}

+ 186 - 155
spine-unity/Assets/spine-unity/Modules/Ghost/SkeletonGhost.cs

@@ -1,156 +1,187 @@
-/*****************************************************************************
- * SkeletonGhost created by Mitch Thompson
- * Full irrevocable rights and permissions granted to Esoteric Software
-*****************************************************************************/
-
-using UnityEngine;
-using System.Collections.Generic;
-
-namespace Spine.Unity.Modules {
-	
-	[RequireComponent(typeof(SkeletonRenderer))]
-	public class SkeletonGhost : MonoBehaviour {
-		public bool ghostingEnabled = true;
-		public float spawnRate = 0.05f;
-		public Color32 color = new Color32(0xFF, 0xFF, 0xFF, 0x00); // default for additive.
-		[Tooltip("Remember to set color alpha to 0 if Additive is true")]
-		public bool additive = true;
-		public int maximumGhosts = 10;
-		public float fadeSpeed = 10;
-		public Shader ghostShader;
-		[Tooltip("0 is Color and Alpha, 1 is Alpha only.")]
-		[Range(0, 1)]
-		public float textureFade = 1;
-
-		[Header("Sorting")]
-		public bool sortWithDistanceOnly;
-		public float zOffset = 0f;
-
-		float nextSpawnTime;
-		SkeletonGhostRenderer[] pool;
-		int poolIndex = 0;
-		SkeletonRenderer skeletonRenderer;
-		MeshRenderer meshRenderer;
-		MeshFilter meshFilter;
-
-
-		Dictionary<Material, Material> materialTable = new Dictionary<Material, Material>();
-
-		void Start () {
-			if (ghostShader == null)
-				ghostShader = Shader.Find("Spine/Special/SkeletonGhost");
-
-			skeletonRenderer = GetComponent<SkeletonRenderer>();
-			meshFilter = GetComponent<MeshFilter>();
-			meshRenderer = GetComponent<MeshRenderer>();
-			nextSpawnTime = Time.time + spawnRate;
-			pool = new SkeletonGhostRenderer[maximumGhosts];
-			for (int i = 0; i < maximumGhosts; i++) {
-				GameObject go = new GameObject(gameObject.name + " Ghost", typeof(SkeletonGhostRenderer));
-				pool[i] = go.GetComponent<SkeletonGhostRenderer>();
-				go.SetActive(false);
-				go.hideFlags = HideFlags.HideInHierarchy;
-			}
-
-			if (skeletonRenderer is SkeletonAnimation)
-				((SkeletonAnimation)skeletonRenderer).state.Event += OnEvent;
-
-		}
-
-		//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) {
-			if (e.Data.Name == "Ghosting") {
-				ghostingEnabled = e.Int > 0;
-				if (e.Float > 0)
-					spawnRate = e.Float;
-				if (e.String != null) {
-					this.color = HexToColor(e.String);
-				}
-			}
-		}
-
-		//SkeletonAnimator
-		//SkeletonAnimator or Mecanim based animations only support toggling ghostingEnabled.  Be sure not to set anything other than the Int param in Spine or String will take priority.
-		void Ghosting (float val) {
-			ghostingEnabled = val > 0;
-		}
-
-		void Update () {
-			if (!ghostingEnabled)
-				return;
-
-			if (Time.time >= nextSpawnTime) {
-				GameObject go = pool[poolIndex].gameObject;
-
-				Material[] materials = meshRenderer.sharedMaterials;
-				for (int i = 0; i < materials.Length; i++) {
-					var originalMat = materials[i];
-					Material ghostMat;
-					if (!materialTable.ContainsKey(originalMat)) {
-						ghostMat = new Material(originalMat);
-						ghostMat.shader = ghostShader;
-						ghostMat.color = Color.white;
-						if (ghostMat.HasProperty("_TextureFade"))
-							ghostMat.SetFloat("_TextureFade", textureFade);
-						materialTable.Add(originalMat, ghostMat);
-					} else {
-						ghostMat = materialTable[originalMat];
-					}
-
-					materials[i] = ghostMat;
-				}
-
-				var goTransform = go.transform;
-				goTransform.parent = transform;
-
-				pool[poolIndex].Initialize(meshFilter.sharedMesh, materials, color, additive, fadeSpeed, meshRenderer.sortingLayerID, (sortWithDistanceOnly) ? meshRenderer.sortingOrder : meshRenderer.sortingOrder - 1);
-
-				goTransform.localPosition = new Vector3(0f, 0f, zOffset);
-				goTransform.localRotation = Quaternion.identity;
-				goTransform.localScale = Vector3.one;
-
-				goTransform.parent = null;
-
-				poolIndex++;
-
-				if (poolIndex == pool.Length)
-					poolIndex = 0;
-
-				nextSpawnTime = Time.time + spawnRate;
-			}
-		}
-
-		void OnDestroy () {
-			for (int i = 0; i < maximumGhosts; i++) {
-				if (pool[i] != null)
-					pool[i].Cleanup();
-			}
-
-			foreach (var mat in materialTable.Values)
-				Destroy(mat);
-		}
-
-
-		//based on UnifyWiki  http://wiki.unity3d.com/index.php?title=HexConverter
-		static Color32 HexToColor (string hex) {
-			if (hex.Length < 6)
-				return Color.magenta;
-
-			hex = hex.Replace("#", "");
-			byte r = byte.Parse(hex.Substring(0, 2), System.Globalization.NumberStyles.HexNumber);
-			byte g = byte.Parse(hex.Substring(2, 2), System.Globalization.NumberStyles.HexNumber);
-			byte b = byte.Parse(hex.Substring(4, 2), System.Globalization.NumberStyles.HexNumber);
-			byte a = 0xFF;
-			if (hex.Length == 8)
-				a = byte.Parse(hex.Substring(6, 2), System.Globalization.NumberStyles.HexNumber);
-
-			return new Color32(r, g, b, a);
-		}
-	}
-
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * SkeletonGhost created by Mitch Thompson
+ * Full irrevocable rights and permissions granted to Esoteric Software
+*****************************************************************************/
+
+using UnityEngine;
+using System.Collections.Generic;
+
+namespace Spine.Unity.Modules {
+	
+	[RequireComponent(typeof(SkeletonRenderer))]
+	public class SkeletonGhost : MonoBehaviour {
+		public bool ghostingEnabled = true;
+		public float spawnRate = 0.05f;
+		public Color32 color = new Color32(0xFF, 0xFF, 0xFF, 0x00); // default for additive.
+		[Tooltip("Remember to set color alpha to 0 if Additive is true")]
+		public bool additive = true;
+		public int maximumGhosts = 10;
+		public float fadeSpeed = 10;
+		public Shader ghostShader;
+		[Tooltip("0 is Color and Alpha, 1 is Alpha only.")]
+		[Range(0, 1)]
+		public float textureFade = 1;
+
+		[Header("Sorting")]
+		public bool sortWithDistanceOnly;
+		public float zOffset = 0f;
+
+		float nextSpawnTime;
+		SkeletonGhostRenderer[] pool;
+		int poolIndex = 0;
+		SkeletonRenderer skeletonRenderer;
+		MeshRenderer meshRenderer;
+		MeshFilter meshFilter;
+
+
+		Dictionary<Material, Material> materialTable = new Dictionary<Material, Material>();
+
+		void Start () {
+			if (ghostShader == null)
+				ghostShader = Shader.Find("Spine/Special/SkeletonGhost");
+
+			skeletonRenderer = GetComponent<SkeletonRenderer>();
+			meshFilter = GetComponent<MeshFilter>();
+			meshRenderer = GetComponent<MeshRenderer>();
+			nextSpawnTime = Time.time + spawnRate;
+			pool = new SkeletonGhostRenderer[maximumGhosts];
+			for (int i = 0; i < maximumGhosts; i++) {
+				GameObject go = new GameObject(gameObject.name + " Ghost", typeof(SkeletonGhostRenderer));
+				pool[i] = go.GetComponent<SkeletonGhostRenderer>();
+				go.SetActive(false);
+				go.hideFlags = HideFlags.HideInHierarchy;
+			}
+
+			if (skeletonRenderer is SkeletonAnimation)
+				((SkeletonAnimation)skeletonRenderer).state.Event += OnEvent;
+
+		}
+
+		//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) {
+			if (e.Data.Name == "Ghosting") {
+				ghostingEnabled = e.Int > 0;
+				if (e.Float > 0)
+					spawnRate = e.Float;
+				if (e.String != null) {
+					this.color = HexToColor(e.String);
+				}
+			}
+		}
+
+		//SkeletonAnimator
+		//SkeletonAnimator or Mecanim based animations only support toggling ghostingEnabled.  Be sure not to set anything other than the Int param in Spine or String will take priority.
+		void Ghosting (float val) {
+			ghostingEnabled = val > 0;
+		}
+
+		void Update () {
+			if (!ghostingEnabled)
+				return;
+
+			if (Time.time >= nextSpawnTime) {
+				GameObject go = pool[poolIndex].gameObject;
+
+				Material[] materials = meshRenderer.sharedMaterials;
+				for (int i = 0; i < materials.Length; i++) {
+					var originalMat = materials[i];
+					Material ghostMat;
+					if (!materialTable.ContainsKey(originalMat)) {
+						ghostMat = new Material(originalMat);
+						ghostMat.shader = ghostShader;
+						ghostMat.color = Color.white;
+						if (ghostMat.HasProperty("_TextureFade"))
+							ghostMat.SetFloat("_TextureFade", textureFade);
+						materialTable.Add(originalMat, ghostMat);
+					} else {
+						ghostMat = materialTable[originalMat];
+					}
+
+					materials[i] = ghostMat;
+				}
+
+				var goTransform = go.transform;
+				goTransform.parent = transform;
+
+				pool[poolIndex].Initialize(meshFilter.sharedMesh, materials, color, additive, fadeSpeed, meshRenderer.sortingLayerID, (sortWithDistanceOnly) ? meshRenderer.sortingOrder : meshRenderer.sortingOrder - 1);
+
+				goTransform.localPosition = new Vector3(0f, 0f, zOffset);
+				goTransform.localRotation = Quaternion.identity;
+				goTransform.localScale = Vector3.one;
+
+				goTransform.parent = null;
+
+				poolIndex++;
+
+				if (poolIndex == pool.Length)
+					poolIndex = 0;
+
+				nextSpawnTime = Time.time + spawnRate;
+			}
+		}
+
+		void OnDestroy () {
+			for (int i = 0; i < maximumGhosts; i++) {
+				if (pool[i] != null)
+					pool[i].Cleanup();
+			}
+
+			foreach (var mat in materialTable.Values)
+				Destroy(mat);
+		}
+
+
+		//based on UnifyWiki  http://wiki.unity3d.com/index.php?title=HexConverter
+		static Color32 HexToColor (string hex) {
+			if (hex.Length < 6)
+				return Color.magenta;
+
+			hex = hex.Replace("#", "");
+			byte r = byte.Parse(hex.Substring(0, 2), System.Globalization.NumberStyles.HexNumber);
+			byte g = byte.Parse(hex.Substring(2, 2), System.Globalization.NumberStyles.HexNumber);
+			byte b = byte.Parse(hex.Substring(4, 2), System.Globalization.NumberStyles.HexNumber);
+			byte a = 0xFF;
+			if (hex.Length == 8)
+				a = byte.Parse(hex.Substring(6, 2), System.Globalization.NumberStyles.HexNumber);
+
+			return new Color32(r, g, b, a);
+		}
+	}
+
 }

+ 40 - 9
spine-unity/Assets/spine-unity/Modules/Ragdoll/Editor/SkeletonRagdoll2DInspector.cs

@@ -1,11 +1,42 @@
-/*****************************************************************************
- * SkeletonRagdoll2D added by Mitch Thompson
- * Full irrevocable rights and permissions granted to Esoteric Software
-*****************************************************************************/
+/******************************************************************************
+ * 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 UnityEditor;
-
-namespace Spine.Unity.Modules {
-	public class SkeletonRagdoll2DInspector {}
+/*****************************************************************************
+ * SkeletonRagdoll2D added by Mitch Thompson
+ * Full irrevocable rights and permissions granted to Esoteric Software
+*****************************************************************************/
+
+using UnityEngine;
+using UnityEditor;
+
+namespace Spine.Unity.Modules {
+	public class SkeletonRagdoll2DInspector {}
 }

+ 49 - 18
spine-unity/Assets/spine-unity/Modules/Ragdoll/Editor/SkeletonRagdollInspector.cs

@@ -1,20 +1,51 @@
-/*****************************************************************************
- * SkeletonRagdoll added by Mitch Thompson
- * Full irrevocable rights and permissions granted to Esoteric Software
-*****************************************************************************/
-
-using UnityEngine;
-using UnityEditor;
-
-namespace Spine.Unity.Modules {
-	
-	public class SkeletonRagdollInspector : UnityEditor.Editor {
-		[CustomPropertyDrawer(typeof(SkeletonRagdoll.LayerFieldAttribute))]
-		public class LayerFieldPropertyDrawer : PropertyDrawer {
-			public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) {
-				property.intValue = EditorGUI.LayerField(position, label, property.intValue);
-			}
-		}
-	}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
 
+/*****************************************************************************
+ * SkeletonRagdoll added by Mitch Thompson
+ * Full irrevocable rights and permissions granted to Esoteric Software
+*****************************************************************************/
+
+using UnityEngine;
+using UnityEditor;
+
+namespace Spine.Unity.Modules {
+	
+	public class SkeletonRagdollInspector : UnityEditor.Editor {
+		[CustomPropertyDrawer(typeof(SkeletonRagdoll.LayerFieldAttribute))]
+		public class LayerFieldPropertyDrawer : PropertyDrawer {
+			public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) {
+				property.intValue = EditorGUI.LayerField(position, label, property.intValue);
+			}
+		}
+	}
+
 }

+ 407 - 376
spine-unity/Assets/spine-unity/Modules/Ragdoll/SkeletonRagdoll.cs

@@ -1,377 +1,408 @@
-/*****************************************************************************
- * SkeletonRagdoll added by Mitch Thompson
- * Full irrevocable rights and permissions granted to Esoteric Software
-*****************************************************************************/
-
-using UnityEngine;
-using System.Collections;
-using System.Collections.Generic;
-
-namespace Spine.Unity.Modules {
-	[RequireComponent(typeof(SkeletonRenderer))]
-	public class SkeletonRagdoll : MonoBehaviour {
-		static Transform parentSpaceHelper;
-
-		#region Inspector
-		[Header("Hierarchy")]
-		[SpineBone]
-		public string startingBoneName = "";
-		[SpineBone]
-		public List<string> stopBoneNames = new List<string>();
-
-		[Header("Parameters")]
-		public bool applyOnStart;
-		[Tooltip("Warning!  You will have to re-enable and tune mix values manually if attempting to remove the ragdoll system.")]
-		public bool disableIK = true;
-		public bool disableOtherConstraints = false;
-		[Space(18)]
-		[Tooltip("Set RootRigidbody IsKinematic to true when Apply is called.")]
-		public bool pinStartBone;
-		[Tooltip("Enable Collision between adjacent ragdoll elements (IE: Neck and Head)")]
-		public bool enableJointCollision;
-		public bool useGravity = true;
-		[Tooltip("If no BoundingBox Attachment is attached to a bone, this becomes the default Width or Radius of a Bone's ragdoll Rigidbody")]
-		public float thickness = 0.125f;
-		[Tooltip("Default rotational limit value.  Min is negative this value, Max is this value.")]
-		public float rotationLimit = 20;
-		public float rootMass = 20;
-		[Tooltip("If your ragdoll seems unstable or uneffected by limits, try lowering this value.")]
-		[Range(0.01f, 1f)]
-		public float massFalloffFactor = 0.4f;
-		[Tooltip("The layer assigned to all of the rigidbody parts.")]
-		public int colliderLayer = 0;
-		[Range(0, 1)]
-		public float mix = 1;
-		#endregion
-
-		ISkeletonAnimation targetSkeletonComponent;
-		Skeleton skeleton;
-		Dictionary<Bone, Transform> boneTable = new Dictionary<Bone, Transform>();
-		Transform ragdollRoot;
-		public Rigidbody RootRigidbody { get; private set; }
-		public Bone StartingBone { get; private set; }
-		Vector3 rootOffset;
-		public Vector3 RootOffset { get { return this.rootOffset; } }
-		bool isActive;
-		public bool IsActive { get { return this.isActive; } }
-
-		IEnumerator Start () {
-			if (parentSpaceHelper == null) {
-				parentSpaceHelper = (new GameObject("Parent Space Helper")).transform;
-				parentSpaceHelper.hideFlags = HideFlags.HideInHierarchy;
-			}
-
-			targetSkeletonComponent = GetComponent<SkeletonRenderer>() as ISkeletonAnimation;
-			if (targetSkeletonComponent == null) Debug.LogError("Attached Spine component does not implement ISkeletonAnimation. This script is not compatible.");
-			skeleton = targetSkeletonComponent.Skeleton;
-
-			if (applyOnStart) {
-				yield return null;
-				Apply();
-			}
-		}
-
-		#region API
-		public Rigidbody[] RigidbodyArray {
-			get {
-				if (!isActive)
-					return new Rigidbody[0];
-
-				var rigidBodies = new Rigidbody[boneTable.Count];
-				int i = 0;
-				foreach (Transform t in boneTable.Values) {
-					rigidBodies[i] = t.GetComponent<Rigidbody>();
-					i++;
-				}
-
-				return rigidBodies;
-			}
-		}
-
-		public Vector3 EstimatedSkeletonPosition {
-			get { return RootRigidbody.position - rootOffset; }
-		}
-
-		/// <summary>Instantiates the ragdoll simulation and applies its transforms to the skeleton.</summary>
-		public void Apply () {
-			isActive = true;
-			mix = 1;
-
-			StartingBone = skeleton.FindBone(startingBoneName);
-			RecursivelyCreateBoneProxies(StartingBone);
-
-			RootRigidbody = boneTable[StartingBone].GetComponent<Rigidbody>();
-			RootRigidbody.isKinematic = pinStartBone;
-			RootRigidbody.mass = rootMass;
-			var boneColliders = new List<Collider>();
-			foreach (var pair in boneTable) {
-				var b = pair.Key;
-				var t = pair.Value;
-				Transform parentTransform;
-				boneColliders.Add(t.GetComponent<Collider>());
-				if (b == StartingBone) {
-					ragdollRoot = new GameObject("RagdollRoot").transform;
-					ragdollRoot.SetParent(transform, false);
-					if (b == skeleton.RootBone) { // RagdollRoot is skeleton root.
-						ragdollRoot.localPosition = new Vector3(b.WorldX, b.WorldY, 0);
-						ragdollRoot.localRotation = Quaternion.Euler(0, 0, GetPropagatedRotation(b));
-					} else {
-						ragdollRoot.localPosition = new Vector3(b.Parent.WorldX, b.Parent.WorldY, 0);
-						ragdollRoot.localRotation = Quaternion.Euler(0, 0, GetPropagatedRotation(b.Parent));
-					}
-					parentTransform = ragdollRoot;
-					rootOffset = t.position - transform.position;
-				} else {
-					parentTransform = boneTable[b.Parent];
-				}
-
-				// Add joint and attach to parent.
-				var rbParent = parentTransform.GetComponent<Rigidbody>();
-				if (rbParent != null) {
-					var joint = t.gameObject.AddComponent<HingeJoint>();
-					joint.connectedBody = rbParent;
-					Vector3 localPos = parentTransform.InverseTransformPoint(t.position);
-					localPos.x *= 1;
-					joint.connectedAnchor = localPos;
-					joint.axis = Vector3.forward;
-
-					joint.GetComponent<Rigidbody>().mass = joint.connectedBody.mass * massFalloffFactor;
-					joint.limits = new JointLimits {
-						min = -rotationLimit,
-						max = rotationLimit,
-					};
-					joint.useLimits = true;
-					joint.enableCollision = enableJointCollision;
-				}
-			}
-
-			// Ignore collisions among bones.
-			for (int x = 0; x < boneColliders.Count; x++) {
-				for (int y = 0; y < boneColliders.Count; y++) {
-					if (x == y) continue;
-					Physics.IgnoreCollision(boneColliders[x], boneColliders[y]);
-				}
-			}
-
-			// Destroy existing override-mode SkeletonUtilityBones.
-			var utilityBones = GetComponentsInChildren<SkeletonUtilityBone>();
-			if (utilityBones.Length > 0) {
-				var destroyedUtilityBoneNames = new List<string>();
-				foreach (var ub in utilityBones) {
-					if (ub.mode == SkeletonUtilityBone.Mode.Override) {
-						destroyedUtilityBoneNames.Add(ub.gameObject.name);
-						Destroy(ub.gameObject);
-					}
-				}
-				if (destroyedUtilityBoneNames.Count > 0) {
-					string msg = "Destroyed Utility Bones: ";
-					for (int i = 0; i < destroyedUtilityBoneNames.Count; i++) {
-						msg += destroyedUtilityBoneNames[i];
-						if (i != destroyedUtilityBoneNames.Count - 1) {
-							msg += ",";
-						}
-					}
-					Debug.LogWarning(msg);
-				}
-			}
-				
-			// Disable skeleton constraints.
-			if (disableIK) {
-				var ikConstraints = skeleton.IkConstraints;
-				for (int i = 0, n = ikConstraints.Count; i < n; i++)
-					ikConstraints.Items[i].mix = 0;
-			}
-
-			if (disableOtherConstraints) {
-				var transformConstraints = skeleton.transformConstraints;
-				for (int i = 0, n = transformConstraints.Count; i < n; i++) {
-					transformConstraints.Items[i].rotateMix = 0;
-					transformConstraints.Items[i].scaleMix = 0;
-					transformConstraints.Items[i].shearMix = 0;
-					transformConstraints.Items[i].translateMix = 0;
-				}
-
-				var pathConstraints = skeleton.pathConstraints;
-				for (int i = 0, n = pathConstraints.Count; i < n; i++) {
-					pathConstraints.Items[i].rotateMix = 0;
-					pathConstraints.Items[i].translateMix = 0;
-				}
-			}
-
-			targetSkeletonComponent.UpdateWorld += UpdateSpineSkeleton;
-		}
-
-		/// <summary>Transitions the mix value from the current value to a target value.</summary>
-		public Coroutine SmoothMix (float target, float duration) {
-			return StartCoroutine(SmoothMixCoroutine(target, duration));
-		}
-
-		IEnumerator SmoothMixCoroutine (float target, float duration) {
-			float startTime = Time.time;
-			float startMix = mix;
-			while (mix > 0) {
-				mix = Mathf.SmoothStep(startMix, target, (Time.time - startTime) / duration);
-				yield return null;
-			}
-		}
-
-		/// <summary>Set the transform world position while preserving the ragdoll parts world position.</summary>
-		public void SetSkeletonPosition (Vector3 worldPosition) {
-			if (!isActive) {
-				Debug.LogWarning("Can't call SetSkeletonPosition while Ragdoll is not active!");
-				return;
-			}
-
-			Vector3 offset = worldPosition - transform.position;
-			transform.position = worldPosition;
-			foreach (Transform t in boneTable.Values)
-				t.position -= offset;
-
-			UpdateSpineSkeleton(null);
-			skeleton.UpdateWorldTransform();
-		}
-
-		/// <summary>Removes the ragdoll instance and effect from the animated skeleton.</summary>
-		public void Remove () {
-			isActive = false;
-			foreach (var t in boneTable.Values)
-				Destroy(t.gameObject);
-			
-			Destroy(ragdollRoot.gameObject);
-
-			boneTable.Clear();
-			targetSkeletonComponent.UpdateWorld -= UpdateSpineSkeleton;
-		}
-
-		public Rigidbody GetRigidbody (string boneName) {
-			var bone = skeleton.FindBone(boneName);
-			return (bone != null && boneTable.ContainsKey(bone)) ? boneTable[bone].GetComponent<Rigidbody>() : null;
-		}
-		#endregion
-
-		void RecursivelyCreateBoneProxies (Bone b) {
-			string boneName = b.data.name;
-			if (stopBoneNames.Contains(boneName))
-				return;
-
-			var boneGameObject = new GameObject(boneName);
-			boneGameObject.layer = colliderLayer;
-			Transform t = boneGameObject.transform;
-			boneTable.Add(b, t);
-
-			t.parent = transform;
-			t.localPosition = new Vector3(b.WorldX, b.WorldY, 0);
-			t.localRotation = Quaternion.Euler(0, 0, b.WorldRotationX - b.shearX);
-			t.localScale = new Vector3(b.WorldScaleX, b.WorldScaleY, 1);
-
-			// MITCH: You left "todo: proper ragdoll branching"
-			var colliders = AttachBoundingBoxRagdollColliders(b);
-			if (colliders.Count == 0) {
-				float length = b.Data.Length;
-				if (length == 0) {
-					var ball = boneGameObject.AddComponent<SphereCollider>();
-					ball.radius = thickness * 0.5f;
-				} else {					
-					var box = boneGameObject.AddComponent<BoxCollider>();
-					box.size = new Vector3(length, thickness, thickness);
-					box.center = new Vector3(length * 0.5f, 0);
-				}
-			}
-			var rb = boneGameObject.AddComponent<Rigidbody>();
-			rb.constraints = RigidbodyConstraints.FreezePositionZ;
-
-			foreach (Bone child in b.Children)
-				RecursivelyCreateBoneProxies(child);
-		}
-
-		void UpdateSpineSkeleton (ISkeletonAnimation skeletonRenderer) {
-			bool flipX = skeleton.flipX;
-			bool flipY = skeleton.flipY;
-			bool flipXOR = flipX ^ flipY;
-			bool flipOR = flipX || flipY;
-
-			foreach (var pair in boneTable) {
-				var b = pair.Key;
-				var t = pair.Value;
-				bool isStartingBone = b == StartingBone;
-				Transform parentTransform = isStartingBone ? ragdollRoot : boneTable[b.Parent];
-				Vector3 parentTransformWorldPosition = parentTransform.position;
-				Quaternion parentTransformWorldRotation = parentTransform.rotation;
-
-				parentSpaceHelper.position = parentTransformWorldPosition;
-				parentSpaceHelper.rotation = parentTransformWorldRotation;
-				parentSpaceHelper.localScale = parentTransform.localScale;
-
-				Vector3 boneWorldPosition = t.position;
-				Vector3 right = parentSpaceHelper.InverseTransformDirection(t.right);
-
-				Vector3 boneLocalPosition = parentSpaceHelper.InverseTransformPoint(boneWorldPosition);
-				float boneLocalRotation = Mathf.Atan2(right.y, right.x) * Mathf.Rad2Deg;
-
-				if (flipOR) {
-					if (isStartingBone) {
-						if (flipX) boneLocalPosition.x *= -1f;
-						if (flipY) boneLocalPosition.y *= -1f;
-
-						boneLocalRotation = boneLocalRotation * (flipXOR ? -1f : 1f);
-						if (flipX) boneLocalRotation += 180;
-					} else {
-						if (flipXOR) {
-							boneLocalRotation *= -1f;
-							boneLocalPosition.y *= -1f; // wtf??
-						}
-					}
-				}
-
-				b.x = Mathf.Lerp(b.x, boneLocalPosition.x, mix);
-				b.y = Mathf.Lerp(b.y, boneLocalPosition.y, mix);
-				b.rotation = Mathf.Lerp(b.rotation, boneLocalRotation, mix);
-				b.appliedRotation = Mathf.Lerp(b.appliedRotation, boneLocalRotation, mix);
-			}
-		}
-
-		List<Collider> AttachBoundingBoxRagdollColliders (Bone b) {
-			const string AttachmentNameMarker = "ragdoll";
-			var colliders = new List<Collider>();
-
-			Transform t = boneTable[b];
-			GameObject go = t.gameObject;
-			var skin = skeleton.Skin ?? skeleton.Data.DefaultSkin;
-
-			var attachments = new List<Attachment>();
-			foreach (Slot s in skeleton.Slots) {
-				if (s.Bone == b) {
-					skin.FindAttachmentsForSlot(skeleton.Slots.IndexOf(s), attachments);
-					foreach (var a in attachments) {
-						var bbAttachment = a as BoundingBoxAttachment;
-						if (bbAttachment != null) {
-							if (!a.Name.ToLower().Contains(AttachmentNameMarker))
-								continue;
-
-							var bbCollider = go.AddComponent<BoxCollider>();
-							var bounds = SkeletonUtility.GetBoundingBoxBounds(bbAttachment, thickness);
-							bbCollider.center = bounds.center;
-							bbCollider.size = bounds.size;
-							colliders.Add(bbCollider);
-						}
-					}
-				}
-			}
-
-			return colliders;
-		}
-
-		static float GetPropagatedRotation (Bone b) {
-			Bone parent = b.Parent;
-			float a = b.AppliedRotation;
-			while (parent != null) {
-				a += parent.AppliedRotation;
-				parent = parent.parent;
-			}
-			return a;
-		}
-
-		public class LayerFieldAttribute : PropertyAttribute {}
-	}
-
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * SkeletonRagdoll added by Mitch Thompson
+ * Full irrevocable rights and permissions granted to Esoteric Software
+*****************************************************************************/
+
+using UnityEngine;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Spine.Unity.Modules {
+	[RequireComponent(typeof(SkeletonRenderer))]
+	public class SkeletonRagdoll : MonoBehaviour {
+		static Transform parentSpaceHelper;
+
+		#region Inspector
+		[Header("Hierarchy")]
+		[SpineBone]
+		public string startingBoneName = "";
+		[SpineBone]
+		public List<string> stopBoneNames = new List<string>();
+
+		[Header("Parameters")]
+		public bool applyOnStart;
+		[Tooltip("Warning!  You will have to re-enable and tune mix values manually if attempting to remove the ragdoll system.")]
+		public bool disableIK = true;
+		public bool disableOtherConstraints = false;
+		[Space(18)]
+		[Tooltip("Set RootRigidbody IsKinematic to true when Apply is called.")]
+		public bool pinStartBone;
+		[Tooltip("Enable Collision between adjacent ragdoll elements (IE: Neck and Head)")]
+		public bool enableJointCollision;
+		public bool useGravity = true;
+		[Tooltip("If no BoundingBox Attachment is attached to a bone, this becomes the default Width or Radius of a Bone's ragdoll Rigidbody")]
+		public float thickness = 0.125f;
+		[Tooltip("Default rotational limit value.  Min is negative this value, Max is this value.")]
+		public float rotationLimit = 20;
+		public float rootMass = 20;
+		[Tooltip("If your ragdoll seems unstable or uneffected by limits, try lowering this value.")]
+		[Range(0.01f, 1f)]
+		public float massFalloffFactor = 0.4f;
+		[Tooltip("The layer assigned to all of the rigidbody parts.")]
+		public int colliderLayer = 0;
+		[Range(0, 1)]
+		public float mix = 1;
+		#endregion
+
+		ISkeletonAnimation targetSkeletonComponent;
+		Skeleton skeleton;
+		Dictionary<Bone, Transform> boneTable = new Dictionary<Bone, Transform>();
+		Transform ragdollRoot;
+		public Rigidbody RootRigidbody { get; private set; }
+		public Bone StartingBone { get; private set; }
+		Vector3 rootOffset;
+		public Vector3 RootOffset { get { return this.rootOffset; } }
+		bool isActive;
+		public bool IsActive { get { return this.isActive; } }
+
+		IEnumerator Start () {
+			if (parentSpaceHelper == null) {
+				parentSpaceHelper = (new GameObject("Parent Space Helper")).transform;
+				parentSpaceHelper.hideFlags = HideFlags.HideInHierarchy;
+			}
+
+			targetSkeletonComponent = GetComponent<SkeletonRenderer>() as ISkeletonAnimation;
+			if (targetSkeletonComponent == null) Debug.LogError("Attached Spine component does not implement ISkeletonAnimation. This script is not compatible.");
+			skeleton = targetSkeletonComponent.Skeleton;
+
+			if (applyOnStart) {
+				yield return null;
+				Apply();
+			}
+		}
+
+		#region API
+		public Rigidbody[] RigidbodyArray {
+			get {
+				if (!isActive)
+					return new Rigidbody[0];
+
+				var rigidBodies = new Rigidbody[boneTable.Count];
+				int i = 0;
+				foreach (Transform t in boneTable.Values) {
+					rigidBodies[i] = t.GetComponent<Rigidbody>();
+					i++;
+				}
+
+				return rigidBodies;
+			}
+		}
+
+		public Vector3 EstimatedSkeletonPosition {
+			get { return RootRigidbody.position - rootOffset; }
+		}
+
+		/// <summary>Instantiates the ragdoll simulation and applies its transforms to the skeleton.</summary>
+		public void Apply () {
+			isActive = true;
+			mix = 1;
+
+			StartingBone = skeleton.FindBone(startingBoneName);
+			RecursivelyCreateBoneProxies(StartingBone);
+
+			RootRigidbody = boneTable[StartingBone].GetComponent<Rigidbody>();
+			RootRigidbody.isKinematic = pinStartBone;
+			RootRigidbody.mass = rootMass;
+			var boneColliders = new List<Collider>();
+			foreach (var pair in boneTable) {
+				var b = pair.Key;
+				var t = pair.Value;
+				Transform parentTransform;
+				boneColliders.Add(t.GetComponent<Collider>());
+				if (b == StartingBone) {
+					ragdollRoot = new GameObject("RagdollRoot").transform;
+					ragdollRoot.SetParent(transform, false);
+					if (b == skeleton.RootBone) { // RagdollRoot is skeleton root.
+						ragdollRoot.localPosition = new Vector3(b.WorldX, b.WorldY, 0);
+						ragdollRoot.localRotation = Quaternion.Euler(0, 0, GetPropagatedRotation(b));
+					} else {
+						ragdollRoot.localPosition = new Vector3(b.Parent.WorldX, b.Parent.WorldY, 0);
+						ragdollRoot.localRotation = Quaternion.Euler(0, 0, GetPropagatedRotation(b.Parent));
+					}
+					parentTransform = ragdollRoot;
+					rootOffset = t.position - transform.position;
+				} else {
+					parentTransform = boneTable[b.Parent];
+				}
+
+				// Add joint and attach to parent.
+				var rbParent = parentTransform.GetComponent<Rigidbody>();
+				if (rbParent != null) {
+					var joint = t.gameObject.AddComponent<HingeJoint>();
+					joint.connectedBody = rbParent;
+					Vector3 localPos = parentTransform.InverseTransformPoint(t.position);
+					localPos.x *= 1;
+					joint.connectedAnchor = localPos;
+					joint.axis = Vector3.forward;
+
+					joint.GetComponent<Rigidbody>().mass = joint.connectedBody.mass * massFalloffFactor;
+					joint.limits = new JointLimits {
+						min = -rotationLimit,
+						max = rotationLimit,
+					};
+					joint.useLimits = true;
+					joint.enableCollision = enableJointCollision;
+				}
+			}
+
+			// Ignore collisions among bones.
+			for (int x = 0; x < boneColliders.Count; x++) {
+				for (int y = 0; y < boneColliders.Count; y++) {
+					if (x == y) continue;
+					Physics.IgnoreCollision(boneColliders[x], boneColliders[y]);
+				}
+			}
+
+			// Destroy existing override-mode SkeletonUtilityBones.
+			var utilityBones = GetComponentsInChildren<SkeletonUtilityBone>();
+			if (utilityBones.Length > 0) {
+				var destroyedUtilityBoneNames = new List<string>();
+				foreach (var ub in utilityBones) {
+					if (ub.mode == SkeletonUtilityBone.Mode.Override) {
+						destroyedUtilityBoneNames.Add(ub.gameObject.name);
+						Destroy(ub.gameObject);
+					}
+				}
+				if (destroyedUtilityBoneNames.Count > 0) {
+					string msg = "Destroyed Utility Bones: ";
+					for (int i = 0; i < destroyedUtilityBoneNames.Count; i++) {
+						msg += destroyedUtilityBoneNames[i];
+						if (i != destroyedUtilityBoneNames.Count - 1) {
+							msg += ",";
+						}
+					}
+					Debug.LogWarning(msg);
+				}
+			}
+				
+			// Disable skeleton constraints.
+			if (disableIK) {
+				var ikConstraints = skeleton.IkConstraints;
+				for (int i = 0, n = ikConstraints.Count; i < n; i++)
+					ikConstraints.Items[i].mix = 0;
+			}
+
+			if (disableOtherConstraints) {
+				var transformConstraints = skeleton.transformConstraints;
+				for (int i = 0, n = transformConstraints.Count; i < n; i++) {
+					transformConstraints.Items[i].rotateMix = 0;
+					transformConstraints.Items[i].scaleMix = 0;
+					transformConstraints.Items[i].shearMix = 0;
+					transformConstraints.Items[i].translateMix = 0;
+				}
+
+				var pathConstraints = skeleton.pathConstraints;
+				for (int i = 0, n = pathConstraints.Count; i < n; i++) {
+					pathConstraints.Items[i].rotateMix = 0;
+					pathConstraints.Items[i].translateMix = 0;
+				}
+			}
+
+			targetSkeletonComponent.UpdateWorld += UpdateSpineSkeleton;
+		}
+
+		/// <summary>Transitions the mix value from the current value to a target value.</summary>
+		public Coroutine SmoothMix (float target, float duration) {
+			return StartCoroutine(SmoothMixCoroutine(target, duration));
+		}
+
+		IEnumerator SmoothMixCoroutine (float target, float duration) {
+			float startTime = Time.time;
+			float startMix = mix;
+			while (mix > 0) {
+				mix = Mathf.SmoothStep(startMix, target, (Time.time - startTime) / duration);
+				yield return null;
+			}
+		}
+
+		/// <summary>Set the transform world position while preserving the ragdoll parts world position.</summary>
+		public void SetSkeletonPosition (Vector3 worldPosition) {
+			if (!isActive) {
+				Debug.LogWarning("Can't call SetSkeletonPosition while Ragdoll is not active!");
+				return;
+			}
+
+			Vector3 offset = worldPosition - transform.position;
+			transform.position = worldPosition;
+			foreach (Transform t in boneTable.Values)
+				t.position -= offset;
+
+			UpdateSpineSkeleton(null);
+			skeleton.UpdateWorldTransform();
+		}
+
+		/// <summary>Removes the ragdoll instance and effect from the animated skeleton.</summary>
+		public void Remove () {
+			isActive = false;
+			foreach (var t in boneTable.Values)
+				Destroy(t.gameObject);
+			
+			Destroy(ragdollRoot.gameObject);
+
+			boneTable.Clear();
+			targetSkeletonComponent.UpdateWorld -= UpdateSpineSkeleton;
+		}
+
+		public Rigidbody GetRigidbody (string boneName) {
+			var bone = skeleton.FindBone(boneName);
+			return (bone != null && boneTable.ContainsKey(bone)) ? boneTable[bone].GetComponent<Rigidbody>() : null;
+		}
+		#endregion
+
+		void RecursivelyCreateBoneProxies (Bone b) {
+			string boneName = b.data.name;
+			if (stopBoneNames.Contains(boneName))
+				return;
+
+			var boneGameObject = new GameObject(boneName);
+			boneGameObject.layer = colliderLayer;
+			Transform t = boneGameObject.transform;
+			boneTable.Add(b, t);
+
+			t.parent = transform;
+			t.localPosition = new Vector3(b.WorldX, b.WorldY, 0);
+			t.localRotation = Quaternion.Euler(0, 0, b.WorldRotationX - b.shearX);
+			t.localScale = new Vector3(b.WorldScaleX, b.WorldScaleY, 1);
+
+			// MITCH: You left "todo: proper ragdoll branching"
+			var colliders = AttachBoundingBoxRagdollColliders(b);
+			if (colliders.Count == 0) {
+				float length = b.Data.Length;
+				if (length == 0) {
+					var ball = boneGameObject.AddComponent<SphereCollider>();
+					ball.radius = thickness * 0.5f;
+				} else {					
+					var box = boneGameObject.AddComponent<BoxCollider>();
+					box.size = new Vector3(length, thickness, thickness);
+					box.center = new Vector3(length * 0.5f, 0);
+				}
+			}
+			var rb = boneGameObject.AddComponent<Rigidbody>();
+			rb.constraints = RigidbodyConstraints.FreezePositionZ;
+
+			foreach (Bone child in b.Children)
+				RecursivelyCreateBoneProxies(child);
+		}
+
+		void UpdateSpineSkeleton (ISkeletonAnimation skeletonRenderer) {
+			bool flipX = skeleton.flipX;
+			bool flipY = skeleton.flipY;
+			bool flipXOR = flipX ^ flipY;
+			bool flipOR = flipX || flipY;
+
+			foreach (var pair in boneTable) {
+				var b = pair.Key;
+				var t = pair.Value;
+				bool isStartingBone = b == StartingBone;
+				Transform parentTransform = isStartingBone ? ragdollRoot : boneTable[b.Parent];
+				Vector3 parentTransformWorldPosition = parentTransform.position;
+				Quaternion parentTransformWorldRotation = parentTransform.rotation;
+
+				parentSpaceHelper.position = parentTransformWorldPosition;
+				parentSpaceHelper.rotation = parentTransformWorldRotation;
+				parentSpaceHelper.localScale = parentTransform.localScale;
+
+				Vector3 boneWorldPosition = t.position;
+				Vector3 right = parentSpaceHelper.InverseTransformDirection(t.right);
+
+				Vector3 boneLocalPosition = parentSpaceHelper.InverseTransformPoint(boneWorldPosition);
+				float boneLocalRotation = Mathf.Atan2(right.y, right.x) * Mathf.Rad2Deg;
+
+				if (flipOR) {
+					if (isStartingBone) {
+						if (flipX) boneLocalPosition.x *= -1f;
+						if (flipY) boneLocalPosition.y *= -1f;
+
+						boneLocalRotation = boneLocalRotation * (flipXOR ? -1f : 1f);
+						if (flipX) boneLocalRotation += 180;
+					} else {
+						if (flipXOR) {
+							boneLocalRotation *= -1f;
+							boneLocalPosition.y *= -1f; // wtf??
+						}
+					}
+				}
+
+				b.x = Mathf.Lerp(b.x, boneLocalPosition.x, mix);
+				b.y = Mathf.Lerp(b.y, boneLocalPosition.y, mix);
+				b.rotation = Mathf.Lerp(b.rotation, boneLocalRotation, mix);
+				b.appliedRotation = Mathf.Lerp(b.appliedRotation, boneLocalRotation, mix);
+			}
+		}
+
+		List<Collider> AttachBoundingBoxRagdollColliders (Bone b) {
+			const string AttachmentNameMarker = "ragdoll";
+			var colliders = new List<Collider>();
+
+			Transform t = boneTable[b];
+			GameObject go = t.gameObject;
+			var skin = skeleton.Skin ?? skeleton.Data.DefaultSkin;
+
+			var attachments = new List<Attachment>();
+			foreach (Slot s in skeleton.Slots) {
+				if (s.Bone == b) {
+					skin.FindAttachmentsForSlot(skeleton.Slots.IndexOf(s), attachments);
+					foreach (var a in attachments) {
+						var bbAttachment = a as BoundingBoxAttachment;
+						if (bbAttachment != null) {
+							if (!a.Name.ToLower().Contains(AttachmentNameMarker))
+								continue;
+
+							var bbCollider = go.AddComponent<BoxCollider>();
+							var bounds = SkeletonUtility.GetBoundingBoxBounds(bbAttachment, thickness);
+							bbCollider.center = bounds.center;
+							bbCollider.size = bounds.size;
+							colliders.Add(bbCollider);
+						}
+					}
+				}
+			}
+
+			return colliders;
+		}
+
+		static float GetPropagatedRotation (Bone b) {
+			Bone parent = b.Parent;
+			float a = b.AppliedRotation;
+			while (parent != null) {
+				a += parent.AppliedRotation;
+				parent = parent.parent;
+			}
+			return a;
+		}
+
+		public class LayerFieldAttribute : PropertyAttribute {}
+	}
+
 }

+ 440 - 409
spine-unity/Assets/spine-unity/Modules/Ragdoll/SkeletonRagdoll2D.cs

@@ -1,410 +1,441 @@
-/*****************************************************************************
- * SkeletonRagdoll2D added by Mitch Thompson
- * Full irrevocable rights and permissions granted to Esoteric Software
-*****************************************************************************/
-//#define FLIPDEBUG
-
-using UnityEngine;
-using System.Collections;
-using System.Collections.Generic;
-using Spine.Unity;
-using UnityEngine.Assertions;
-
-namespace Spine.Unity.Modules {
-	[RequireComponent(typeof(SkeletonRenderer))]
-	public class SkeletonRagdoll2D : MonoBehaviour {
-		static Transform parentSpaceHelper;
-
-		#region Inspector
-		#if FLIPDEBUG
-		[Header("DEBUG")]
-		public bool flipXInitially;
-		public bool flipYInitially;
-		public bool spawnKinematic;
-		public bool disableUpdateBones;
-		#endif
-
-		[Header("Hierarchy")]
-		[SpineBone]
-		public string startingBoneName = "";
-		[SpineBone]
-		public List<string> stopBoneNames = new List<string>();
-
-		[Header("Parameters")]
-		public bool applyOnStart;
-		[Tooltip("Warning!  You will have to re-enable and tune mix values manually if attempting to remove the ragdoll system.")]
-		public bool disableIK = true;
-		public bool disableOtherConstraints = false;
-		[Space]
-		[Tooltip("Set RootRigidbody IsKinematic to true when Apply is called.")]
-		public bool pinStartBone;
-		public float gravityScale = 1;
-		[Tooltip("If no BoundingBox Attachment is attached to a bone, this becomes the default Width or Radius of a Bone's ragdoll Rigidbody")]
-		public float thickness = 0.125f;
-		[Tooltip("Default rotational limit value.  Min is negative this value, Max is this value.")]
-		public float rotationLimit = 20;
-		public float rootMass = 20;
-		[Tooltip("If your ragdoll seems unstable or uneffected by limits, try lowering this value.")]
-		[Range(0.01f, 1f)]
-		public float massFalloffFactor = 0.4f;
-		[Tooltip("The layer assigned to all of the rigidbody parts.")]
-		[SkeletonRagdoll.LayerField]
-		public int colliderLayer = 0;
-		[Range(0, 1)]
-		public float mix = 1;
-		#endregion
-
-		ISkeletonAnimation targetSkeletonComponent;
-		Skeleton skeleton;
-		Dictionary<Bone, Transform> boneTable = new Dictionary<Bone, Transform>();
-		Transform ragdollRoot;
-		public Rigidbody2D RootRigidbody { get; private set; }
-		public Bone StartingBone { get; private set; }
-		Vector2 rootOffset;
-		public Vector3 RootOffset { get { return this.rootOffset; } }
-		bool isActive;
-		public bool IsActive { get { return this.isActive; } }
-
-		IEnumerator Start () {
-			if (parentSpaceHelper == null) {
-				parentSpaceHelper = (new GameObject("Parent Space Helper")).transform;
-				#if !FLIPDEBUG
-				parentSpaceHelper.hideFlags = HideFlags.HideInHierarchy;
-				#endif
-			}
-
-			targetSkeletonComponent = GetComponent<SkeletonRenderer>() as ISkeletonAnimation;
-			if (targetSkeletonComponent == null) Debug.LogError("Attached Spine component does not implement ISkeletonAnimation. This script is not compatible.");
-			skeleton = targetSkeletonComponent.Skeleton;
-
-			#if FLIPDEBUG
-			skeleton.flipX = flipXInitially;
-			skeleton.flipY = flipYInitially;
-			#endif
-
-			if (applyOnStart) {
-				yield return null;
-				Apply();
-			}
-		}
-
-		#region API
-		public Rigidbody2D[] RigidbodyArray {
-			get {
-				if (!isActive)
-					return new Rigidbody2D[0];
-
-				var rigidBodies = new Rigidbody2D[boneTable.Count];
-				int i = 0;
-				foreach (Transform t in boneTable.Values) {
-					rigidBodies[i] = t.GetComponent<Rigidbody2D>();
-					i++;
-				}
-
-				return rigidBodies;
-			}
-		}
-
-		public Vector3 EstimatedSkeletonPosition {
-			get { return this.RootRigidbody.position - rootOffset; }
-		}
-
-		/// <summary>Instantiates the ragdoll simulation and applies its transforms to the skeleton.</summary>
-		public void Apply () {
-			isActive = true;
-			mix = 1;
-
-			Bone startingBone = this.StartingBone = skeleton.FindBone(startingBoneName);
-			RecursivelyCreateBoneProxies(startingBone);
-
-			RootRigidbody = boneTable[startingBone].GetComponent<Rigidbody2D>();
-			#if FLIPDEBUG
-			if (!RootRigidbody.isKinematic)
-			#endif
-			RootRigidbody.isKinematic = pinStartBone;
-			RootRigidbody.mass = rootMass;
-			var boneColliders = new List<Collider2D>();
-			foreach (var pair in boneTable) {
-				var b = pair.Key;
-				var t = pair.Value;
-				Transform parentTransform;
-				boneColliders.Add(t.GetComponent<Collider2D>());
-				if (b == startingBone) {
-					ragdollRoot = new GameObject("RagdollRoot").transform;
-					ragdollRoot.SetParent(transform, false);
-					if (b == skeleton.RootBone) { // RagdollRoot is skeleton root.
-						ragdollRoot.localPosition = new Vector3(b.WorldX, b.WorldY, 0);
-						ragdollRoot.localRotation = Quaternion.Euler(0, 0, GetPropagatedRotation(b));
-					} else {
-						ragdollRoot.localPosition = new Vector3(b.Parent.WorldX, b.Parent.WorldY, 0);
-						ragdollRoot.localRotation = Quaternion.Euler(0, 0, GetPropagatedRotation(b.Parent));
-					}
-					parentTransform = ragdollRoot;
-					rootOffset = t.position - transform.position;
-				} else {
-					parentTransform = boneTable[b.Parent];
-				}
-
-				// Add joint and attach to parent.
-				var rbParent = parentTransform.GetComponent<Rigidbody2D>();
-				if (rbParent != null) {
-					var joint = t.gameObject.AddComponent<HingeJoint2D>();
-					joint.connectedBody = rbParent;
-					Vector3 localPos = parentTransform.InverseTransformPoint(t.position);
-					joint.connectedAnchor = localPos;
-
-					joint.GetComponent<Rigidbody2D>().mass = joint.connectedBody.mass * massFalloffFactor;
-					joint.limits = new JointAngleLimits2D {
-						min = -rotationLimit,
-						max = rotationLimit
-					};
-					joint.useLimits = true;
-				}
-			}
-
-			// Ignore collisions among bones.
-			for (int x = 0; x < boneColliders.Count; x++) {
-				for (int y = 0; y < boneColliders.Count; y++) {
-					if (x == y) continue;
-					Physics2D.IgnoreCollision(boneColliders[x], boneColliders[y]);
-				}
-			}
-
-			// Destroy existing override-mode SkeletonUtility bones.
-			var utilityBones = GetComponentsInChildren<SkeletonUtilityBone>();
-			if (utilityBones.Length > 0) {
-				var destroyedUtilityBoneNames = new List<string>();
-				foreach (var ub in utilityBones) {
-					if (ub.mode == SkeletonUtilityBone.Mode.Override) {
-						destroyedUtilityBoneNames.Add(ub.gameObject.name);
-						Destroy(ub.gameObject);
-					}
-				}
-				if (destroyedUtilityBoneNames.Count > 0) {
-					string msg = "Destroyed Utility Bones: ";
-					for (int i = 0; i < destroyedUtilityBoneNames.Count; i++) {
-						msg += destroyedUtilityBoneNames[i];
-						if (i != destroyedUtilityBoneNames.Count - 1) {
-							msg += ",";
-						}
-					}
-					Debug.LogWarning(msg);
-				}
-			}
-
-			// Disable skeleton constraints.
-			if (disableIK) {
-				var ikConstraints = skeleton.IkConstraints;
-				for (int i = 0, n = ikConstraints.Count; i < n; i++)
-					ikConstraints.Items[i].mix = 0;
-			}
-
-			if (disableOtherConstraints) {
-				var transformConstraints = skeleton.transformConstraints;
-				for (int i = 0, n = transformConstraints.Count; i < n; i++) {
-					transformConstraints.Items[i].rotateMix = 0;
-					transformConstraints.Items[i].scaleMix = 0;
-					transformConstraints.Items[i].shearMix = 0;
-					transformConstraints.Items[i].translateMix = 0;
-				}
-
-				var pathConstraints = skeleton.pathConstraints;
-				for (int i = 0, n = pathConstraints.Count; i < n; i++) {
-					pathConstraints.Items[i].rotateMix = 0;
-					pathConstraints.Items[i].translateMix = 0;
-				}
-			}
-
-			targetSkeletonComponent.UpdateWorld += UpdateSpineSkeleton;
-		}
-
-		/// <summary>Transitions the mix value from the current value to a target value.</summary>
-		public Coroutine SmoothMix (float target, float duration) {
-			return StartCoroutine(SmoothMixCoroutine(target, duration));
-		}
-
-		IEnumerator SmoothMixCoroutine (float target, float duration) {
-			float startTime = Time.time;
-			float startMix = mix;
-			while (mix > 0) {
-				mix = Mathf.SmoothStep(startMix, target, (Time.time - startTime) / duration);
-				yield return null;
-			}
-		}
-
-		/// <summary>Set the transform world position while preserving the ragdoll parts world position.</summary>
-		public void SetSkeletonPosition (Vector3 worldPosition) {
-			if (!isActive) {
-				Debug.LogWarning("Can't call SetSkeletonPosition while Ragdoll is not active!");
-				return;
-			}
-
-			Vector3 offset = worldPosition - transform.position;
-			transform.position = worldPosition;
-			foreach (Transform t in boneTable.Values)
-				t.position -= offset;
-
-			UpdateSpineSkeleton(null);
-			skeleton.UpdateWorldTransform();
-		}
-
-		/// <summary>Removes the ragdoll instance and effect from the animated skeleton.</summary>
-		public void Remove () {
-			isActive = false;
-			foreach (var t in boneTable.Values)
-				Destroy(t.gameObject);
-
-			Destroy(ragdollRoot.gameObject);
-			boneTable.Clear();
-			targetSkeletonComponent.UpdateWorld -= UpdateSpineSkeleton;
-		}
-
-		public Rigidbody2D GetRigidbody (string boneName) {
-			var bone = skeleton.FindBone(boneName);
-			return (bone != null && boneTable.ContainsKey(bone)) ? boneTable[bone].GetComponent<Rigidbody2D>() : null;
-		}
-		#endregion
-
-		/// <summary>Generates the ragdoll simulation's Transform and joint setup.</summary>
-		void RecursivelyCreateBoneProxies (Bone b) {
-			string boneName = b.data.name;
-			if (stopBoneNames.Contains(boneName))
-				return;
-
-			var boneGameObject = new GameObject(boneName);
-			boneGameObject.layer = this.colliderLayer;
-			Transform t = boneGameObject.transform;
-			boneTable.Add(b, t);
-
-			t.parent = transform;
-			t.localPosition = new Vector3(b.WorldX, b.WorldY, 0);
-			t.localRotation = Quaternion.Euler(0, 0, b.WorldRotationX - b.shearX);
-			t.localScale = new Vector3(b.WorldScaleX, b.WorldScaleY, 0);
-
-			// MITCH: You left "todo: proper ragdoll branching"
-			var colliders = AttachBoundingBoxRagdollColliders(b, boneGameObject, skeleton);
-			if (colliders.Count == 0) {
-				float length = b.data.length;
-				if (length == 0) {
-					var circle = boneGameObject.AddComponent<CircleCollider2D>();
-					circle.radius = thickness * 0.5f;
-				} else {				
-					var box = boneGameObject.AddComponent<BoxCollider2D>();
-					box.size = new Vector2(length, thickness);
-					box.offset = new Vector2(length * 0.5f, 0); // box.center in UNITY_4
-				}
-			}
-			var rb = boneGameObject.AddComponent<Rigidbody2D>();
-			rb.gravityScale = this.gravityScale;
-
-			#if FLIPDEBUG
-			rb.isKinematic = spawnKinematic;
-			#endif
-
-			foreach (Bone child in b.Children)
-				RecursivelyCreateBoneProxies(child);
-		}
-
-		/// <summary>Performed every skeleton animation update to translate Unity Transforms positions into Spine bone transforms.</summary>
-		void UpdateSpineSkeleton (ISkeletonAnimation animatedSkeleton) {
-			#if FLIPDEBUG
-			if (disableUpdateBones) return;
-			#endif
-
-			bool flipX = skeleton.flipX;
-			bool flipY = skeleton.flipY;
-			bool flipXOR = flipX ^ flipY;
-			bool flipOR = flipX || flipY;
-			var startingBone = this.StartingBone;
-
-			foreach (var pair in boneTable) {
-				var b = pair.Key;
-				var t = pair.Value;
-				bool isStartingBone = (b == startingBone);
-				Transform parentTransform = isStartingBone ? ragdollRoot : boneTable[b.Parent];
-				Vector3 parentTransformWorldPosition = parentTransform.position;
-				Quaternion parentTransformWorldRotation = parentTransform.rotation;
-
-				parentSpaceHelper.position = parentTransformWorldPosition;
-				parentSpaceHelper.rotation = parentTransformWorldRotation;
-				parentSpaceHelper.localScale = parentTransform.localScale;
-
-				Vector3 boneWorldPosition = t.position;
-				Vector3 right = parentSpaceHelper.InverseTransformDirection(t.right);
-
-				Vector3 boneLocalPosition = parentSpaceHelper.InverseTransformPoint(boneWorldPosition);
-				float boneLocalRotation = Mathf.Atan2(right.y, right.x) * Mathf.Rad2Deg;
-				if (flipOR) {
-					if (isStartingBone) {
-						if (flipX) boneLocalPosition.x *= -1f;
-						if (flipY) boneLocalPosition.y *= -1f;
-
-						boneLocalRotation = boneLocalRotation * (flipXOR ? -1f : 1f);
-						if (flipX) boneLocalRotation += 180;
-					} else {
-						if (flipXOR) {
-							boneLocalRotation *= -1f;
-							boneLocalPosition.y *= -1f; // wtf??
-						}
-					}
-				}
-
-				b.x = Mathf.Lerp(b.x, boneLocalPosition.x, mix);
-				b.y = Mathf.Lerp(b.y, boneLocalPosition.y, mix);
-				b.rotation = Mathf.Lerp(b.rotation, boneLocalRotation, mix);
-				b.appliedRotation = Mathf.Lerp(b.appliedRotation, boneLocalRotation, mix);
-			}
-		}
-
-		static List<Collider2D> AttachBoundingBoxRagdollColliders (Bone b, GameObject go, Skeleton skeleton) {
-			const string AttachmentNameMarker = "ragdoll";
-			var colliders = new List<Collider2D>();
-			var skin = skeleton.Skin ?? skeleton.Data.DefaultSkin;
-
-			var attachments = new List<Attachment>();
-			foreach (Slot s in skeleton.Slots) {
-				if (s.bone == b) {
-					skin.FindAttachmentsForSlot(skeleton.Slots.IndexOf(s), attachments);
-					foreach (var a in attachments) {
-						var bbAttachment = a as BoundingBoxAttachment;
-						if (bbAttachment != null) {
-							if (!a.Name.ToLower().Contains(AttachmentNameMarker))
-								continue;
-
-							var bbCollider = SkeletonUtility.AddBoundingBoxAsComponent(bbAttachment, go, false);
-							colliders.Add(bbCollider);
-						}
-					}
-				}
-			}
-
-			return colliders;
-		}
-			
-		static float GetPropagatedRotation (Bone b) {
-			Bone parent = b.Parent;
-			float a = b.AppliedRotation;
-			while (parent != null) {
-				a += parent.AppliedRotation;
-				parent = parent.parent;
-			}
-			return a;
-		}
-
-		static Vector3 FlipScale (bool flipX, bool flipY) {
-			return new Vector3(flipX ? -1f : 1f, flipY ? -1f : 1f, 1f);
-		}
-
-		#if UNITY_EDITOR
-		void OnDrawGizmosSelected () {
-			if (isActive) {
-				Gizmos.DrawWireSphere(transform.position, thickness * 1.2f);
-				Vector3 newTransformPos = RootRigidbody.position - rootOffset;
-				Gizmos.DrawLine(transform.position, newTransformPos);
-				Gizmos.DrawWireSphere(newTransformPos, thickness * 1.2f);
-			}
-		}
-		#endif
-	}
-
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * SkeletonRagdoll2D added by Mitch Thompson
+ * Full irrevocable rights and permissions granted to Esoteric Software
+*****************************************************************************/
+//#define FLIPDEBUG
+
+using UnityEngine;
+using System.Collections;
+using System.Collections.Generic;
+using Spine.Unity;
+using UnityEngine.Assertions;
+
+namespace Spine.Unity.Modules {
+	[RequireComponent(typeof(SkeletonRenderer))]
+	public class SkeletonRagdoll2D : MonoBehaviour {
+		static Transform parentSpaceHelper;
+
+		#region Inspector
+		#if FLIPDEBUG
+		[Header("DEBUG")]
+		public bool flipXInitially;
+		public bool flipYInitially;
+		public bool spawnKinematic;
+		public bool disableUpdateBones;
+		#endif
+
+		[Header("Hierarchy")]
+		[SpineBone]
+		public string startingBoneName = "";
+		[SpineBone]
+		public List<string> stopBoneNames = new List<string>();
+
+		[Header("Parameters")]
+		public bool applyOnStart;
+		[Tooltip("Warning!  You will have to re-enable and tune mix values manually if attempting to remove the ragdoll system.")]
+		public bool disableIK = true;
+		public bool disableOtherConstraints = false;
+		[Space]
+		[Tooltip("Set RootRigidbody IsKinematic to true when Apply is called.")]
+		public bool pinStartBone;
+		public float gravityScale = 1;
+		[Tooltip("If no BoundingBox Attachment is attached to a bone, this becomes the default Width or Radius of a Bone's ragdoll Rigidbody")]
+		public float thickness = 0.125f;
+		[Tooltip("Default rotational limit value.  Min is negative this value, Max is this value.")]
+		public float rotationLimit = 20;
+		public float rootMass = 20;
+		[Tooltip("If your ragdoll seems unstable or uneffected by limits, try lowering this value.")]
+		[Range(0.01f, 1f)]
+		public float massFalloffFactor = 0.4f;
+		[Tooltip("The layer assigned to all of the rigidbody parts.")]
+		[SkeletonRagdoll.LayerField]
+		public int colliderLayer = 0;
+		[Range(0, 1)]
+		public float mix = 1;
+		#endregion
+
+		ISkeletonAnimation targetSkeletonComponent;
+		Skeleton skeleton;
+		Dictionary<Bone, Transform> boneTable = new Dictionary<Bone, Transform>();
+		Transform ragdollRoot;
+		public Rigidbody2D RootRigidbody { get; private set; }
+		public Bone StartingBone { get; private set; }
+		Vector2 rootOffset;
+		public Vector3 RootOffset { get { return this.rootOffset; } }
+		bool isActive;
+		public bool IsActive { get { return this.isActive; } }
+
+		IEnumerator Start () {
+			if (parentSpaceHelper == null) {
+				parentSpaceHelper = (new GameObject("Parent Space Helper")).transform;
+				#if !FLIPDEBUG
+				parentSpaceHelper.hideFlags = HideFlags.HideInHierarchy;
+				#endif
+			}
+
+			targetSkeletonComponent = GetComponent<SkeletonRenderer>() as ISkeletonAnimation;
+			if (targetSkeletonComponent == null) Debug.LogError("Attached Spine component does not implement ISkeletonAnimation. This script is not compatible.");
+			skeleton = targetSkeletonComponent.Skeleton;
+
+			#if FLIPDEBUG
+			skeleton.flipX = flipXInitially;
+			skeleton.flipY = flipYInitially;
+			#endif
+
+			if (applyOnStart) {
+				yield return null;
+				Apply();
+			}
+		}
+
+		#region API
+		public Rigidbody2D[] RigidbodyArray {
+			get {
+				if (!isActive)
+					return new Rigidbody2D[0];
+
+				var rigidBodies = new Rigidbody2D[boneTable.Count];
+				int i = 0;
+				foreach (Transform t in boneTable.Values) {
+					rigidBodies[i] = t.GetComponent<Rigidbody2D>();
+					i++;
+				}
+
+				return rigidBodies;
+			}
+		}
+
+		public Vector3 EstimatedSkeletonPosition {
+			get { return this.RootRigidbody.position - rootOffset; }
+		}
+
+		/// <summary>Instantiates the ragdoll simulation and applies its transforms to the skeleton.</summary>
+		public void Apply () {
+			isActive = true;
+			mix = 1;
+
+			Bone startingBone = this.StartingBone = skeleton.FindBone(startingBoneName);
+			RecursivelyCreateBoneProxies(startingBone);
+
+			RootRigidbody = boneTable[startingBone].GetComponent<Rigidbody2D>();
+			#if FLIPDEBUG
+			if (!RootRigidbody.isKinematic)
+			#endif
+			RootRigidbody.isKinematic = pinStartBone;
+			RootRigidbody.mass = rootMass;
+			var boneColliders = new List<Collider2D>();
+			foreach (var pair in boneTable) {
+				var b = pair.Key;
+				var t = pair.Value;
+				Transform parentTransform;
+				boneColliders.Add(t.GetComponent<Collider2D>());
+				if (b == startingBone) {
+					ragdollRoot = new GameObject("RagdollRoot").transform;
+					ragdollRoot.SetParent(transform, false);
+					if (b == skeleton.RootBone) { // RagdollRoot is skeleton root.
+						ragdollRoot.localPosition = new Vector3(b.WorldX, b.WorldY, 0);
+						ragdollRoot.localRotation = Quaternion.Euler(0, 0, GetPropagatedRotation(b));
+					} else {
+						ragdollRoot.localPosition = new Vector3(b.Parent.WorldX, b.Parent.WorldY, 0);
+						ragdollRoot.localRotation = Quaternion.Euler(0, 0, GetPropagatedRotation(b.Parent));
+					}
+					parentTransform = ragdollRoot;
+					rootOffset = t.position - transform.position;
+				} else {
+					parentTransform = boneTable[b.Parent];
+				}
+
+				// Add joint and attach to parent.
+				var rbParent = parentTransform.GetComponent<Rigidbody2D>();
+				if (rbParent != null) {
+					var joint = t.gameObject.AddComponent<HingeJoint2D>();
+					joint.connectedBody = rbParent;
+					Vector3 localPos = parentTransform.InverseTransformPoint(t.position);
+					joint.connectedAnchor = localPos;
+
+					joint.GetComponent<Rigidbody2D>().mass = joint.connectedBody.mass * massFalloffFactor;
+					joint.limits = new JointAngleLimits2D {
+						min = -rotationLimit,
+						max = rotationLimit
+					};
+					joint.useLimits = true;
+				}
+			}
+
+			// Ignore collisions among bones.
+			for (int x = 0; x < boneColliders.Count; x++) {
+				for (int y = 0; y < boneColliders.Count; y++) {
+					if (x == y) continue;
+					Physics2D.IgnoreCollision(boneColliders[x], boneColliders[y]);
+				}
+			}
+
+			// Destroy existing override-mode SkeletonUtility bones.
+			var utilityBones = GetComponentsInChildren<SkeletonUtilityBone>();
+			if (utilityBones.Length > 0) {
+				var destroyedUtilityBoneNames = new List<string>();
+				foreach (var ub in utilityBones) {
+					if (ub.mode == SkeletonUtilityBone.Mode.Override) {
+						destroyedUtilityBoneNames.Add(ub.gameObject.name);
+						Destroy(ub.gameObject);
+					}
+				}
+				if (destroyedUtilityBoneNames.Count > 0) {
+					string msg = "Destroyed Utility Bones: ";
+					for (int i = 0; i < destroyedUtilityBoneNames.Count; i++) {
+						msg += destroyedUtilityBoneNames[i];
+						if (i != destroyedUtilityBoneNames.Count - 1) {
+							msg += ",";
+						}
+					}
+					Debug.LogWarning(msg);
+				}
+			}
+
+			// Disable skeleton constraints.
+			if (disableIK) {
+				var ikConstraints = skeleton.IkConstraints;
+				for (int i = 0, n = ikConstraints.Count; i < n; i++)
+					ikConstraints.Items[i].mix = 0;
+			}
+
+			if (disableOtherConstraints) {
+				var transformConstraints = skeleton.transformConstraints;
+				for (int i = 0, n = transformConstraints.Count; i < n; i++) {
+					transformConstraints.Items[i].rotateMix = 0;
+					transformConstraints.Items[i].scaleMix = 0;
+					transformConstraints.Items[i].shearMix = 0;
+					transformConstraints.Items[i].translateMix = 0;
+				}
+
+				var pathConstraints = skeleton.pathConstraints;
+				for (int i = 0, n = pathConstraints.Count; i < n; i++) {
+					pathConstraints.Items[i].rotateMix = 0;
+					pathConstraints.Items[i].translateMix = 0;
+				}
+			}
+
+			targetSkeletonComponent.UpdateWorld += UpdateSpineSkeleton;
+		}
+
+		/// <summary>Transitions the mix value from the current value to a target value.</summary>
+		public Coroutine SmoothMix (float target, float duration) {
+			return StartCoroutine(SmoothMixCoroutine(target, duration));
+		}
+
+		IEnumerator SmoothMixCoroutine (float target, float duration) {
+			float startTime = Time.time;
+			float startMix = mix;
+			while (mix > 0) {
+				mix = Mathf.SmoothStep(startMix, target, (Time.time - startTime) / duration);
+				yield return null;
+			}
+		}
+
+		/// <summary>Set the transform world position while preserving the ragdoll parts world position.</summary>
+		public void SetSkeletonPosition (Vector3 worldPosition) {
+			if (!isActive) {
+				Debug.LogWarning("Can't call SetSkeletonPosition while Ragdoll is not active!");
+				return;
+			}
+
+			Vector3 offset = worldPosition - transform.position;
+			transform.position = worldPosition;
+			foreach (Transform t in boneTable.Values)
+				t.position -= offset;
+
+			UpdateSpineSkeleton(null);
+			skeleton.UpdateWorldTransform();
+		}
+
+		/// <summary>Removes the ragdoll instance and effect from the animated skeleton.</summary>
+		public void Remove () {
+			isActive = false;
+			foreach (var t in boneTable.Values)
+				Destroy(t.gameObject);
+
+			Destroy(ragdollRoot.gameObject);
+			boneTable.Clear();
+			targetSkeletonComponent.UpdateWorld -= UpdateSpineSkeleton;
+		}
+
+		public Rigidbody2D GetRigidbody (string boneName) {
+			var bone = skeleton.FindBone(boneName);
+			return (bone != null && boneTable.ContainsKey(bone)) ? boneTable[bone].GetComponent<Rigidbody2D>() : null;
+		}
+		#endregion
+
+		/// <summary>Generates the ragdoll simulation's Transform and joint setup.</summary>
+		void RecursivelyCreateBoneProxies (Bone b) {
+			string boneName = b.data.name;
+			if (stopBoneNames.Contains(boneName))
+				return;
+
+			var boneGameObject = new GameObject(boneName);
+			boneGameObject.layer = this.colliderLayer;
+			Transform t = boneGameObject.transform;
+			boneTable.Add(b, t);
+
+			t.parent = transform;
+			t.localPosition = new Vector3(b.WorldX, b.WorldY, 0);
+			t.localRotation = Quaternion.Euler(0, 0, b.WorldRotationX - b.shearX);
+			t.localScale = new Vector3(b.WorldScaleX, b.WorldScaleY, 0);
+
+			// MITCH: You left "todo: proper ragdoll branching"
+			var colliders = AttachBoundingBoxRagdollColliders(b, boneGameObject, skeleton);
+			if (colliders.Count == 0) {
+				float length = b.data.length;
+				if (length == 0) {
+					var circle = boneGameObject.AddComponent<CircleCollider2D>();
+					circle.radius = thickness * 0.5f;
+				} else {				
+					var box = boneGameObject.AddComponent<BoxCollider2D>();
+					box.size = new Vector2(length, thickness);
+					box.offset = new Vector2(length * 0.5f, 0); // box.center in UNITY_4
+				}
+			}
+			var rb = boneGameObject.AddComponent<Rigidbody2D>();
+			rb.gravityScale = this.gravityScale;
+
+			#if FLIPDEBUG
+			rb.isKinematic = spawnKinematic;
+			#endif
+
+			foreach (Bone child in b.Children)
+				RecursivelyCreateBoneProxies(child);
+		}
+
+		/// <summary>Performed every skeleton animation update to translate Unity Transforms positions into Spine bone transforms.</summary>
+		void UpdateSpineSkeleton (ISkeletonAnimation animatedSkeleton) {
+			#if FLIPDEBUG
+			if (disableUpdateBones) return;
+			#endif
+
+			bool flipX = skeleton.flipX;
+			bool flipY = skeleton.flipY;
+			bool flipXOR = flipX ^ flipY;
+			bool flipOR = flipX || flipY;
+			var startingBone = this.StartingBone;
+
+			foreach (var pair in boneTable) {
+				var b = pair.Key;
+				var t = pair.Value;
+				bool isStartingBone = (b == startingBone);
+				Transform parentTransform = isStartingBone ? ragdollRoot : boneTable[b.Parent];
+				Vector3 parentTransformWorldPosition = parentTransform.position;
+				Quaternion parentTransformWorldRotation = parentTransform.rotation;
+
+				parentSpaceHelper.position = parentTransformWorldPosition;
+				parentSpaceHelper.rotation = parentTransformWorldRotation;
+				parentSpaceHelper.localScale = parentTransform.localScale;
+
+				Vector3 boneWorldPosition = t.position;
+				Vector3 right = parentSpaceHelper.InverseTransformDirection(t.right);
+
+				Vector3 boneLocalPosition = parentSpaceHelper.InverseTransformPoint(boneWorldPosition);
+				float boneLocalRotation = Mathf.Atan2(right.y, right.x) * Mathf.Rad2Deg;
+				if (flipOR) {
+					if (isStartingBone) {
+						if (flipX) boneLocalPosition.x *= -1f;
+						if (flipY) boneLocalPosition.y *= -1f;
+
+						boneLocalRotation = boneLocalRotation * (flipXOR ? -1f : 1f);
+						if (flipX) boneLocalRotation += 180;
+					} else {
+						if (flipXOR) {
+							boneLocalRotation *= -1f;
+							boneLocalPosition.y *= -1f; // wtf??
+						}
+					}
+				}
+
+				b.x = Mathf.Lerp(b.x, boneLocalPosition.x, mix);
+				b.y = Mathf.Lerp(b.y, boneLocalPosition.y, mix);
+				b.rotation = Mathf.Lerp(b.rotation, boneLocalRotation, mix);
+				b.appliedRotation = Mathf.Lerp(b.appliedRotation, boneLocalRotation, mix);
+			}
+		}
+
+		static List<Collider2D> AttachBoundingBoxRagdollColliders (Bone b, GameObject go, Skeleton skeleton) {
+			const string AttachmentNameMarker = "ragdoll";
+			var colliders = new List<Collider2D>();
+			var skin = skeleton.Skin ?? skeleton.Data.DefaultSkin;
+
+			var attachments = new List<Attachment>();
+			foreach (Slot s in skeleton.Slots) {
+				if (s.bone == b) {
+					skin.FindAttachmentsForSlot(skeleton.Slots.IndexOf(s), attachments);
+					foreach (var a in attachments) {
+						var bbAttachment = a as BoundingBoxAttachment;
+						if (bbAttachment != null) {
+							if (!a.Name.ToLower().Contains(AttachmentNameMarker))
+								continue;
+
+							var bbCollider = SkeletonUtility.AddBoundingBoxAsComponent(bbAttachment, go, false);
+							colliders.Add(bbCollider);
+						}
+					}
+				}
+			}
+
+			return colliders;
+		}
+			
+		static float GetPropagatedRotation (Bone b) {
+			Bone parent = b.Parent;
+			float a = b.AppliedRotation;
+			while (parent != null) {
+				a += parent.AppliedRotation;
+				parent = parent.parent;
+			}
+			return a;
+		}
+
+		static Vector3 FlipScale (bool flipX, bool flipY) {
+			return new Vector3(flipX ? -1f : 1f, flipY ? -1f : 1f, 1f);
+		}
+
+		#if UNITY_EDITOR
+		void OnDrawGizmosSelected () {
+			if (isActive) {
+				Gizmos.DrawWireSphere(transform.position, thickness * 1.2f);
+				Vector3 newTransformPos = RootRigidbody.position - rootOffset;
+				Gizmos.DrawLine(transform.position, newTransformPos);
+				Gizmos.DrawWireSphere(newTransformPos, thickness * 1.2f);
+			}
+		}
+		#endif
+	}
+
 }

+ 261 - 230
spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/Editor/SkeletonGraphicInspector.cs

@@ -1,232 +1,263 @@
-/******************************************************************************
- * 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.
+/******************************************************************************
+ * 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.
  *****************************************************************************/
-#if (UNITY_5_0 || UNITY_5_1 || UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7)
-#define PREUNITY_5_2
-#endif
 
-using UnityEngine;
-using UnityEditor;
-using Spine;
-
-namespace Spine.Unity.Editor {
-
-	[InitializeOnLoad]
-	[CustomEditor(typeof(SkeletonGraphic))]
-	public class SkeletonGraphicInspector : UnityEditor.Editor {
-		SerializedProperty material_, color_;
-		SerializedProperty skeletonDataAsset_, initialSkinName_;
-		SerializedProperty startingAnimation_, startingLoop_, timeScale_, freeze_;
-	#if !PREUNITY_5_2
-		SerializedProperty raycastTarget_;
-
-		SkeletonGraphic thisSkeletonGraphic;
-
-		void OnEnable () {
-			var so = this.serializedObject;
-			thisSkeletonGraphic = target as SkeletonGraphic;
-
-			// MaskableGraphic
-			material_ = so.FindProperty("m_Material");
-			color_ = so.FindProperty("m_Color");
-			raycastTarget_ = so.FindProperty("m_RaycastTarget");
-
-			// SkeletonRenderer
-			skeletonDataAsset_ = so.FindProperty("skeletonDataAsset");
-			initialSkinName_ = so.FindProperty("initialSkinName");
-
-			// SkeletonAnimation
-			startingAnimation_ = so.FindProperty("startingAnimation");
-			startingLoop_ = so.FindProperty("startingLoop");
-			timeScale_ = so.FindProperty("timeScale");
-			freeze_ = so.FindProperty("freeze");
-		}
-
-		public override void OnInspectorGUI () {
-			EditorGUI.BeginChangeCheck();
-
-			EditorGUILayout.PropertyField(skeletonDataAsset_);
-			EditorGUILayout.PropertyField(material_);
-			EditorGUILayout.PropertyField(color_);
-
-			if (thisSkeletonGraphic.skeletonDataAsset == null) {
-				EditorGUILayout.HelpBox("You need to assign a SkeletonDataAsset first.", MessageType.Info);
-				serializedObject.ApplyModifiedProperties();
-				serializedObject.Update();
-				return;
-			}
-
-			EditorGUILayout.Space();
-			EditorGUILayout.PropertyField(initialSkinName_);
-			EditorGUILayout.Space();
-			EditorGUILayout.LabelField("Animation", EditorStyles.boldLabel);
-			EditorGUILayout.PropertyField(startingAnimation_);
-			EditorGUILayout.PropertyField(startingLoop_);
-			EditorGUILayout.PropertyField(timeScale_);
-			EditorGUILayout.Space();
-			EditorGUILayout.PropertyField(freeze_);
-			EditorGUILayout.Space();
-			EditorGUILayout.LabelField("UI", EditorStyles.boldLabel);
-			EditorGUILayout.PropertyField(raycastTarget_);
-
-			bool wasChanged = EditorGUI.EndChangeCheck();
-
-			if (wasChanged) {
-				serializedObject.ApplyModifiedProperties();
-			}
-		}
-
-		#region Menus
-		[MenuItem("CONTEXT/SkeletonGraphic/Match RectTransform with Mesh Bounds")]
-		static void MatchRectTransformWithBounds (MenuCommand command) {
-			var skeletonGraphic = (SkeletonGraphic)command.context;
-			var mesh = skeletonGraphic.SpineMeshGenerator.LastGeneratedMesh;
-
-			mesh.RecalculateBounds();
-			var bounds = mesh.bounds;
-			var size = bounds.size;
-			var center = bounds.center;
-			var p = new Vector2(
-				        0.5f - (center.x / size.x),
-				        0.5f - (center.y / size.y)
-			        );
-
-			skeletonGraphic.rectTransform.sizeDelta = size;
-			skeletonGraphic.rectTransform.pivot = p;
-		}
-
-		[MenuItem("GameObject/Spine/SkeletonGraphic (UnityUI)", false, 15)]
-		static public void SkeletonGraphicCreateMenuItem () {
-			var parentGameObject = Selection.activeObject as GameObject;
-			var parentTransform = parentGameObject == null ? null : parentGameObject.GetComponent<RectTransform>();
-
-			if (parentTransform == null)
-				Debug.LogWarning("Your new SkeletonGraphic will not be visible until it is placed under a Canvas");
-
-			var gameObject = NewSkeletonGraphicGameObject("New SkeletonGraphic");
-			gameObject.transform.SetParent(parentTransform, false);
-			EditorUtility.FocusProjectWindow();
-			Selection.activeObject = gameObject;
-			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;
-		}
-
-		// SpineEditorUtilities.InstantiateDelegate. Used by drag and drop.
-		public static Component SpawnSkeletonGraphicFromDrop (SkeletonDataAsset data) {
-			return InstantiateSkeletonGraphic(data);
-		}
-
-		public static SkeletonGraphic InstantiateSkeletonGraphic (SkeletonDataAsset skeletonDataAsset, string skinName) {
-			return InstantiateSkeletonGraphic(skeletonDataAsset, skeletonDataAsset.GetSkeletonData(true).FindSkin(skinName));
-		}
-
-		public static SkeletonGraphic InstantiateSkeletonGraphic (SkeletonDataAsset skeletonDataAsset, Skin skin = null) {
-			string spineGameObjectName = string.Format("SkeletonGraphic ({0})", skeletonDataAsset.name.Replace("_SkeletonData", ""));
-			var go = NewSkeletonGraphicGameObject(spineGameObjectName);
-			var graphic = go.GetComponent<SkeletonGraphic>();
-			graphic.skeletonDataAsset = skeletonDataAsset;
-
-			SkeletonData data = skeletonDataAsset.GetSkeletonData(true);
-
-			if (data == null) {
-				for (int i = 0; i < skeletonDataAsset.atlasAssets.Length; i++) {
-					string reloadAtlasPath = AssetDatabase.GetAssetPath(skeletonDataAsset.atlasAssets[i]);
-					skeletonDataAsset.atlasAssets[i] = (AtlasAsset)AssetDatabase.LoadAssetAtPath(reloadAtlasPath, typeof(AtlasAsset));
-				}
-
-				data = skeletonDataAsset.GetSkeletonData(true);
-			}
-
-			if (skin == null)
-				skin = data.DefaultSkin;
-
-			if (skin == null)
-				skin = data.Skins.Items[0];
-
-			graphic.Initialize(false);
-			graphic.Skeleton.SetSkin(skin);
-			graphic.initialSkinName = skin.Name;
-			graphic.Skeleton.UpdateWorldTransform();
-			graphic.UpdateMesh();
-
-			return graphic;
-		}
-
-		static GameObject NewSkeletonGraphicGameObject (string gameObjectName) {
-			var go = new GameObject(gameObjectName, typeof(RectTransform), typeof(CanvasRenderer), typeof(SkeletonGraphic));
-			var graphic = go.GetComponent<SkeletonGraphic>();
-			graphic.material = SkeletonGraphicInspector.DefaultSkeletonGraphicMaterial;
-			return go;
-		}
-
-		public static Material DefaultSkeletonGraphicMaterial {
-			get {
-				var guids = AssetDatabase.FindAssets("SkeletonGraphicDefault t:material");
-				if (guids.Length <= 0) return null;
-
-				var firstAssetPath = AssetDatabase.GUIDToAssetPath(guids[0]);
-				if (string.IsNullOrEmpty(firstAssetPath)) return null;
-
-				var firstMaterial = AssetDatabase.LoadAssetAtPath<Material>(firstAssetPath);
-				return firstMaterial;
-			}
-		}
-
-		#endregion
-
-	#endif
-	}
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+#if (UNITY_5_0 || UNITY_5_1 || UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7)
+#define PREUNITY_5_2
+#endif
+
+using UnityEngine;
+using UnityEditor;
+using Spine;
+
+namespace Spine.Unity.Editor {
+
+	[InitializeOnLoad]
+	[CustomEditor(typeof(SkeletonGraphic))]
+	public class SkeletonGraphicInspector : UnityEditor.Editor {
+		SerializedProperty material_, color_;
+		SerializedProperty skeletonDataAsset_, initialSkinName_;
+		SerializedProperty startingAnimation_, startingLoop_, timeScale_, freeze_;
+	#if !PREUNITY_5_2
+		SerializedProperty raycastTarget_;
+
+		SkeletonGraphic thisSkeletonGraphic;
+
+		void OnEnable () {
+			var so = this.serializedObject;
+			thisSkeletonGraphic = target as SkeletonGraphic;
+
+			// MaskableGraphic
+			material_ = so.FindProperty("m_Material");
+			color_ = so.FindProperty("m_Color");
+			raycastTarget_ = so.FindProperty("m_RaycastTarget");
+
+			// SkeletonRenderer
+			skeletonDataAsset_ = so.FindProperty("skeletonDataAsset");
+			initialSkinName_ = so.FindProperty("initialSkinName");
+
+			// SkeletonAnimation
+			startingAnimation_ = so.FindProperty("startingAnimation");
+			startingLoop_ = so.FindProperty("startingLoop");
+			timeScale_ = so.FindProperty("timeScale");
+			freeze_ = so.FindProperty("freeze");
+		}
+
+		public override void OnInspectorGUI () {
+			EditorGUI.BeginChangeCheck();
+
+			EditorGUILayout.PropertyField(skeletonDataAsset_);
+			EditorGUILayout.PropertyField(material_);
+			EditorGUILayout.PropertyField(color_);
+
+			if (thisSkeletonGraphic.skeletonDataAsset == null) {
+				EditorGUILayout.HelpBox("You need to assign a SkeletonDataAsset first.", MessageType.Info);
+				serializedObject.ApplyModifiedProperties();
+				serializedObject.Update();
+				return;
+			}
+
+			EditorGUILayout.Space();
+			EditorGUILayout.PropertyField(initialSkinName_);
+			EditorGUILayout.Space();
+			EditorGUILayout.LabelField("Animation", EditorStyles.boldLabel);
+			EditorGUILayout.PropertyField(startingAnimation_);
+			EditorGUILayout.PropertyField(startingLoop_);
+			EditorGUILayout.PropertyField(timeScale_);
+			EditorGUILayout.Space();
+			EditorGUILayout.PropertyField(freeze_);
+			EditorGUILayout.Space();
+			EditorGUILayout.LabelField("UI", EditorStyles.boldLabel);
+			EditorGUILayout.PropertyField(raycastTarget_);
+
+			bool wasChanged = EditorGUI.EndChangeCheck();
+
+			if (wasChanged) {
+				serializedObject.ApplyModifiedProperties();
+			}
+		}
+
+		#region Menus
+		[MenuItem("CONTEXT/SkeletonGraphic/Match RectTransform with Mesh Bounds")]
+		static void MatchRectTransformWithBounds (MenuCommand command) {
+			var skeletonGraphic = (SkeletonGraphic)command.context;
+			var mesh = skeletonGraphic.SpineMeshGenerator.LastGeneratedMesh;
+
+			mesh.RecalculateBounds();
+			var bounds = mesh.bounds;
+			var size = bounds.size;
+			var center = bounds.center;
+			var p = new Vector2(
+				        0.5f - (center.x / size.x),
+				        0.5f - (center.y / size.y)
+			        );
+
+			skeletonGraphic.rectTransform.sizeDelta = size;
+			skeletonGraphic.rectTransform.pivot = p;
+		}
+
+		[MenuItem("GameObject/Spine/SkeletonGraphic (UnityUI)", false, 15)]
+		static public void SkeletonGraphicCreateMenuItem () {
+			var parentGameObject = Selection.activeObject as GameObject;
+			var parentTransform = parentGameObject == null ? null : parentGameObject.GetComponent<RectTransform>();
+
+			if (parentTransform == null)
+				Debug.LogWarning("Your new SkeletonGraphic will not be visible until it is placed under a Canvas");
+
+			var gameObject = NewSkeletonGraphicGameObject("New SkeletonGraphic");
+			gameObject.transform.SetParent(parentTransform, false);
+			EditorUtility.FocusProjectWindow();
+			Selection.activeObject = gameObject;
+			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;
+		}
+
+		// SpineEditorUtilities.InstantiateDelegate. Used by drag and drop.
+		public static Component SpawnSkeletonGraphicFromDrop (SkeletonDataAsset data) {
+			return InstantiateSkeletonGraphic(data);
+		}
+
+		public static SkeletonGraphic InstantiateSkeletonGraphic (SkeletonDataAsset skeletonDataAsset, string skinName) {
+			return InstantiateSkeletonGraphic(skeletonDataAsset, skeletonDataAsset.GetSkeletonData(true).FindSkin(skinName));
+		}
+
+		public static SkeletonGraphic InstantiateSkeletonGraphic (SkeletonDataAsset skeletonDataAsset, Skin skin = null) {
+			string spineGameObjectName = string.Format("SkeletonGraphic ({0})", skeletonDataAsset.name.Replace("_SkeletonData", ""));
+			var go = NewSkeletonGraphicGameObject(spineGameObjectName);
+			var graphic = go.GetComponent<SkeletonGraphic>();
+			graphic.skeletonDataAsset = skeletonDataAsset;
+
+			SkeletonData data = skeletonDataAsset.GetSkeletonData(true);
+
+			if (data == null) {
+				for (int i = 0; i < skeletonDataAsset.atlasAssets.Length; i++) {
+					string reloadAtlasPath = AssetDatabase.GetAssetPath(skeletonDataAsset.atlasAssets[i]);
+					skeletonDataAsset.atlasAssets[i] = (AtlasAsset)AssetDatabase.LoadAssetAtPath(reloadAtlasPath, typeof(AtlasAsset));
+				}
+
+				data = skeletonDataAsset.GetSkeletonData(true);
+			}
+
+			if (skin == null)
+				skin = data.DefaultSkin;
+
+			if (skin == null)
+				skin = data.Skins.Items[0];
+
+			graphic.Initialize(false);
+			graphic.Skeleton.SetSkin(skin);
+			graphic.initialSkinName = skin.Name;
+			graphic.Skeleton.UpdateWorldTransform();
+			graphic.UpdateMesh();
+
+			return graphic;
+		}
+
+		static GameObject NewSkeletonGraphicGameObject (string gameObjectName) {
+			var go = new GameObject(gameObjectName, typeof(RectTransform), typeof(CanvasRenderer), typeof(SkeletonGraphic));
+			var graphic = go.GetComponent<SkeletonGraphic>();
+			graphic.material = SkeletonGraphicInspector.DefaultSkeletonGraphicMaterial;
+			return go;
+		}
+
+		public static Material DefaultSkeletonGraphicMaterial {
+			get {
+				var guids = AssetDatabase.FindAssets("SkeletonGraphicDefault t:material");
+				if (guids.Length <= 0) return null;
+
+				var firstAssetPath = AssetDatabase.GUIDToAssetPath(guids[0]);
+				if (string.IsNullOrEmpty(firstAssetPath)) return null;
+
+				var firstMaterial = AssetDatabase.LoadAssetAtPath<Material>(firstAssetPath);
+				return firstMaterial;
+			}
+		}
+
+		#endregion
+
+	#endif
+	}
+}

+ 249 - 218
spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/SkeletonGraphic.cs

@@ -1,220 +1,251 @@
-/******************************************************************************
- * 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.
+/******************************************************************************
+ * 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.
  *****************************************************************************/
-#if (UNITY_5_0 || UNITY_5_1 || UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7)
-#define PREUNITY_5_2
-#endif
 
-using UnityEngine;
-using UnityEngine.UI;
-using Spine;
-
-namespace Spine.Unity {
-	[ExecuteInEditMode, RequireComponent(typeof(CanvasRenderer), typeof(RectTransform)), DisallowMultipleComponent]
-	[AddComponentMenu("Spine/SkeletonGraphic (Unity UI Canvas)")]
-	public class SkeletonGraphic : MaskableGraphic, ISkeletonComponent, IAnimationStateComponent, ISkeletonAnimation {
-
-		#region Inspector
-		public SkeletonDataAsset skeletonDataAsset;
-		public SkeletonDataAsset SkeletonDataAsset { get { return skeletonDataAsset; } }
-
-		[SpineSkin(dataField:"skeletonDataAsset")]
-		public string initialSkinName = "default";
-
-		[SpineAnimation(dataField:"skeletonDataAsset")]
-		public string startingAnimation;
-		public bool startingLoop;
-		public float timeScale = 1f;
-		public bool freeze;
-
-		#if UNITY_EDITOR
-		protected override void OnValidate () {
-			// This handles Scene View preview.
-			base.OnValidate ();
-			#if !PREUNITY_5_2
-			if (this.IsValid) {
-				if (skeletonDataAsset == null) {
-					Clear();
-					startingAnimation = "";
-				} else if (skeletonDataAsset.GetSkeletonData(true) != skeleton.data) {
-					Clear();
-					Initialize(true);
-					startingAnimation = "";
-					if (skeletonDataAsset.atlasAssets.Length > 1 || skeletonDataAsset.atlasAssets[0].materials.Length > 1)
-						Debug.LogError("Unity UI does not support multiple textures per Renderer. Your skeleton will not be rendered correctly. Recommend using SkeletonAnimation instead. This requires the use of a Screen space camera canvas.");
-				} else {
-					if (freeze) return;
-					skeleton.SetToSetupPose();
-					if (!string.IsNullOrEmpty(startingAnimation))
-						skeleton.PoseWithAnimation(startingAnimation, 0f, false);
-				}
-			} else {
-				if (skeletonDataAsset != null)
-					Initialize(true);
-			}
-			#else
-			Debug.LogWarning("SkeletonGraphic requres Unity 5.2 or higher.\nUnityEngine.UI 5.1 and below does not accept meshes and can't be used to render Spine skeletons. You may delete the SkeletonGraphic folder under `Modules` if you want to exclude it from your project." );
-			#endif
-				
-		}
-
-		protected override void Reset () {
-			base.Reset();
-			if (material == null || material.shader != Shader.Find("Spine/SkeletonGraphic (Premultiply Alpha)"))
-				Debug.LogWarning("SkeletonGraphic works best with the SkeletonGraphic material.");			
-		}
-		#endif
-		#endregion
-
-		#if !PREUNITY_5_2
-		#region Internals
-		// This is used by the UI system to determine what to put in the MaterialPropertyBlock.
-		public override Texture mainTexture {
-			get { 
-				// Fail loudly when incorrectly set up.
-				return skeletonDataAsset == null ? null : skeletonDataAsset.atlasAssets[0].materials[0].mainTexture;
-			}
-		}
-
-		protected override void Awake () {
-			base.Awake ();
-			if (!this.IsValid) {
-				Initialize(false);
-				Rebuild(CanvasUpdate.PreRender);
-			}
-		}
-
-		public override void Rebuild (CanvasUpdate update) {
-			base.Rebuild(update);
-			if (canvasRenderer.cull) return;
-			if (update == CanvasUpdate.PreRender) UpdateMesh();
-		}
-
-		public virtual void Update () {
-			if (freeze) return;
-			Update(Time.deltaTime);
-		}
-
-		public virtual void Update (float deltaTime) {
-			if (!this.IsValid) return;
-
-			deltaTime *= timeScale;
-			skeleton.Update(deltaTime);
-			state.Update(deltaTime);
-			state.Apply(skeleton);
-
-			if (UpdateLocal != null) UpdateLocal(this);
-
-			skeleton.UpdateWorldTransform();
-
-			if (UpdateWorld != null) { 
-				UpdateWorld(this);
-				skeleton.UpdateWorldTransform();
-			}
-
-			if (UpdateComplete != null) UpdateComplete(this);
-		}
-
-		public void LateUpdate () {
-			if (freeze) return;
-			//this.SetVerticesDirty(); // Which is better?
-			UpdateMesh();
-		}
-		#endregion
-
-		#region API
-		protected Skeleton skeleton;
-		public Skeleton Skeleton { get { return skeleton; } }
-		public SkeletonData SkeletonData { get { return skeleton == null ? null : skeleton.data; } }
-		public bool IsValid { get { return skeleton != null; } }
-
-		protected Spine.AnimationState state;
-		public Spine.AnimationState AnimationState { get { return state; } }
-
-		// This is any object that can give you a mesh when you give it a skeleton to render.
-		protected Spine.Unity.MeshGeneration.ISimpleMeshGenerator spineMeshGenerator;
-		public Spine.Unity.MeshGeneration.ISimpleMeshGenerator SpineMeshGenerator { get { return this.spineMeshGenerator; } }
-
-		public event UpdateBonesDelegate UpdateLocal;
-		public event UpdateBonesDelegate UpdateWorld;
-		public event UpdateBonesDelegate UpdateComplete;
-
-		public void Clear () {
-			skeleton = null;
-			canvasRenderer.Clear();
-		}
-
-		public void Initialize (bool overwrite) {
-			if (this.IsValid && !overwrite) return;
-
-			// Make sure none of the stuff is null
-			if (this.skeletonDataAsset == null) return;
-			var skeletonData = this.skeletonDataAsset.GetSkeletonData(false);
-			if (skeletonData == null) return;
-
-			if (skeletonDataAsset.atlasAssets.Length <= 0 || skeletonDataAsset.atlasAssets[0].materials.Length <= 0) return;
-
-			this.state = new Spine.AnimationState(skeletonDataAsset.GetAnimationStateData());
-			if (state == null) {
-				Clear();
-				return;
-			}
-
-			this.skeleton = new Skeleton(skeletonData);
-			this.spineMeshGenerator = new Spine.Unity.MeshGeneration.ArraysSimpleMeshGenerator(); // You can switch this out with any other implementer of Spine.Unity.MeshGeneration.ISimpleMeshGenerator
-			this.spineMeshGenerator.PremultiplyVertexColors = true;
-
-			// Set the initial Skin and Animation
-			if (!string.IsNullOrEmpty(initialSkinName))
-				skeleton.SetSkin(initialSkinName);
-
-			if (!string.IsNullOrEmpty(startingAnimation))
-				state.SetAnimation(0, startingAnimation, startingLoop);
-		}
-
-		public void UpdateMesh () {
-			if (this.IsValid) {
-				skeleton.SetColor(this.color);
-				if (canvas != null)
-					spineMeshGenerator.Scale = canvas.referencePixelsPerUnit; //JOHN: left a todo: move this to a listener to of the canvas?
-
-				canvasRenderer.SetMesh(spineMeshGenerator.GenerateMesh(skeleton));
-				//this.UpdateMaterial(); // TODO: This allocates memory.
-			}
-		}
-		#endregion
-		#else
-		public Skeleton Skeleton { get { return null; } }
-		public AnimationState AnimationState { get { return null; } }
-		public event UpdateBonesDelegate UpdateLocal, UpdateWorld, UpdateComplete;
-		public void LateUpdate () { }
-		#endif
-	}
-}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+#if (UNITY_5_0 || UNITY_5_1 || UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7)
+#define PREUNITY_5_2
+#endif
+
+using UnityEngine;
+using UnityEngine.UI;
+using Spine;
+
+namespace Spine.Unity {
+	[ExecuteInEditMode, RequireComponent(typeof(CanvasRenderer), typeof(RectTransform)), DisallowMultipleComponent]
+	[AddComponentMenu("Spine/SkeletonGraphic (Unity UI Canvas)")]
+	public class SkeletonGraphic : MaskableGraphic, ISkeletonComponent, IAnimationStateComponent, ISkeletonAnimation {
+
+		#region Inspector
+		public SkeletonDataAsset skeletonDataAsset;
+		public SkeletonDataAsset SkeletonDataAsset { get { return skeletonDataAsset; } }
+
+		[SpineSkin(dataField:"skeletonDataAsset")]
+		public string initialSkinName = "default";
+
+		[SpineAnimation(dataField:"skeletonDataAsset")]
+		public string startingAnimation;
+		public bool startingLoop;
+		public float timeScale = 1f;
+		public bool freeze;
+
+		#if UNITY_EDITOR
+		protected override void OnValidate () {
+			// This handles Scene View preview.
+			base.OnValidate ();
+			#if !PREUNITY_5_2
+			if (this.IsValid) {
+				if (skeletonDataAsset == null) {
+					Clear();
+					startingAnimation = "";
+				} else if (skeletonDataAsset.GetSkeletonData(true) != skeleton.data) {
+					Clear();
+					Initialize(true);
+					startingAnimation = "";
+					if (skeletonDataAsset.atlasAssets.Length > 1 || skeletonDataAsset.atlasAssets[0].materials.Length > 1)
+						Debug.LogError("Unity UI does not support multiple textures per Renderer. Your skeleton will not be rendered correctly. Recommend using SkeletonAnimation instead. This requires the use of a Screen space camera canvas.");
+				} else {
+					if (freeze) return;
+					skeleton.SetToSetupPose();
+					if (!string.IsNullOrEmpty(startingAnimation))
+						skeleton.PoseWithAnimation(startingAnimation, 0f, false);
+				}
+			} else {
+				if (skeletonDataAsset != null)
+					Initialize(true);
+			}
+			#else
+			Debug.LogWarning("SkeletonGraphic requres Unity 5.2 or higher.\nUnityEngine.UI 5.1 and below does not accept meshes and can't be used to render Spine skeletons. You may delete the SkeletonGraphic folder under `Modules` if you want to exclude it from your project." );
+			#endif
+				
+		}
+
+		protected override void Reset () {
+			base.Reset();
+			if (material == null || material.shader != Shader.Find("Spine/SkeletonGraphic (Premultiply Alpha)"))
+				Debug.LogWarning("SkeletonGraphic works best with the SkeletonGraphic material.");			
+		}
+		#endif
+		#endregion
+
+		#if !PREUNITY_5_2
+		#region Internals
+		// This is used by the UI system to determine what to put in the MaterialPropertyBlock.
+		public override Texture mainTexture {
+			get { 
+				// Fail loudly when incorrectly set up.
+				return skeletonDataAsset == null ? null : skeletonDataAsset.atlasAssets[0].materials[0].mainTexture;
+			}
+		}
+
+		protected override void Awake () {
+			base.Awake ();
+			if (!this.IsValid) {
+				Initialize(false);
+				Rebuild(CanvasUpdate.PreRender);
+			}
+		}
+
+		public override void Rebuild (CanvasUpdate update) {
+			base.Rebuild(update);
+			if (canvasRenderer.cull) return;
+			if (update == CanvasUpdate.PreRender) UpdateMesh();
+		}
+
+		public virtual void Update () {
+			if (freeze) return;
+			Update(Time.deltaTime);
+		}
+
+		public virtual void Update (float deltaTime) {
+			if (!this.IsValid) return;
+
+			deltaTime *= timeScale;
+			skeleton.Update(deltaTime);
+			state.Update(deltaTime);
+			state.Apply(skeleton);
+
+			if (UpdateLocal != null) UpdateLocal(this);
+
+			skeleton.UpdateWorldTransform();
+
+			if (UpdateWorld != null) { 
+				UpdateWorld(this);
+				skeleton.UpdateWorldTransform();
+			}
+
+			if (UpdateComplete != null) UpdateComplete(this);
+		}
+
+		public void LateUpdate () {
+			if (freeze) return;
+			//this.SetVerticesDirty(); // Which is better?
+			UpdateMesh();
+		}
+		#endregion
+
+		#region API
+		protected Skeleton skeleton;
+		public Skeleton Skeleton { get { return skeleton; } }
+		public SkeletonData SkeletonData { get { return skeleton == null ? null : skeleton.data; } }
+		public bool IsValid { get { return skeleton != null; } }
+
+		protected Spine.AnimationState state;
+		public Spine.AnimationState AnimationState { get { return state; } }
+
+		// This is any object that can give you a mesh when you give it a skeleton to render.
+		protected Spine.Unity.MeshGeneration.ISimpleMeshGenerator spineMeshGenerator;
+		public Spine.Unity.MeshGeneration.ISimpleMeshGenerator SpineMeshGenerator { get { return this.spineMeshGenerator; } }
+
+		public event UpdateBonesDelegate UpdateLocal;
+		public event UpdateBonesDelegate UpdateWorld;
+		public event UpdateBonesDelegate UpdateComplete;
+
+		public void Clear () {
+			skeleton = null;
+			canvasRenderer.Clear();
+		}
+
+		public void Initialize (bool overwrite) {
+			if (this.IsValid && !overwrite) return;
+
+			// Make sure none of the stuff is null
+			if (this.skeletonDataAsset == null) return;
+			var skeletonData = this.skeletonDataAsset.GetSkeletonData(false);
+			if (skeletonData == null) return;
+
+			if (skeletonDataAsset.atlasAssets.Length <= 0 || skeletonDataAsset.atlasAssets[0].materials.Length <= 0) return;
+
+			this.state = new Spine.AnimationState(skeletonDataAsset.GetAnimationStateData());
+			if (state == null) {
+				Clear();
+				return;
+			}
+
+			this.skeleton = new Skeleton(skeletonData);
+			this.spineMeshGenerator = new Spine.Unity.MeshGeneration.ArraysSimpleMeshGenerator(); // You can switch this out with any other implementer of Spine.Unity.MeshGeneration.ISimpleMeshGenerator
+			this.spineMeshGenerator.PremultiplyVertexColors = true;
+
+			// Set the initial Skin and Animation
+			if (!string.IsNullOrEmpty(initialSkinName))
+				skeleton.SetSkin(initialSkinName);
+
+			if (!string.IsNullOrEmpty(startingAnimation))
+				state.SetAnimation(0, startingAnimation, startingLoop);
+		}
+
+		public void UpdateMesh () {
+			if (this.IsValid) {
+				skeleton.SetColor(this.color);
+				if (canvas != null)
+					spineMeshGenerator.Scale = canvas.referencePixelsPerUnit; //JOHN: left a todo: move this to a listener to of the canvas?
+
+				canvasRenderer.SetMesh(spineMeshGenerator.GenerateMesh(skeleton));
+				//this.UpdateMaterial(); // TODO: This allocates memory.
+			}
+		}
+		#endregion
+		#else
+		public Skeleton Skeleton { get { return null; } }
+		public AnimationState AnimationState { get { return null; } }
+		public event UpdateBonesDelegate UpdateLocal, UpdateWorld, UpdateComplete;
+		public void LateUpdate () { }
+		#endif
+	}
+}

+ 77 - 46
spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonPartsRendererInspector.cs

@@ -1,49 +1,80 @@
-/******************************************************************************
- * 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.
+/******************************************************************************
+ * 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 UnityEditor;
-using Spine.Unity.Editor;
-
-namespace Spine.Unity.Modules {
-	[CustomEditor(typeof(SkeletonPartsRenderer))]
-	public class SkeletonRenderPartInspector : UnityEditor.Editor {
-		SpineInspectorUtility.SerializedSortingProperties sortingProperties;
-
-		void OnEnable () {			
-			sortingProperties = new SpineInspectorUtility.SerializedSortingProperties((target as Component).GetComponent<MeshRenderer>());
-		}
-
-		public override void OnInspectorGUI () {
-			SpineInspectorUtility.SortingPropertyFields(sortingProperties, true);
-		}
-	}
 
+/******************************************************************************
+ * 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 UnityEditor;
+using Spine.Unity.Editor;
+
+namespace Spine.Unity.Modules {
+	[CustomEditor(typeof(SkeletonPartsRenderer))]
+	public class SkeletonRenderPartInspector : UnityEditor.Editor {
+		SpineInspectorUtility.SerializedSortingProperties sortingProperties;
+
+		void OnEnable () {			
+			sortingProperties = new SpineInspectorUtility.SerializedSortingProperties((target as Component).GetComponent<MeshRenderer>());
+		}
+
+		public override void OnInspectorGUI () {
+			SpineInspectorUtility.SortingPropertyFields(sortingProperties, true);
+		}
+	}
+
 }

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

@@ -1,283 +1,314 @@
-/******************************************************************************
- * 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.
+/******************************************************************************
+ * 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 UnityEditor;
 
-using Spine.Unity;
-using Spine.Unity.Editor;
-
-namespace Spine.Unity.Modules {
-	
-	[CustomEditor(typeof(SkeletonRenderSeparator))]
-	public class SkeletonRenderSeparatorInspector : UnityEditor.Editor {
-		SkeletonRenderSeparator component;
-
-		// Properties
-		SerializedProperty skeletonRenderer_, copyPropertyBlock_, copyMeshRendererFlags_, partsRenderers_;
-		static bool partsRenderersExpanded = false;
-
-		// For separator field.
-		SerializedObject skeletonRendererSerializedObject;
-		SerializedProperty separatorNamesProp;
-		static bool skeletonRendererExpanded = true;
-		bool slotsReapplyRequired = false;
-
-		void OnEnable () {
-			if (component == null)
-				component = target as SkeletonRenderSeparator;
-
-			skeletonRenderer_ = serializedObject.FindProperty("skeletonRenderer");
-			copyPropertyBlock_ = serializedObject.FindProperty("copyPropertyBlock");
-			copyMeshRendererFlags_ = serializedObject.FindProperty("copyMeshRendererFlags");
-
-			var partsRenderers = component.partsRenderers;
-			partsRenderers_ = serializedObject.FindProperty("partsRenderers");
-			partsRenderers_.isExpanded = partsRenderersExpanded ||	// last state
-				partsRenderers.Contains(null) ||	// null items found
-				partsRenderers.Count < 1 ||			// no parts renderers
-				(skeletonRenderer_.objectReferenceValue != null && SkeletonRendererSeparatorCount + 1 > partsRenderers.Count); // not enough parts renderers
-		}
-
-		int SkeletonRendererSeparatorCount {
-			get {
-				if (Application.isPlaying) {
-					return component.SkeletonRenderer.separatorSlots.Count;
-				} else {
-					return separatorNamesProp == null ? 0 : separatorNamesProp.arraySize;
-				}
-			}
-		}
-
-		public override void OnInspectorGUI () {
-			//JOHN: left todo: Add Undo support
-			var componentRenderers = component.partsRenderers;
-			int totalParts;
-
-			bool componentEnabled = component.enabled;
-			bool checkBox = EditorGUILayout.Toggle("Enable Separator", componentEnabled);
-			if (checkBox != componentEnabled) {
-				component.enabled = checkBox;
-			}
-
-			EditorGUILayout.PropertyField(copyPropertyBlock_);
-			EditorGUILayout.PropertyField(copyMeshRendererFlags_);
-
-			// SkeletonRenderer Box
-			using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) {
-				// Fancy SkeletonRenderer foldout reference field
-				{
-					EditorGUI.indentLevel++;
-					EditorGUI.BeginChangeCheck();
-					var foldoutSkeletonRendererRect = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight);
-					EditorGUI.PropertyField(foldoutSkeletonRendererRect, skeletonRenderer_);
-					if (EditorGUI.EndChangeCheck())
-						serializedObject.ApplyModifiedProperties();
-					if (component.SkeletonRenderer != null) {
-						skeletonRendererExpanded = EditorGUI.Foldout(foldoutSkeletonRendererRect, skeletonRendererExpanded, "");
-					}
-					EditorGUI.indentLevel--;
-				}
-
-				int separatorCount = 0;
-				EditorGUI.BeginChangeCheck();
-				if (component.SkeletonRenderer != null) {
-					// Separators from SkeletonRenderer
-					{
-						bool skeletonRendererMismatch = skeletonRendererSerializedObject != null && skeletonRendererSerializedObject.targetObject != component.SkeletonRenderer;
-						if (separatorNamesProp == null || skeletonRendererMismatch) {
-							if (component.SkeletonRenderer != null) {
-								skeletonRendererSerializedObject = new SerializedObject(component.SkeletonRenderer);
-								separatorNamesProp = skeletonRendererSerializedObject.FindProperty("separatorSlotNames");
-								separatorNamesProp.isExpanded = true;
-							}
-						}
-							
-						if (separatorNamesProp != null) {
-							if (skeletonRendererExpanded) {
-								EditorGUI.indentLevel++;
-								SkeletonRendererInspector.SeparatorsField(separatorNamesProp);
-								EditorGUI.indentLevel--;
-							}
-							separatorCount = this.SkeletonRendererSeparatorCount;
-						}
-					}
-
-					if (SkeletonRendererSeparatorCount == 0) {
-						EditorGUILayout.HelpBox("Separators are empty. Change the size to 1 and choose a slot if you want the render to be separated.", MessageType.Info);
-					}
-				}
-
-				if (EditorGUI.EndChangeCheck()) {
-					skeletonRendererSerializedObject.ApplyModifiedProperties();
-
-					if (!Application.isPlaying)
-						slotsReapplyRequired = true;
-				}
-					
-
-				totalParts = separatorCount + 1;
-				var counterStyle = skeletonRendererExpanded ? EditorStyles.label : EditorStyles.miniLabel;
-				EditorGUILayout.LabelField(string.Format("{0}: separates into {1}.", SpineInspectorUtility.Pluralize(separatorCount, "separator", "separators"), SpineInspectorUtility.Pluralize(totalParts, "part", "parts") ), counterStyle);
-			}
-
-			// Parts renderers
-			using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) {
-				EditorGUI.indentLevel++;
-				EditorGUILayout.PropertyField(this.partsRenderers_, true);
-				EditorGUI.indentLevel--;
-
-				// Null items warning
-				bool nullItemsFound = componentRenderers.Contains(null);
-				if (nullItemsFound)
-					EditorGUILayout.HelpBox("Some items in the parts renderers list are null and may cause problems.\n\nYou can right-click on that element and choose 'Delete Array Element' to remove it.", MessageType.Warning);
-
-				// (Button) Match Separators count
-				if (separatorNamesProp != null) {
-					int currentRenderers = 0;
-					foreach (var r in componentRenderers) {
-						if (r != null)
-							currentRenderers++;
-					}
-					int extraRenderersNeeded = totalParts - currentRenderers;
-					if (component.enabled && component.SkeletonRenderer != null && extraRenderersNeeded > 0) {
-						EditorGUILayout.HelpBox(string.Format("Insufficient parts renderers. Some parts will not be rendered."), MessageType.Warning);
-						string addMissingLabel = string.Format("Add the missing renderer{1} ({0}) ", extraRenderersNeeded, SpineInspectorUtility.PluralThenS(extraRenderersNeeded));
-						if (GUILayout.Button(addMissingLabel, GUILayout.Height(40f))) {
-							AddPartsRenderer(extraRenderersNeeded);
-							DetectOrphanedPartsRenderers(component);
-						}
-					}
-				}
-					
-				if (partsRenderers_.isExpanded != partsRenderersExpanded) partsRenderersExpanded = partsRenderers_.isExpanded;
-				if (partsRenderers_.isExpanded) {
-					using (new EditorGUILayout.HorizontalScope()) {
-						// (Button) Destroy Renderers button
-						if (componentRenderers.Count > 0) {
-							if (GUILayout.Button("Clear Parts Renderers")) {
-								// Do you really want to destroy all?
-								if (EditorUtility.DisplayDialog("Destroy Renderers", "Do you really want to destroy all the Parts Renderer GameObjects in the list? (Undo will not work.)", "Destroy", "Cancel")) {						
-									foreach (var r in componentRenderers) {
-										if (r != null)
-											DestroyImmediate(r.gameObject, allowDestroyingAssets: false);
-									}
-									componentRenderers.Clear();
-									// Do you also want to destroy orphans? (You monster.)
-									DetectOrphanedPartsRenderers(component);
-								}
-							}
-						}
-
-						// (Button) Add Part Renderer button
-						if (GUILayout.Button("Add Parts Renderer"))
-							AddPartsRenderer(1);				
-					}
-				}
-			}
-
-			serializedObject.ApplyModifiedProperties();
-
-			if (slotsReapplyRequired && UnityEngine.Event.current.type == EventType.Repaint) {
-				SkeletonRendererInspector.ReapplySeparatorSlotNames(component.SkeletonRenderer);
-				component.SkeletonRenderer.LateUpdate();
-				SceneView.RepaintAll();
-				slotsReapplyRequired = false;
-			}
-		}
-
-		public void AddPartsRenderer (int count) {
-			var componentRenderers = component.partsRenderers;
-			bool emptyFound = componentRenderers.Contains(null);
-			if (emptyFound) {
-				bool userClearEntries = EditorUtility.DisplayDialog("Empty entries found", "Null entries found. Do you want to remove null entries before adding the new renderer? ", "Clear Empty Entries", "Don't Clear");
-				if (userClearEntries) componentRenderers.RemoveAll(x => x == null);
-			}
-
-			for (int i = 0; i < count; i++) {
-				int index = componentRenderers.Count;
-				var smr = SkeletonPartsRenderer.NewPartsRendererGameObject(component.transform, index.ToString());
-				componentRenderers.Add(smr);
-				EditorGUIUtility.PingObject(smr);
-
-				// increment renderer sorting order.
-				if (index == 0) continue;
-				var prev = componentRenderers[index - 1]; if (prev == null) continue;
-
-				var prevMeshRenderer = prev.GetComponent<MeshRenderer>();
-				var currentMeshRenderer = smr.GetComponent<MeshRenderer>();
-				if (prevMeshRenderer == null || currentMeshRenderer == null) continue;
-
-				int prevSortingLayer = prevMeshRenderer.sortingLayerID;
-				int prevSortingOrder = prevMeshRenderer.sortingOrder;
-				currentMeshRenderer.sortingLayerID = prevSortingLayer;
-				currentMeshRenderer.sortingOrder = prevSortingOrder + SkeletonRenderSeparator.DefaultSortingOrderIncrement;
-			}
-
-		}
-
-		/// <summary>Detects orphaned parts renderers and offers to delete them.</summary>
-		public void DetectOrphanedPartsRenderers (SkeletonRenderSeparator component) {
-			var children = component.GetComponentsInChildren<SkeletonPartsRenderer>();
-
-			var orphans = new System.Collections.Generic.List<SkeletonPartsRenderer>();
-			foreach (var r in children) {
-				if (!component.partsRenderers.Contains(r))
-					orphans.Add(r);
-			}
-
-			if (orphans.Count > 0) {
-				if (EditorUtility.DisplayDialog("Destroy Submesh Renderers", "Unassigned renderers were found. Do you want to delete them? (These may belong to another Render Separator in the same hierarchy. If you don't have another Render Separator component in the children of this GameObject, it's likely safe to delete. Warning: This operation cannot be undone.)", "Delete", "Cancel")) {
-					foreach (var o in orphans) {
-						DestroyImmediate(o.gameObject, allowDestroyingAssets: false);
-					}
-				}
-			}
-		}
-
-		#region SkeletonRenderer Context Menu Item
-		[MenuItem ("CONTEXT/SkeletonRenderer/Add Skeleton Render Separator")]
-		static void AddRenderSeparatorComponent (MenuCommand cmd) {
-			var skeletonRenderer = cmd.context as SkeletonRenderer;
-			skeletonRenderer.gameObject.AddComponent<SkeletonRenderSeparator>();
-		}
-
-		// Validate
-		[MenuItem ("CONTEXT/SkeletonRenderer/Add Skeleton Render Separator", true)]
-		static bool ValidateAddRenderSeparatorComponent (MenuCommand cmd) {
-			var skeletonRenderer = cmd.context as SkeletonRenderer;
-			var separator = skeletonRenderer.GetComponent<SkeletonRenderSeparator>();
-			bool separatorNotOnObject = separator == null;
-			return separatorNotOnObject;
-		}
-		#endregion
-
-	}
+/******************************************************************************
+ * 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 UnityEditor;
+
+using Spine.Unity;
+using Spine.Unity.Editor;
+
+namespace Spine.Unity.Modules {
+	
+	[CustomEditor(typeof(SkeletonRenderSeparator))]
+	public class SkeletonRenderSeparatorInspector : UnityEditor.Editor {
+		SkeletonRenderSeparator component;
+
+		// Properties
+		SerializedProperty skeletonRenderer_, copyPropertyBlock_, copyMeshRendererFlags_, partsRenderers_;
+		static bool partsRenderersExpanded = false;
+
+		// For separator field.
+		SerializedObject skeletonRendererSerializedObject;
+		SerializedProperty separatorNamesProp;
+		static bool skeletonRendererExpanded = true;
+		bool slotsReapplyRequired = false;
+
+		void OnEnable () {
+			if (component == null)
+				component = target as SkeletonRenderSeparator;
+
+			skeletonRenderer_ = serializedObject.FindProperty("skeletonRenderer");
+			copyPropertyBlock_ = serializedObject.FindProperty("copyPropertyBlock");
+			copyMeshRendererFlags_ = serializedObject.FindProperty("copyMeshRendererFlags");
+
+			var partsRenderers = component.partsRenderers;
+			partsRenderers_ = serializedObject.FindProperty("partsRenderers");
+			partsRenderers_.isExpanded = partsRenderersExpanded ||	// last state
+				partsRenderers.Contains(null) ||	// null items found
+				partsRenderers.Count < 1 ||			// no parts renderers
+				(skeletonRenderer_.objectReferenceValue != null && SkeletonRendererSeparatorCount + 1 > partsRenderers.Count); // not enough parts renderers
+		}
+
+		int SkeletonRendererSeparatorCount {
+			get {
+				if (Application.isPlaying) {
+					return component.SkeletonRenderer.separatorSlots.Count;
+				} else {
+					return separatorNamesProp == null ? 0 : separatorNamesProp.arraySize;
+				}
+			}
+		}
+
+		public override void OnInspectorGUI () {
+			//JOHN: left todo: Add Undo support
+			var componentRenderers = component.partsRenderers;
+			int totalParts;
+
+			bool componentEnabled = component.enabled;
+			bool checkBox = EditorGUILayout.Toggle("Enable Separator", componentEnabled);
+			if (checkBox != componentEnabled) {
+				component.enabled = checkBox;
+			}
+
+			EditorGUILayout.PropertyField(copyPropertyBlock_);
+			EditorGUILayout.PropertyField(copyMeshRendererFlags_);
+
+			// SkeletonRenderer Box
+			using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) {
+				// Fancy SkeletonRenderer foldout reference field
+				{
+					EditorGUI.indentLevel++;
+					EditorGUI.BeginChangeCheck();
+					var foldoutSkeletonRendererRect = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight);
+					EditorGUI.PropertyField(foldoutSkeletonRendererRect, skeletonRenderer_);
+					if (EditorGUI.EndChangeCheck())
+						serializedObject.ApplyModifiedProperties();
+					if (component.SkeletonRenderer != null) {
+						skeletonRendererExpanded = EditorGUI.Foldout(foldoutSkeletonRendererRect, skeletonRendererExpanded, "");
+					}
+					EditorGUI.indentLevel--;
+				}
+
+				int separatorCount = 0;
+				EditorGUI.BeginChangeCheck();
+				if (component.SkeletonRenderer != null) {
+					// Separators from SkeletonRenderer
+					{
+						bool skeletonRendererMismatch = skeletonRendererSerializedObject != null && skeletonRendererSerializedObject.targetObject != component.SkeletonRenderer;
+						if (separatorNamesProp == null || skeletonRendererMismatch) {
+							if (component.SkeletonRenderer != null) {
+								skeletonRendererSerializedObject = new SerializedObject(component.SkeletonRenderer);
+								separatorNamesProp = skeletonRendererSerializedObject.FindProperty("separatorSlotNames");
+								separatorNamesProp.isExpanded = true;
+							}
+						}
+							
+						if (separatorNamesProp != null) {
+							if (skeletonRendererExpanded) {
+								EditorGUI.indentLevel++;
+								SkeletonRendererInspector.SeparatorsField(separatorNamesProp);
+								EditorGUI.indentLevel--;
+							}
+							separatorCount = this.SkeletonRendererSeparatorCount;
+						}
+					}
+
+					if (SkeletonRendererSeparatorCount == 0) {
+						EditorGUILayout.HelpBox("Separators are empty. Change the size to 1 and choose a slot if you want the render to be separated.", MessageType.Info);
+					}
+				}
+
+				if (EditorGUI.EndChangeCheck()) {
+					skeletonRendererSerializedObject.ApplyModifiedProperties();
+
+					if (!Application.isPlaying)
+						slotsReapplyRequired = true;
+				}
+					
+
+				totalParts = separatorCount + 1;
+				var counterStyle = skeletonRendererExpanded ? EditorStyles.label : EditorStyles.miniLabel;
+				EditorGUILayout.LabelField(string.Format("{0}: separates into {1}.", SpineInspectorUtility.Pluralize(separatorCount, "separator", "separators"), SpineInspectorUtility.Pluralize(totalParts, "part", "parts") ), counterStyle);
+			}
+
+			// Parts renderers
+			using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) {
+				EditorGUI.indentLevel++;
+				EditorGUILayout.PropertyField(this.partsRenderers_, true);
+				EditorGUI.indentLevel--;
+
+				// Null items warning
+				bool nullItemsFound = componentRenderers.Contains(null);
+				if (nullItemsFound)
+					EditorGUILayout.HelpBox("Some items in the parts renderers list are null and may cause problems.\n\nYou can right-click on that element and choose 'Delete Array Element' to remove it.", MessageType.Warning);
+
+				// (Button) Match Separators count
+				if (separatorNamesProp != null) {
+					int currentRenderers = 0;
+					foreach (var r in componentRenderers) {
+						if (r != null)
+							currentRenderers++;
+					}
+					int extraRenderersNeeded = totalParts - currentRenderers;
+					if (component.enabled && component.SkeletonRenderer != null && extraRenderersNeeded > 0) {
+						EditorGUILayout.HelpBox(string.Format("Insufficient parts renderers. Some parts will not be rendered."), MessageType.Warning);
+						string addMissingLabel = string.Format("Add the missing renderer{1} ({0}) ", extraRenderersNeeded, SpineInspectorUtility.PluralThenS(extraRenderersNeeded));
+						if (GUILayout.Button(addMissingLabel, GUILayout.Height(40f))) {
+							AddPartsRenderer(extraRenderersNeeded);
+							DetectOrphanedPartsRenderers(component);
+						}
+					}
+				}
+					
+				if (partsRenderers_.isExpanded != partsRenderersExpanded) partsRenderersExpanded = partsRenderers_.isExpanded;
+				if (partsRenderers_.isExpanded) {
+					using (new EditorGUILayout.HorizontalScope()) {
+						// (Button) Destroy Renderers button
+						if (componentRenderers.Count > 0) {
+							if (GUILayout.Button("Clear Parts Renderers")) {
+								// Do you really want to destroy all?
+								if (EditorUtility.DisplayDialog("Destroy Renderers", "Do you really want to destroy all the Parts Renderer GameObjects in the list? (Undo will not work.)", "Destroy", "Cancel")) {						
+									foreach (var r in componentRenderers) {
+										if (r != null)
+											DestroyImmediate(r.gameObject, allowDestroyingAssets: false);
+									}
+									componentRenderers.Clear();
+									// Do you also want to destroy orphans? (You monster.)
+									DetectOrphanedPartsRenderers(component);
+								}
+							}
+						}
+
+						// (Button) Add Part Renderer button
+						if (GUILayout.Button("Add Parts Renderer"))
+							AddPartsRenderer(1);				
+					}
+				}
+			}
+
+			serializedObject.ApplyModifiedProperties();
+
+			if (slotsReapplyRequired && UnityEngine.Event.current.type == EventType.Repaint) {
+				SkeletonRendererInspector.ReapplySeparatorSlotNames(component.SkeletonRenderer);
+				component.SkeletonRenderer.LateUpdate();
+				SceneView.RepaintAll();
+				slotsReapplyRequired = false;
+			}
+		}
+
+		public void AddPartsRenderer (int count) {
+			var componentRenderers = component.partsRenderers;
+			bool emptyFound = componentRenderers.Contains(null);
+			if (emptyFound) {
+				bool userClearEntries = EditorUtility.DisplayDialog("Empty entries found", "Null entries found. Do you want to remove null entries before adding the new renderer? ", "Clear Empty Entries", "Don't Clear");
+				if (userClearEntries) componentRenderers.RemoveAll(x => x == null);
+			}
+
+			for (int i = 0; i < count; i++) {
+				int index = componentRenderers.Count;
+				var smr = SkeletonPartsRenderer.NewPartsRendererGameObject(component.transform, index.ToString());
+				componentRenderers.Add(smr);
+				EditorGUIUtility.PingObject(smr);
+
+				// increment renderer sorting order.
+				if (index == 0) continue;
+				var prev = componentRenderers[index - 1]; if (prev == null) continue;
+
+				var prevMeshRenderer = prev.GetComponent<MeshRenderer>();
+				var currentMeshRenderer = smr.GetComponent<MeshRenderer>();
+				if (prevMeshRenderer == null || currentMeshRenderer == null) continue;
+
+				int prevSortingLayer = prevMeshRenderer.sortingLayerID;
+				int prevSortingOrder = prevMeshRenderer.sortingOrder;
+				currentMeshRenderer.sortingLayerID = prevSortingLayer;
+				currentMeshRenderer.sortingOrder = prevSortingOrder + SkeletonRenderSeparator.DefaultSortingOrderIncrement;
+			}
+
+		}
+
+		/// <summary>Detects orphaned parts renderers and offers to delete them.</summary>
+		public void DetectOrphanedPartsRenderers (SkeletonRenderSeparator component) {
+			var children = component.GetComponentsInChildren<SkeletonPartsRenderer>();
+
+			var orphans = new System.Collections.Generic.List<SkeletonPartsRenderer>();
+			foreach (var r in children) {
+				if (!component.partsRenderers.Contains(r))
+					orphans.Add(r);
+			}
+
+			if (orphans.Count > 0) {
+				if (EditorUtility.DisplayDialog("Destroy Submesh Renderers", "Unassigned renderers were found. Do you want to delete them? (These may belong to another Render Separator in the same hierarchy. If you don't have another Render Separator component in the children of this GameObject, it's likely safe to delete. Warning: This operation cannot be undone.)", "Delete", "Cancel")) {
+					foreach (var o in orphans) {
+						DestroyImmediate(o.gameObject, allowDestroyingAssets: false);
+					}
+				}
+			}
+		}
+
+		#region SkeletonRenderer Context Menu Item
+		[MenuItem ("CONTEXT/SkeletonRenderer/Add Skeleton Render Separator")]
+		static void AddRenderSeparatorComponent (MenuCommand cmd) {
+			var skeletonRenderer = cmd.context as SkeletonRenderer;
+			skeletonRenderer.gameObject.AddComponent<SkeletonRenderSeparator>();
+		}
+
+		// Validate
+		[MenuItem ("CONTEXT/SkeletonRenderer/Add Skeleton Render Separator", true)]
+		static bool ValidateAddRenderSeparatorComponent (MenuCommand cmd) {
+			var skeletonRenderer = cmd.context as SkeletonRenderer;
+			var separator = skeletonRenderer.GetComponent<SkeletonRenderSeparator>();
+			bool separatorNotOnObject = separator == null;
+			return separatorNotOnObject;
+		}
+		#endregion
+
+	}
 }

+ 124 - 93
spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonPartsRenderer.cs

@@ -1,96 +1,127 @@
-/******************************************************************************
- * 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.
+/******************************************************************************
+ * 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 Spine.Unity.MeshGeneration;
 
-namespace Spine.Unity.Modules {
-	[RequireComponent(typeof(MeshRenderer), typeof(MeshFilter))]
-	public class SkeletonPartsRenderer : MonoBehaviour {
-
-		#region Properties
-		ISubmeshSetMeshGenerator meshGenerator;
-		public ISubmeshSetMeshGenerator MeshGenerator {
-			get {
-				LazyIntialize();
-				return meshGenerator;
-			}
-		}
-
-		MeshRenderer meshRenderer;
-		public MeshRenderer MeshRenderer {
-			get {
-				LazyIntialize();
-				return meshRenderer;
-			}
-		}
-
-		MeshFilter meshFilter;
-		public MeshFilter MeshFilter {
-			get {
-				LazyIntialize();
-				return meshFilter;
-			}
-		}
-		#endregion
-
-		void LazyIntialize () {
-			if (meshGenerator != null) return;
-			meshGenerator = new ArraysSubmeshSetMeshGenerator();
-			meshFilter = GetComponent<MeshFilter>();
-			meshRenderer = GetComponent<MeshRenderer>();
-		}
-
-		public void ClearMesh () {
-			LazyIntialize();
-			meshFilter.sharedMesh = null;
-		}
-
-		public void RenderParts (ExposedList<SubmeshInstruction> instructions, int startSubmesh, int endSubmesh) {
-			LazyIntialize();
-			MeshAndMaterials m = meshGenerator.GenerateMesh(instructions, startSubmesh, endSubmesh);
-			meshFilter.sharedMesh = m.mesh;
-			meshRenderer.sharedMaterials = m.materials;
-		}
-
-		public void SetPropertyBlock (MaterialPropertyBlock block) {
-			LazyIntialize();
-			meshRenderer.SetPropertyBlock(block);
-		}
-
-		public static SkeletonPartsRenderer NewPartsRendererGameObject (Transform parent, string name) {
-			var go = new GameObject(name, typeof(MeshFilter), typeof(MeshRenderer));
-			go.transform.SetParent(parent, false);
-			var returnComponent = go.AddComponent<SkeletonPartsRenderer>();
-
-			return returnComponent;
-		}
-	}
+/******************************************************************************
+ * 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 Spine.Unity.MeshGeneration;
+
+namespace Spine.Unity.Modules {
+	[RequireComponent(typeof(MeshRenderer), typeof(MeshFilter))]
+	public class SkeletonPartsRenderer : MonoBehaviour {
+
+		#region Properties
+		ISubmeshSetMeshGenerator meshGenerator;
+		public ISubmeshSetMeshGenerator MeshGenerator {
+			get {
+				LazyIntialize();
+				return meshGenerator;
+			}
+		}
+
+		MeshRenderer meshRenderer;
+		public MeshRenderer MeshRenderer {
+			get {
+				LazyIntialize();
+				return meshRenderer;
+			}
+		}
+
+		MeshFilter meshFilter;
+		public MeshFilter MeshFilter {
+			get {
+				LazyIntialize();
+				return meshFilter;
+			}
+		}
+		#endregion
+
+		void LazyIntialize () {
+			if (meshGenerator != null) return;
+			meshGenerator = new ArraysSubmeshSetMeshGenerator();
+			meshFilter = GetComponent<MeshFilter>();
+			meshRenderer = GetComponent<MeshRenderer>();
+		}
+
+		public void ClearMesh () {
+			LazyIntialize();
+			meshFilter.sharedMesh = null;
+		}
+
+		public void RenderParts (ExposedList<SubmeshInstruction> instructions, int startSubmesh, int endSubmesh) {
+			LazyIntialize();
+			MeshAndMaterials m = meshGenerator.GenerateMesh(instructions, startSubmesh, endSubmesh);
+			meshFilter.sharedMesh = m.mesh;
+			meshRenderer.sharedMaterials = m.materials;
+		}
+
+		public void SetPropertyBlock (MaterialPropertyBlock block) {
+			LazyIntialize();
+			meshRenderer.SetPropertyBlock(block);
+		}
+
+		public static SkeletonPartsRenderer NewPartsRendererGameObject (Transform parent, string name) {
+			var go = new GameObject(name, typeof(MeshFilter), typeof(MeshRenderer));
+			go.transform.SetParent(parent, false);
+			var returnComponent = go.AddComponent<SkeletonPartsRenderer>();
+
+			return returnComponent;
+		}
+	}
 }

+ 203 - 172
spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderSeparator.cs

@@ -1,174 +1,205 @@
-/******************************************************************************
- * 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.
+/******************************************************************************
+ * 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 System.Collections.Generic;
-using Spine.Unity;
 
-namespace Spine.Unity.Modules {
-	
-	[ExecuteInEditMode]
-	[HelpURL("https://github.com/pharan/spine-unity-docs/blob/master/SkeletonRenderSeparator.md")]
-	public class SkeletonRenderSeparator : MonoBehaviour {
-		public const int DefaultSortingOrderIncrement = 5;
-
-		#region Inspector
-		[SerializeField]
-		protected SkeletonRenderer skeletonRenderer;
-		public SkeletonRenderer SkeletonRenderer {
-			get { return skeletonRenderer; }
-			set {
-				if (skeletonRenderer != null)
-					skeletonRenderer.GenerateMeshOverride -= HandleRender;
-				
-				skeletonRenderer = value;
-				this.enabled = false; // Disable if nulled.
-			}
-		}
-
-		MeshRenderer mainMeshRenderer;
-		public bool copyPropertyBlock = false;
-		[Tooltip("Copies MeshRenderer flags into ")]
-		public bool copyMeshRendererFlags = false;
-		public List<Spine.Unity.Modules.SkeletonPartsRenderer> partsRenderers = new List<SkeletonPartsRenderer>();
-
-		#if UNITY_EDITOR
-		void Reset () {
-			if (skeletonRenderer == null)
-				skeletonRenderer = GetComponent<SkeletonRenderer>();
-		}
-		#endif
-		#endregion
-
-		void OnEnable () {
-			if (skeletonRenderer == null) return;
-			if (copiedBlock == null) copiedBlock = new MaterialPropertyBlock();	
-			mainMeshRenderer = skeletonRenderer.GetComponent<MeshRenderer>();
-
-			skeletonRenderer.GenerateMeshOverride -= HandleRender;
-			skeletonRenderer.GenerateMeshOverride += HandleRender;
-
-			#if UNITY_5_4_OR_NEWER
-			if (copyMeshRendererFlags) {
-				var lightProbeUsage = mainMeshRenderer.lightProbeUsage;
-				bool receiveShadows = mainMeshRenderer.receiveShadows;
-
-				for (int i = 0; i < partsRenderers.Count; i++) {
-					var currentRenderer = partsRenderers[i];
-					if (currentRenderer == null) continue; // skip null items.
-
-					var mr = currentRenderer.MeshRenderer;
-					mr.lightProbeUsage = lightProbeUsage;
-					mr.receiveShadows = receiveShadows;
-				}
-			}
-			#else
-			if (copyMeshRendererFlags) {
-				var useLightProbes = mainMeshRenderer.useLightProbes;
-				bool receiveShadows = mainMeshRenderer.receiveShadows;
-
-				for (int i = 0; i < partsRenderers.Count; i++) {
-					var currentRenderer = partsRenderers[i];
-					if (currentRenderer == null) continue; // skip null items.
-
-					var mr = currentRenderer.MeshRenderer;
-					mr.useLightProbes = useLightProbes;
-					mr.receiveShadows = receiveShadows;
-				}
-			}
-			#endif
-
-		}
-
-		void OnDisable () {
-			if (skeletonRenderer == null) return;
-			skeletonRenderer.GenerateMeshOverride -= HandleRender;
-
-			#if UNITY_EDITOR
-			skeletonRenderer.LateUpdate();
-			#endif
-
-			foreach (var s in partsRenderers)
-				s.ClearMesh();		
-		}
-
-		MaterialPropertyBlock copiedBlock;
-
-		void HandleRender (SkeletonRenderer.SmartMesh.Instruction instruction) {
-			int rendererCount = partsRenderers.Count;
-			if (rendererCount <= 0) return;
-
-			int rendererIndex = 0;
-
-			if (copyPropertyBlock)
-				mainMeshRenderer.GetPropertyBlock(copiedBlock);
-
-			var submeshInstructions = instruction.submeshInstructions;
-			var submeshInstructionsItems = submeshInstructions.Items;
-			int lastSubmeshInstruction = submeshInstructions.Count - 1;
-
-			var currentRenderer = partsRenderers[rendererIndex];
-			bool addNormals = skeletonRenderer.calculateNormals;
-			bool addTangents = skeletonRenderer.calculateTangents;
-			bool pmaVertexColors = skeletonRenderer.pmaVertexColors;
-				
-			for (int si = 0, start = 0; si <= lastSubmeshInstruction; si++) {
-				if (submeshInstructionsItems[si].forceSeparate || si == lastSubmeshInstruction) {
-					// Apply properties
-					var meshGenerator = currentRenderer.MeshGenerator;
-					meshGenerator.AddNormals = addNormals;
-					meshGenerator.AddTangents = addTangents;
-					meshGenerator.PremultiplyVertexColors = pmaVertexColors;
-					if (copyPropertyBlock)
-						currentRenderer.SetPropertyBlock(copiedBlock);
-
-					// Render
-					currentRenderer.RenderParts(instruction.submeshInstructions, start, si + 1);
-
-					start = si + 1;
-					rendererIndex++;
-					if (rendererIndex < rendererCount) {
-						currentRenderer = partsRenderers[rendererIndex];
-					} else {
-						// Not enough renderers. Skip the rest of the instructions.
-						break;
-					}
-				}
-			}
-				
-			// Clear extra renderers if they exist.
-			for (; rendererIndex < rendererCount; rendererIndex++) {
-				partsRenderers[rendererIndex].ClearMesh();
-			}
-
-		}
-
-	}
-}
+/******************************************************************************
+ * 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 System.Collections.Generic;
+using Spine.Unity;
+
+namespace Spine.Unity.Modules {
+	
+	[ExecuteInEditMode]
+	[HelpURL("https://github.com/pharan/spine-unity-docs/blob/master/SkeletonRenderSeparator.md")]
+	public class SkeletonRenderSeparator : MonoBehaviour {
+		public const int DefaultSortingOrderIncrement = 5;
+
+		#region Inspector
+		[SerializeField]
+		protected SkeletonRenderer skeletonRenderer;
+		public SkeletonRenderer SkeletonRenderer {
+			get { return skeletonRenderer; }
+			set {
+				if (skeletonRenderer != null)
+					skeletonRenderer.GenerateMeshOverride -= HandleRender;
+				
+				skeletonRenderer = value;
+				this.enabled = false; // Disable if nulled.
+			}
+		}
+
+		MeshRenderer mainMeshRenderer;
+		public bool copyPropertyBlock = false;
+		[Tooltip("Copies MeshRenderer flags into ")]
+		public bool copyMeshRendererFlags = false;
+		public List<Spine.Unity.Modules.SkeletonPartsRenderer> partsRenderers = new List<SkeletonPartsRenderer>();
+
+		#if UNITY_EDITOR
+		void Reset () {
+			if (skeletonRenderer == null)
+				skeletonRenderer = GetComponent<SkeletonRenderer>();
+		}
+		#endif
+		#endregion
+
+		void OnEnable () {
+			if (skeletonRenderer == null) return;
+			if (copiedBlock == null) copiedBlock = new MaterialPropertyBlock();	
+			mainMeshRenderer = skeletonRenderer.GetComponent<MeshRenderer>();
+
+			skeletonRenderer.GenerateMeshOverride -= HandleRender;
+			skeletonRenderer.GenerateMeshOverride += HandleRender;
+
+			#if UNITY_5_4_OR_NEWER
+			if (copyMeshRendererFlags) {
+				var lightProbeUsage = mainMeshRenderer.lightProbeUsage;
+				bool receiveShadows = mainMeshRenderer.receiveShadows;
+
+				for (int i = 0; i < partsRenderers.Count; i++) {
+					var currentRenderer = partsRenderers[i];
+					if (currentRenderer == null) continue; // skip null items.
+
+					var mr = currentRenderer.MeshRenderer;
+					mr.lightProbeUsage = lightProbeUsage;
+					mr.receiveShadows = receiveShadows;
+				}
+			}
+			#else
+			if (copyMeshRendererFlags) {
+				var useLightProbes = mainMeshRenderer.useLightProbes;
+				bool receiveShadows = mainMeshRenderer.receiveShadows;
+
+				for (int i = 0; i < partsRenderers.Count; i++) {
+					var currentRenderer = partsRenderers[i];
+					if (currentRenderer == null) continue; // skip null items.
+
+					var mr = currentRenderer.MeshRenderer;
+					mr.useLightProbes = useLightProbes;
+					mr.receiveShadows = receiveShadows;
+				}
+			}
+			#endif
+
+		}
+
+		void OnDisable () {
+			if (skeletonRenderer == null) return;
+			skeletonRenderer.GenerateMeshOverride -= HandleRender;
+
+			#if UNITY_EDITOR
+			skeletonRenderer.LateUpdate();
+			#endif
+
+			foreach (var s in partsRenderers)
+				s.ClearMesh();		
+		}
+
+		MaterialPropertyBlock copiedBlock;
+
+		void HandleRender (SkeletonRenderer.SmartMesh.Instruction instruction) {
+			int rendererCount = partsRenderers.Count;
+			if (rendererCount <= 0) return;
+
+			int rendererIndex = 0;
+
+			if (copyPropertyBlock)
+				mainMeshRenderer.GetPropertyBlock(copiedBlock);
+
+			var submeshInstructions = instruction.submeshInstructions;
+			var submeshInstructionsItems = submeshInstructions.Items;
+			int lastSubmeshInstruction = submeshInstructions.Count - 1;
+
+			var currentRenderer = partsRenderers[rendererIndex];
+			bool addNormals = skeletonRenderer.calculateNormals;
+			bool addTangents = skeletonRenderer.calculateTangents;
+			bool pmaVertexColors = skeletonRenderer.pmaVertexColors;
+				
+			for (int si = 0, start = 0; si <= lastSubmeshInstruction; si++) {
+				if (submeshInstructionsItems[si].forceSeparate || si == lastSubmeshInstruction) {
+					// Apply properties
+					var meshGenerator = currentRenderer.MeshGenerator;
+					meshGenerator.AddNormals = addNormals;
+					meshGenerator.AddTangents = addTangents;
+					meshGenerator.PremultiplyVertexColors = pmaVertexColors;
+					if (copyPropertyBlock)
+						currentRenderer.SetPropertyBlock(copiedBlock);
+
+					// Render
+					currentRenderer.RenderParts(instruction.submeshInstructions, start, si + 1);
+
+					start = si + 1;
+					rendererIndex++;
+					if (rendererIndex < rendererCount) {
+						currentRenderer = partsRenderers[rendererIndex];
+					} else {
+						// Not enough renderers. Skip the rest of the instructions.
+						break;
+					}
+				}
+			}
+				
+			// Clear extra renderers if they exist.
+			for (; rendererIndex < rendererCount; rendererIndex++) {
+				partsRenderers[rendererIndex].ClearMesh();
+			}
+
+		}
+
+	}
+}

+ 124 - 93
spine-unity/Assets/spine-unity/Modules/YieldInstructions/WaitForSpineAnimationComplete.cs

@@ -1,96 +1,127 @@
-/******************************************************************************
- * 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.
+/******************************************************************************
+ * 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.
  *****************************************************************************/
 
-#if (UNITY_5_0 || UNITY_5_1 || UNITY_5_2 || UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7)
-#define PREUNITY_5_3
-#endif
-
-using UnityEngine;
-using System.Collections;
-using Spine;
-
-namespace Spine.Unity {
-	/// <summary>
-	/// Use this as a condition-blocking yield instruction for Unity Coroutines. 
-	/// The routine will pause until the AnimationState.TrackEntry fires its Complete event.</summary>
-	public class WaitForSpineAnimationComplete : IEnumerator {
-		
-		bool m_WasFired = false;
-
-		public WaitForSpineAnimationComplete (Spine.TrackEntry trackEntry) {
-			#if PREUNITY_5_3
-			Debug.LogWarning("Unity 5.3 or later is required for Spine Unity custom yield instructions to function correctly.");
-			#endif
-
-			SafeSubscribe(trackEntry);
-		}
-
-		void HandleComplete (AnimationState state, int trackIndex, int loopCount) {
-			m_WasFired = true;
-		}
-
-		void SafeSubscribe (Spine.TrackEntry trackEntry) {
-			if (trackEntry == null) {
-				// Break immediately if trackEntry is null.
-				Debug.LogWarning("TrackEntry was null. Coroutine will continue immediately.");
-				m_WasFired = true;
-			} else {
-				// Function normally.
-				trackEntry.Complete += HandleComplete;
-			}
-		}
-
-		#region Reuse
-		/// <summary>
-		/// One optimization high-frequency YieldInstruction returns is to cache instances to minimize pressure. 
-		/// Use NowWaitFor to reuse the same instance of WaitForSpineAnimationComplete.</summary>
-		public WaitForSpineAnimationComplete NowWaitFor (Spine.TrackEntry trackEntry) {
-			SafeSubscribe(trackEntry);
-			return this;
-		}
-		#endregion
-
-		#region IEnumerator
-		bool IEnumerator.MoveNext () {
-			if (m_WasFired) {
-				((IEnumerator)this).Reset();	// auto-reset for YieldInstruction reuse
-				return false;
-			}
-
-			return true;
-		}
-		void IEnumerator.Reset () { m_WasFired = false; }
-		object IEnumerator.Current { get { return null; } }
-		#endregion
-
-	}
-
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#if (UNITY_5_0 || UNITY_5_1 || UNITY_5_2 || UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7)
+#define PREUNITY_5_3
+#endif
+
+using UnityEngine;
+using System.Collections;
+using Spine;
+
+namespace Spine.Unity {
+	/// <summary>
+	/// Use this as a condition-blocking yield instruction for Unity Coroutines. 
+	/// The routine will pause until the AnimationState.TrackEntry fires its Complete event.</summary>
+	public class WaitForSpineAnimationComplete : IEnumerator {
+		
+		bool m_WasFired = false;
+
+		public WaitForSpineAnimationComplete (Spine.TrackEntry trackEntry) {
+			#if PREUNITY_5_3
+			Debug.LogWarning("Unity 5.3 or later is required for Spine Unity custom yield instructions to function correctly.");
+			#endif
+
+			SafeSubscribe(trackEntry);
+		}
+
+		void HandleComplete (AnimationState state, int trackIndex, int loopCount) {
+			m_WasFired = true;
+		}
+
+		void SafeSubscribe (Spine.TrackEntry trackEntry) {
+			if (trackEntry == null) {
+				// Break immediately if trackEntry is null.
+				Debug.LogWarning("TrackEntry was null. Coroutine will continue immediately.");
+				m_WasFired = true;
+			} else {
+				// Function normally.
+				trackEntry.Complete += HandleComplete;
+			}
+		}
+
+		#region Reuse
+		/// <summary>
+		/// One optimization high-frequency YieldInstruction returns is to cache instances to minimize pressure. 
+		/// Use NowWaitFor to reuse the same instance of WaitForSpineAnimationComplete.</summary>
+		public WaitForSpineAnimationComplete NowWaitFor (Spine.TrackEntry trackEntry) {
+			SafeSubscribe(trackEntry);
+			return this;
+		}
+		#endregion
+
+		#region IEnumerator
+		bool IEnumerator.MoveNext () {
+			if (m_WasFired) {
+				((IEnumerator)this).Reset();	// auto-reset for YieldInstruction reuse
+				return false;
+			}
+
+			return true;
+		}
+		void IEnumerator.Reset () { m_WasFired = false; }
+		object IEnumerator.Current { get { return null; } }
+		#endregion
+
+	}
+
 }

+ 207 - 176
spine-unity/Assets/spine-unity/Modules/YieldInstructions/WaitForSpineEvent.cs

@@ -1,179 +1,210 @@
-/******************************************************************************
- * 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.
+/******************************************************************************
+ * 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.
  *****************************************************************************/
 
-#if (UNITY_5_0 || UNITY_5_1 || UNITY_5_2 || UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7)
-#define PREUNITY_5_3
-#endif
-
-using UnityEngine;
-using System.Collections;
-using Spine;
-
-namespace Spine.Unity {
-	/// <summary>
-	/// Use this as a condition-blocking yield instruction for Unity Coroutines. 
-	/// The routine will pause until the AnimationState fires an event matching the given event name or EventData reference.</summary>
-	public class WaitForSpineEvent : IEnumerator {
-
-		Spine.EventData m_TargetEvent;
-		string m_EventName;
-		Spine.AnimationState m_AnimationState;
-
-		bool m_WasFired = false;
-		bool m_unsubscribeAfterFiring = false;
-
-		#region Constructors
-		void Subscribe (Spine.AnimationState state, Spine.EventData eventDataReference, bool unsubscribe) {
-			#if PREUNITY_5_3
-			Debug.LogWarning("Unity 5.3 or later is required for Spine Unity custom yield instructions to function correctly.");
-			#endif
-
-			if (state == null) {
-				Debug.LogWarning("AnimationState argument was null. Coroutine will continue immediately.");
-				m_WasFired = true;
-				return;
-			} else if (eventDataReference == null) {
-				Debug.LogWarning("eventDataReference argument was null. Coroutine will continue immediately.");
-				m_WasFired = true;
-				return;
-			}
-
-			m_AnimationState = state;
-			m_TargetEvent = eventDataReference;
-			state.Event += HandleAnimationStateEvent;
-
-			m_unsubscribeAfterFiring = unsubscribe;
-
-		}
-
-		void SubscribeByName (Spine.AnimationState state, string eventName, bool unsubscribe) {
-			#if PREUNITY_5_3
-			Debug.LogWarning("Unity 5.3 or later is required for Spine Unity custom yield instructions to function correctly.");
-			#endif
-
-			if (state == null) {
-				Debug.LogWarning("AnimationState argument was null. Coroutine will continue immediately.");
-				m_WasFired = true;
-				return;
-			} else if (string.IsNullOrEmpty(eventName)) {
-				Debug.LogWarning("eventName argument was null. Coroutine will continue immediately.");
-				m_WasFired = true;
-				return;
-			}
-		
-			m_AnimationState = state;
-			m_EventName = eventName;
-			state.Event += HandleAnimationStateEventByName;
-
-			m_unsubscribeAfterFiring = unsubscribe;
-		}
-
-		public WaitForSpineEvent (Spine.AnimationState state, Spine.EventData eventDataReference, bool unsubscribeAfterFiring = true) {
-			Subscribe(state, eventDataReference, unsubscribeAfterFiring);
-		}
-
-		public WaitForSpineEvent (SkeletonAnimation skeletonAnimation, Spine.EventData eventDataReference, bool unsubscribeAfterFiring = true) {			
-			// If skeletonAnimation is invalid, its state will be null. Subscribe handles null states just fine.
-			Subscribe(skeletonAnimation.state, eventDataReference, unsubscribeAfterFiring);
-		}
-			
-		public WaitForSpineEvent (Spine.AnimationState state, string eventName, bool unsubscribeAfterFiring = true) {
-			SubscribeByName(state, eventName, unsubscribeAfterFiring);
-		}
-
-		public WaitForSpineEvent (SkeletonAnimation skeletonAnimation, string eventName, bool unsubscribeAfterFiring = true) {
-			// If skeletonAnimation is invalid, its state will be null. Subscribe handles null states just fine.
-			SubscribeByName(skeletonAnimation.state, eventName, unsubscribeAfterFiring);
-		}
-		#endregion
-
-		#region Event Handlers
-		void HandleAnimationStateEventByName (AnimationState state, int trackIndex, Spine.Event e) {
-			if (state != m_AnimationState) return;
-
-			m_WasFired |= (e.Data.Name == m_EventName);			// Check event name string match.
-			if (m_WasFired && m_unsubscribeAfterFiring)
-				state.Event -= HandleAnimationStateEventByName;	// Unsubscribe after correct event fires.
-		}
-
-		void HandleAnimationStateEvent (AnimationState state, int trackIndex, Spine.Event e) {
-			if (state != m_AnimationState) return;
-
-			m_WasFired |= (e.Data == m_TargetEvent);			// Check event data reference match.
-			if (m_WasFired && m_unsubscribeAfterFiring)
-				state.Event -= HandleAnimationStateEvent; 		// Usubscribe after correct event fires.
-		}
-		#endregion
-
-		#region Reuse
-		/// <summary>
-		/// By default, WaitForSpineEvent will unsubscribe from the event immediately after it fires a correct matching event. 
-		/// If you want to reuse this WaitForSpineEvent instance on the same event, you can set this to false.</summary>
-		public bool WillUnsubscribeAfterFiring { get { return m_unsubscribeAfterFiring; } set { m_unsubscribeAfterFiring = value; } }
-
-		public WaitForSpineEvent NowWaitFor (Spine.AnimationState state, Spine.EventData eventDataReference, bool unsubscribeAfterFiring = true) {
-			((IEnumerator)this).Reset();
-			Clear(state);
-			Subscribe(state, eventDataReference, unsubscribeAfterFiring);
-
-			return this;
-		}
-
-		public WaitForSpineEvent NowWaitFor (Spine.AnimationState state, string eventName, bool unsubscribeAfterFiring = true) {
-			((IEnumerator)this).Reset();
-			Clear(state);
-			SubscribeByName(state, eventName, unsubscribeAfterFiring);
-
-			return this;
-		}
-
-		void Clear (Spine.AnimationState state) {
-			state.Event -= HandleAnimationStateEvent;
-			state.Event -= HandleAnimationStateEventByName;
-		}
-		#endregion
-
-		#region IEnumerator
-		bool IEnumerator.MoveNext () {
-			if (m_WasFired) {
-				((IEnumerator)this).Reset();	// auto-reset for YieldInstruction reuse
-				return false;
-			}
-
-			return true;
-		}
-		void IEnumerator.Reset () { m_WasFired = false; }
-		object IEnumerator.Current { get { return null; } }
-		#endregion
-
-
-	}
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#if (UNITY_5_0 || UNITY_5_1 || UNITY_5_2 || UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7)
+#define PREUNITY_5_3
+#endif
+
+using UnityEngine;
+using System.Collections;
+using Spine;
+
+namespace Spine.Unity {
+	/// <summary>
+	/// Use this as a condition-blocking yield instruction for Unity Coroutines. 
+	/// The routine will pause until the AnimationState fires an event matching the given event name or EventData reference.</summary>
+	public class WaitForSpineEvent : IEnumerator {
+
+		Spine.EventData m_TargetEvent;
+		string m_EventName;
+		Spine.AnimationState m_AnimationState;
+
+		bool m_WasFired = false;
+		bool m_unsubscribeAfterFiring = false;
+
+		#region Constructors
+		void Subscribe (Spine.AnimationState state, Spine.EventData eventDataReference, bool unsubscribe) {
+			#if PREUNITY_5_3
+			Debug.LogWarning("Unity 5.3 or later is required for Spine Unity custom yield instructions to function correctly.");
+			#endif
+
+			if (state == null) {
+				Debug.LogWarning("AnimationState argument was null. Coroutine will continue immediately.");
+				m_WasFired = true;
+				return;
+			} else if (eventDataReference == null) {
+				Debug.LogWarning("eventDataReference argument was null. Coroutine will continue immediately.");
+				m_WasFired = true;
+				return;
+			}
+
+			m_AnimationState = state;
+			m_TargetEvent = eventDataReference;
+			state.Event += HandleAnimationStateEvent;
+
+			m_unsubscribeAfterFiring = unsubscribe;
+
+		}
+
+		void SubscribeByName (Spine.AnimationState state, string eventName, bool unsubscribe) {
+			#if PREUNITY_5_3
+			Debug.LogWarning("Unity 5.3 or later is required for Spine Unity custom yield instructions to function correctly.");
+			#endif
+
+			if (state == null) {
+				Debug.LogWarning("AnimationState argument was null. Coroutine will continue immediately.");
+				m_WasFired = true;
+				return;
+			} else if (string.IsNullOrEmpty(eventName)) {
+				Debug.LogWarning("eventName argument was null. Coroutine will continue immediately.");
+				m_WasFired = true;
+				return;
+			}
+		
+			m_AnimationState = state;
+			m_EventName = eventName;
+			state.Event += HandleAnimationStateEventByName;
+
+			m_unsubscribeAfterFiring = unsubscribe;
+		}
+
+		public WaitForSpineEvent (Spine.AnimationState state, Spine.EventData eventDataReference, bool unsubscribeAfterFiring = true) {
+			Subscribe(state, eventDataReference, unsubscribeAfterFiring);
+		}
+
+		public WaitForSpineEvent (SkeletonAnimation skeletonAnimation, Spine.EventData eventDataReference, bool unsubscribeAfterFiring = true) {			
+			// If skeletonAnimation is invalid, its state will be null. Subscribe handles null states just fine.
+			Subscribe(skeletonAnimation.state, eventDataReference, unsubscribeAfterFiring);
+		}
+			
+		public WaitForSpineEvent (Spine.AnimationState state, string eventName, bool unsubscribeAfterFiring = true) {
+			SubscribeByName(state, eventName, unsubscribeAfterFiring);
+		}
+
+		public WaitForSpineEvent (SkeletonAnimation skeletonAnimation, string eventName, bool unsubscribeAfterFiring = true) {
+			// If skeletonAnimation is invalid, its state will be null. Subscribe handles null states just fine.
+			SubscribeByName(skeletonAnimation.state, eventName, unsubscribeAfterFiring);
+		}
+		#endregion
+
+		#region Event Handlers
+		void HandleAnimationStateEventByName (AnimationState state, int trackIndex, Spine.Event e) {
+			if (state != m_AnimationState) return;
+
+			m_WasFired |= (e.Data.Name == m_EventName);			// Check event name string match.
+			if (m_WasFired && m_unsubscribeAfterFiring)
+				state.Event -= HandleAnimationStateEventByName;	// Unsubscribe after correct event fires.
+		}
+
+		void HandleAnimationStateEvent (AnimationState state, int trackIndex, Spine.Event e) {
+			if (state != m_AnimationState) return;
+
+			m_WasFired |= (e.Data == m_TargetEvent);			// Check event data reference match.
+			if (m_WasFired && m_unsubscribeAfterFiring)
+				state.Event -= HandleAnimationStateEvent; 		// Usubscribe after correct event fires.
+		}
+		#endregion
+
+		#region Reuse
+		/// <summary>
+		/// By default, WaitForSpineEvent will unsubscribe from the event immediately after it fires a correct matching event. 
+		/// If you want to reuse this WaitForSpineEvent instance on the same event, you can set this to false.</summary>
+		public bool WillUnsubscribeAfterFiring { get { return m_unsubscribeAfterFiring; } set { m_unsubscribeAfterFiring = value; } }
+
+		public WaitForSpineEvent NowWaitFor (Spine.AnimationState state, Spine.EventData eventDataReference, bool unsubscribeAfterFiring = true) {
+			((IEnumerator)this).Reset();
+			Clear(state);
+			Subscribe(state, eventDataReference, unsubscribeAfterFiring);
+
+			return this;
+		}
+
+		public WaitForSpineEvent NowWaitFor (Spine.AnimationState state, string eventName, bool unsubscribeAfterFiring = true) {
+			((IEnumerator)this).Reset();
+			Clear(state);
+			SubscribeByName(state, eventName, unsubscribeAfterFiring);
+
+			return this;
+		}
+
+		void Clear (Spine.AnimationState state) {
+			state.Event -= HandleAnimationStateEvent;
+			state.Event -= HandleAnimationStateEventByName;
+		}
+		#endregion
+
+		#region IEnumerator
+		bool IEnumerator.MoveNext () {
+			if (m_WasFired) {
+				((IEnumerator)this).Reset();	// auto-reset for YieldInstruction reuse
+				return false;
+			}
+
+			return true;
+		}
+		void IEnumerator.Reset () { m_WasFired = false; }
+		object IEnumerator.Current { get { return null; } }
+		#endregion
+
+
+	}
 }

Some files were not shown because too many files changed in this diff