Browse Source

[csharp] Unified all line endings of files in spine-csharp to lf.

Harald Csaszar 6 years ago
parent
commit
f186a83fca
35 changed files with 9683 additions and 9683 deletions
  1. 1587 1587
      spine-csharp/src/Animation.cs
  2. 1134 1134
      spine-csharp/src/AnimationState.cs
  3. 115 115
      spine-csharp/src/AnimationStateData.cs
  4. 315 315
      spine-csharp/src/Atlas.cs
  5. 109 109
      spine-csharp/src/Attachments/AtlasAttachmentLoader.cs
  6. 50 50
      spine-csharp/src/Attachments/Attachment.cs
  7. 49 49
      spine-csharp/src/Attachments/AttachmentLoader.cs
  8. 35 35
      spine-csharp/src/Attachments/AttachmentType.cs
  9. 40 40
      spine-csharp/src/Attachments/BoundingBoxAttachment.cs
  10. 134 134
      spine-csharp/src/Attachments/MeshAttachment.cs
  11. 48 48
      spine-csharp/src/Attachments/PathAttachment.cs
  12. 183 183
      spine-csharp/src/Attachments/RegionAttachment.cs
  13. 130 130
      spine-csharp/src/Attachments/VertexAttachment.cs
  14. 35 35
      spine-csharp/src/BlendMode.cs
  15. 362 362
      spine-csharp/src/Bone.cs
  16. 100 100
      spine-csharp/src/BoneData.cs
  17. 65 65
      spine-csharp/src/Event.cs
  18. 57 57
      spine-csharp/src/EventData.cs
  19. 35 35
      spine-csharp/src/IUpdatable.cs
  20. 276 276
      spine-csharp/src/IkConstraint.cs
  21. 112 112
      spine-csharp/src/IkConstraintData.cs
  22. 535 535
      spine-csharp/src/Json.cs
  23. 146 146
      spine-csharp/src/MathUtils.cs
  24. 436 436
      spine-csharp/src/PathConstraint.cs
  25. 79 79
      spine-csharp/src/PathConstraintData.cs
  26. 600 600
      spine-csharp/src/Skeleton.cs
  27. 911 911
      spine-csharp/src/SkeletonBinary.cs
  28. 234 234
      spine-csharp/src/SkeletonBounds.cs
  29. 229 229
      spine-csharp/src/SkeletonData.cs
  30. 882 882
      spine-csharp/src/SkeletonJson.cs
  31. 131 131
      spine-csharp/src/Skin.cs
  32. 100 100
      spine-csharp/src/Slot.cs
  33. 74 74
      spine-csharp/src/SlotData.cs
  34. 284 284
      spine-csharp/src/TransformConstraint.cs
  35. 71 71
      spine-csharp/src/TransformConstraintData.cs

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

@@ -1,1587 +1,1587 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-using System;
-using System.Collections.Generic;
-
-namespace Spine {
-	public class Animation {
-		internal ExposedList<Timeline> timelines;
-		internal float duration;
-		internal String name;
-
-		public string Name { get { return name; } }
-		public ExposedList<Timeline> Timelines { get { return timelines; } set { timelines = value; } }
-		public float Duration { get { return duration; } set { duration = value; } }
-
-		public Animation (string name, ExposedList<Timeline> timelines, float duration) {
-			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
-			if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null.");
-			this.name = name;
-			this.timelines = timelines;
-			this.duration = duration;
-		}
-
-		/// <summary>Applies all the animation's timelines to the specified skeleton.</summary>
-		/// <seealso cref="Timeline.Apply(Skeleton, float, float, ExposedList, float, MixBlend, MixDirection)"/>
-		public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList<Event> events, float alpha, MixBlend pose, MixDirection direction) {
-			if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
-
-			if (loop && duration != 0) {
-				time %= duration;
-				if (lastTime > 0) lastTime %= duration;
-			}
-
-			ExposedList<Timeline> timelines = this.timelines;
-			for (int i = 0, n = timelines.Count; i < n; i++)
-				timelines.Items[i].Apply(skeleton, lastTime, time, events, alpha, pose, direction);
-		}
-
-		/// <param name="target">After the first and before the last entry.</param>
-		internal static int BinarySearch (float[] values, float target, int step) {
-			int low = 0;
-			int high = values.Length / step - 2;
-			if (high == 0) return step;
-			int current = (int)((uint)high >> 1);
-			while (true) {
-				if (values[(current + 1) * step] <= target)
-					low = current + 1;
-				else
-					high = current;
-				if (low == high) return (low + 1) * step;
-				current = (int)((uint)(low + high) >> 1);
-			}
-		}
-
-		/// <param name="target">After the first and before the last entry.</param>
-		internal static int BinarySearch (float[] values, float target) {
-			int low = 0;
-			int high = values.Length - 2;
-			if (high == 0) return 1;
-			int current = (int)((uint)high >> 1);
-			while (true) {
-				if (values[(current + 1)] <= target)
-					low = current + 1;
-				else
-					high = current;
-				if (low == high) return (low + 1);
-				current = (int)((uint)(low + high) >> 1);
-			}
-		}
-
-		internal static int LinearSearch (float[] values, float target, int step) {
-			for (int i = 0, last = values.Length - step; i <= last; i += step)
-				if (values[i] > target) return i;
-			return -1;
-		}
-	}
-
-	public interface Timeline {
-		/// <summary>Sets the value(s) for the specified time.</summary>
-		/// <param name="skeleton">The skeleton the timeline is being applied to. This provides access to the bones, slots, and other skeleton components the timeline may change.</param>
-		/// <param name="lastTime">lastTime The time this timeline was last applied. Timelines such as EventTimeline trigger only at specific times rather than every frame. In that case, the timeline triggers everything between lastTime (exclusive) and <code>time</code> (inclusive).</param>
-		/// <param name="time">The time within the animation. Most timelines find the key before and the key after this time so they can interpolate between the keys.</param>
-		/// <param name="events">If any events are fired, they are added to this list. Can be null to ignore firing events or if the timeline does not fire events. May be null.</param>
-		/// <param name="alpha">alpha 0 applies the current or setup pose value (depending on pose parameter). 1 applies the timeline 
-		/// 	value. Between 0 and 1 applies a value between the current or setup pose and the timeline value. By adjusting
-		/// 	alpha over time, an animation can be mixed in or out. <code>alpha</code> can also be useful to
-		/// 	 apply animations on top of each other (layered).</param>
-		/// <param name="blend">Controls how mixing is applied when alpha is than 1.</param>
-		/// <param name="direction">Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions such as DrawOrderTimeline and AttachmentTimeline.</param>
-		void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> events, float alpha, MixBlend blend, MixDirection direction);
-		int PropertyId { get; }
-	}
-
-	/// <summary>
-	/// Controls how a timeline is mixed with the setup or current pose.</summary>
-	/// <seealso cref="Timeline.Apply(Skeleton, float, float, ExposedList, float, MixBlend, MixDirection)"/>
-	public enum MixBlend {
-
-		/// <summary> Transitions from the setup value to the timeline value (the current value is not used). Before the first key, the setup value is set..</summary>
-		Setup,
-
-		/// <summary>
-		/// <para>Transitions from the current value to the timeline value. Before the first key, transitions from the current value to 
-		/// the setup value. Timelines which perform instant transitions, such as <see cref="DrawOrderTimeline"/> or <see cref="AttachmentTimeline"/>, use the setup value before the first key.</para>
-		/// <para>
-		/// <code>First</code> is intended for the first animations applied, not for animations layered on top of those.</para>
-		/// </summary>
-		First,
-
-		/// <summary>
-		/// <para>
-		/// Transitions from the current value to the timeline value. No change is made before the first key (the current value is kept until the first key).</para>
-		/// <para>
-		/// <code>Replace</code> is intended for animations layered on top of others, not for the first animations applied.</para>
-		/// </summary>
-		Replace,
-
-		/// <summary>
-		/// <para>
-		/// Transitions from the current value to the current value plus the timeline value. No change is made before the first key (the current value is kept until the first key).</para>
-		/// <para>
-		/// <code>Add</code> is intended for animations layered on top of others, not for the first animations applied.</para>
-		/// </summary>
-		Add
-	}
-
-	/// <summary>
-	/// Indicates whether a timeline's <code>alpha</code> is mixing out over time toward 0 (the setup or current pose value) or mixing in toward 1 (the timeline's value).</summary>
-	/// <seealso cref="Timeline.Apply(Skeleton, float, float, ExposedList, float, MixBlend, MixDirection)"/>
-	public enum MixDirection {
-		In,
-		Out
-	}
-
-	internal enum TimelineType {
-		Rotate = 0, Translate, Scale, Shear, //
-		Attachment, Color, Deform, //
-		Event, DrawOrder, //
-		IkConstraint, TransformConstraint, //
-		PathConstraintPosition, PathConstraintSpacing, PathConstraintMix, //
-		TwoColor
-	}
-
-	public interface IBoneTimeline {
-		int BoneIndex { get; }
-	}
-
-	public interface ISlotTimeline {
-		int SlotIndex { get; }
-	}
-
-	/// <summary>Base class for frames that use an interpolation bezier curve.</summary>
-	abstract public class CurveTimeline : Timeline {
-		protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2;
-		protected const int BEZIER_SIZE = 10 * 2 - 1;
-
-		internal float[] curves; // type, x, y, ...
-		public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } }
-
-		public CurveTimeline (int frameCount) {
-			if (frameCount <= 0) throw new ArgumentException("frameCount must be > 0: " + frameCount, "frameCount");
-			curves = new float[(frameCount - 1) * BEZIER_SIZE];
-		}
-
-		abstract public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend pose, MixDirection direction);
-
-		abstract public int PropertyId { get; }
-
-		public void SetLinear (int frameIndex) {
-			curves[frameIndex * BEZIER_SIZE] = LINEAR;
-		}
-
-		public void SetStepped (int frameIndex) {
-			curves[frameIndex * BEZIER_SIZE] = STEPPED;
-		}
-
-		/// <summary>Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next.
-		/// cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of
-		/// the difference between the keyframe's values.</summary>
-		public void SetCurve (int frameIndex, float cx1, float cy1, float cx2, float cy2) {
-			float tmpx = (-cx1 * 2 + cx2) * 0.03f, tmpy = (-cy1 * 2 + cy2) * 0.03f;
-			float dddfx = ((cx1 - cx2) * 3 + 1) * 0.006f, dddfy = ((cy1 - cy2) * 3 + 1) * 0.006f;
-			float ddfx = tmpx * 2 + dddfx, ddfy = tmpy * 2 + dddfy;
-			float dfx = cx1 * 0.3f + tmpx + dddfx * 0.16666667f, dfy = cy1 * 0.3f + tmpy + dddfy * 0.16666667f;
-
-			int i = frameIndex * BEZIER_SIZE;
-			float[] curves = this.curves;
-			curves[i++] = BEZIER;
-
-			float x = dfx, y = dfy;
-			for (int n = i + BEZIER_SIZE - 1; i < n; i += 2) {
-				curves[i] = x;
-				curves[i + 1] = y;
-				dfx += ddfx;
-				dfy += ddfy;
-				ddfx += dddfx;
-				ddfy += dddfy;
-				x += dfx;
-				y += dfy;
-			}
-		}
-
-		public float GetCurvePercent (int frameIndex, float percent) {
-			percent = MathUtils.Clamp (percent, 0, 1);
-			float[] curves = this.curves;
-			int i = frameIndex * BEZIER_SIZE;
-			float type = curves[i];
-			if (type == LINEAR) return percent;
-			if (type == STEPPED) return 0;
-			i++;
-			float x = 0;
-			for (int start = i, n = i + BEZIER_SIZE - 1; i < n; i += 2) {
-				x = curves[i];
-				if (x >= percent) {
-					float prevX, prevY;
-					if (i == start) {
-						prevX = 0;
-						prevY = 0;
-					} else {
-						prevX = curves[i - 2];
-						prevY = curves[i - 1];
-					}
-					return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX);
-				}
-			}
-			float y = curves[i - 1];
-			return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1.
-		}
-		public float GetCurveType (int frameIndex) {
-			return curves[frameIndex * BEZIER_SIZE];
-		}
-	}
-
-	public class RotateTimeline : CurveTimeline, IBoneTimeline {
-		public const int ENTRIES = 2;
-		internal const int PREV_TIME = -2, PREV_ROTATION = -1;
-		internal const int ROTATION = 1;
-
-		internal int boneIndex;
-		internal float[] frames;
-
-		public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } }
-		public float[] Frames { get { return frames; } set { frames = value; } } // time, angle, ...
-
-		override public int PropertyId {
-			get { return ((int)TimelineType.Rotate << 24) + boneIndex; }
-		}
-
-		public RotateTimeline (int frameCount)
-			: base(frameCount) {
-			frames = new float[frameCount << 1];
-		}
-
-		/// <summary>Sets the time and value of the specified keyframe.</summary>
-		public void SetFrame (int frameIndex, float time, float degrees) {
-			frameIndex <<= 1;
-			frames[frameIndex] = time;
-			frames[frameIndex + ROTATION] = degrees;
-		}
-
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
-			Bone bone = skeleton.bones.Items[boneIndex];
-
-			float[] frames = this.frames;
-			if (time < frames[0]) {
-				switch (blend) {
-				case MixBlend.Setup:
-					bone.rotation = bone.data.rotation;
-					return;
-				case MixBlend.First:
-						float r = bone.data.rotation - bone.rotation;
-						bone.rotation += (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha;
-						return;
-				}
-				return;
-			}
-
-			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
-				float r = frames[frames.Length + PREV_ROTATION];
-				switch (blend) {
-					case MixBlend.Setup:
-						bone.rotation = bone.data.rotation + r * alpha;
-						break;
-					case MixBlend.First:
-					case MixBlend.Replace:
-						r += bone.data.rotation - bone.rotation;
-						r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360;
-						goto case MixBlend.Add; // Fall through.
-
-					case MixBlend.Add:
-						bone.rotation += r * alpha;
-						break;
-				}
-				return;
-			}
-
-			// Interpolate between the previous frame and the current frame.
-			int frame = Animation.BinarySearch(frames, time, ENTRIES);
-			float prevRotation = frames[frame + PREV_ROTATION];
-			float frameTime = frames[frame];
-			float percent = GetCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-			{
-				float r = frames[frame + ROTATION] - prevRotation;
-				r = prevRotation + (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * percent;
-				switch (blend) {
-					case MixBlend.Setup:
-						bone.rotation = bone.data.rotation + (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha;
-						break;
-					case MixBlend.First:
-					case MixBlend.Replace:
-						r += bone.data.rotation - bone.rotation;
-						goto case MixBlend.Add; // Fall through.
-					case MixBlend.Add:
-						bone.rotation += (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha;
-						break;
-				}
-			}
-			
-		}
-	}
-
-	public class TranslateTimeline : CurveTimeline, IBoneTimeline {
-		public const int ENTRIES = 3;
-		protected const int PREV_TIME = -3, PREV_X = -2, PREV_Y = -1;
-		protected const int X = 1, Y = 2;
-
-		internal int boneIndex;
-		internal float[] frames;
-
-		public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } }
-		public float[] Frames { get { return frames; } set { frames = value; } } // time, value, value, ...
-
-		override public int PropertyId {
-			get { return ((int)TimelineType.Translate << 24) + boneIndex; }
-		}
-
-		public TranslateTimeline (int frameCount)
-			: base(frameCount) {
-			frames = new float[frameCount * ENTRIES];
-		}
-
-		/// <summary>Sets the time and value of the specified keyframe.</summary>
-		public void SetFrame (int frameIndex, float time, float x, float y) {
-			frameIndex *= ENTRIES;
-			frames[frameIndex] = time;
-			frames[frameIndex + X] = x;
-			frames[frameIndex + Y] = y;
-		}
-
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
-			Bone bone = skeleton.bones.Items[boneIndex];
-
-			float[] frames = this.frames;
-			if (time < frames[0]) {
-				switch (blend) {
-				case MixBlend.Setup:
-					bone.x = bone.data.x;
-					bone.y = bone.data.y;
-					return;
-				case MixBlend.First:
-					bone.x += (bone.data.x - bone.x) * alpha;
-					bone.y += (bone.data.y - bone.y) * alpha;
-					return;
-				}
-				return;
-			}
-
-			float x, y;
-			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
-				x = frames[frames.Length + PREV_X];
-				y = frames[frames.Length + PREV_Y];
-			} else {
-				// Interpolate between the previous frame and the current frame.
-				int frame = Animation.BinarySearch(frames, time, ENTRIES);
-				x = frames[frame + PREV_X];
-				y = frames[frame + PREV_Y];
-				float frameTime = frames[frame];
-				float percent = GetCurvePercent(frame / ENTRIES - 1,
-					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
-				x += (frames[frame + X] - x) * percent;
-				y += (frames[frame + Y] - y) * percent;
-			}
-			switch (blend) {
-				case MixBlend.Setup:
-					bone.x = bone.data.x + x * alpha;
-					bone.y = bone.data.y + y * alpha;
-					break;
-				case MixBlend.First:
-				case MixBlend.Replace:
-					bone.x += (bone.data.x + x - bone.x) * alpha;
-					bone.y += (bone.data.y + y - bone.y) * alpha;
-					break;
-				case MixBlend.Add:
-					bone.x += x * alpha;
-					bone.y += y * alpha;
-					break;
-			}
-		}
-	}
-
-	public class ScaleTimeline : TranslateTimeline, IBoneTimeline {
-		override public int PropertyId {
-			get { return ((int)TimelineType.Scale << 24) + boneIndex; }
-		}
-
-		public ScaleTimeline (int frameCount)
-			: base(frameCount) {
-		}
-
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
-			Bone bone = skeleton.bones.Items[boneIndex];
-
-			float[] frames = this.frames;
-			if (time < frames[0]) {
-				switch (blend) {
-				case MixBlend.Setup:
-					bone.scaleX = bone.data.scaleX;
-					bone.scaleY = bone.data.scaleY;
-					return;
-				case MixBlend.First:
-					bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha;
-					bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha;
-					return;
-				}
-				return;
-			}
-
-			float x, y;
-			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
-				x = frames[frames.Length + PREV_X] * bone.data.scaleX;
-				y = frames[frames.Length + PREV_Y] * bone.data.scaleY;
-			} else {
-				// Interpolate between the previous frame and the current frame.
-				int frame = Animation.BinarySearch(frames, time, ENTRIES);
-				x = frames[frame + PREV_X];
-				y = frames[frame + PREV_Y];
-				float frameTime = frames[frame];
-				float percent = GetCurvePercent(frame / ENTRIES - 1,
-					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
-				x = (x + (frames[frame + X] - x) * percent) * bone.data.scaleX;
-				y = (y + (frames[frame + Y] - y) * percent) * bone.data.scaleY;
-			}
-			if (alpha == 1) {
-				if (blend == MixBlend.Add) {
-					bone.scaleX += x - bone.data.scaleX;
-					bone.scaleY += y - bone.data.scaleY;
-				} else {
-					bone.scaleX = x;
-					bone.scaleY = y;
-				}
-			} else {
-				// Mixing out uses sign of setup or current pose, else use sign of key.
-				float bx, by;
-				if (direction == MixDirection.Out) {
-					switch (blend) {
-						case MixBlend.Setup:
-							bx = bone.data.scaleX;
-							by = bone.data.scaleY;
-							bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha;
-							bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha;
-							break;
-						case MixBlend.First:
-						case MixBlend.Replace:
-							bx = bone.scaleX;
-							by = bone.scaleY;
-							bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha;
-							bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha;
-							break;
-						case MixBlend.Add:
-							bx = bone.scaleX;
-							by = bone.scaleY;
-							bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bone.data.scaleX) * alpha;
-							bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - bone.data.scaleY) * alpha;
-							break;
-					}
-				} else {
-					switch (blend) {
-						case MixBlend.Setup:
-							bx = Math.Abs(bone.data.scaleX) * Math.Sign(x);
-							by = Math.Abs(bone.data.scaleY) * Math.Sign(y);
-							bone.scaleX = bx + (x - bx) * alpha;
-							bone.scaleY = by + (y - by) * alpha;
-							break;
-						case MixBlend.First:
-						case MixBlend.Replace:
-							bx = Math.Abs(bone.scaleX) * Math.Sign(x);
-							by = Math.Abs(bone.scaleY) * Math.Sign(y);
-							bone.scaleX = bx + (x - bx) * alpha;
-							bone.scaleY = by + (y - by) * alpha;
-							break;
-						case MixBlend.Add:
-							bx = Math.Sign(x);
-							by = Math.Sign(y);
-							bone.scaleX = Math.Abs(bone.scaleX) * bx + (x - Math.Abs(bone.data.scaleX) * bx) * alpha;
-							bone.scaleY = Math.Abs(bone.scaleY) * by + (y - Math.Abs(bone.data.scaleY) * by) * alpha;
-							break;
-					}
-				}
-			}
-		}
-	}
-
-	public class ShearTimeline : TranslateTimeline, IBoneTimeline {
-		override public int PropertyId {
-			get { return ((int)TimelineType.Shear << 24) + boneIndex; }
-		}
-
-		public ShearTimeline (int frameCount)
-			: base(frameCount) {
-		}
-
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
-			Bone bone = skeleton.bones.Items[boneIndex];
-			float[] frames = this.frames;
-			if (time < frames[0]) {
-				switch (blend) {
-				case MixBlend.Setup:
-					bone.shearX = bone.data.shearX;
-					bone.shearY = bone.data.shearY;
-					return;
-				case MixBlend.First:
-					bone.shearX += (bone.data.shearX - bone.shearX) * alpha;
-					bone.shearY += (bone.data.shearY - bone.shearY) * alpha;
-					return;
-				}
-				return;
-			}
-
-			float x, y;
-			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
-				x = frames[frames.Length + PREV_X];
-				y = frames[frames.Length + PREV_Y];
-			} else {
-				// Interpolate between the previous frame and the current frame.
-				int frame = Animation.BinarySearch(frames, time, ENTRIES);
-				x = frames[frame + PREV_X];
-				y = frames[frame + PREV_Y];
-				float frameTime = frames[frame];
-				float percent = GetCurvePercent(frame / ENTRIES - 1,
-					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
-				x = x + (frames[frame + X] - x) * percent;
-				y = y + (frames[frame + Y] - y) * percent;
-			}
-			switch (blend) {
-				case MixBlend.Setup:
-					bone.shearX = bone.data.shearX + x * alpha;
-					bone.shearY = bone.data.shearY + y * alpha;
-					break;
-				case MixBlend.First:
-				case MixBlend.Replace:
-					bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha;
-					bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha;
-					break;
-				case MixBlend.Add:
-					bone.shearX += x * alpha;
-					bone.shearY += y * alpha;
-					break;
-			}
-		}
-	}
-
-	public class ColorTimeline : CurveTimeline, ISlotTimeline {
-		public const int ENTRIES = 5;
-		protected const int PREV_TIME = -5, PREV_R = -4, PREV_G = -3, PREV_B = -2, PREV_A = -1;
-		protected const int R = 1, G = 2, B = 3, A = 4;
-
-		internal int slotIndex;
-		internal float[] frames;
-
-		public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } }
-		public float[] Frames { get { return frames; } set { frames = value; } } // time, r, g, b, a, ...
-
-		override public int PropertyId {
-			get { return ((int)TimelineType.Color << 24) + slotIndex; }
-		}
-
-		public ColorTimeline (int frameCount)
-			: base(frameCount) {
-			frames = new float[frameCount * ENTRIES];
-		}
-
-		/// <summary>Sets the time and value of the specified keyframe.</summary>
-		public void SetFrame (int frameIndex, float time, float r, float g, float b, float a) {
-			frameIndex *= ENTRIES;
-			frames[frameIndex] = time;
-			frames[frameIndex + R] = r;
-			frames[frameIndex + G] = g;
-			frames[frameIndex + B] = b;
-			frames[frameIndex + A] = a;
-		}
-
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
-			Slot slot = skeleton.slots.Items[slotIndex];
-			float[] frames = this.frames;
-			if (time < frames[0]) {
-				var slotData = slot.data;
-				switch (blend) {
-				case MixBlend.Setup:
-					slot.r = slotData.r;
-					slot.g = slotData.g;
-					slot.b = slotData.b;
-					slot.a = slotData.a;
-					return;
-				case MixBlend.First:
-					slot.r += (slotData.r - slot.r) * alpha;
-					slot.g += (slotData.g - slot.g) * alpha;
-					slot.b += (slotData.b - slot.b) * alpha;
-					slot.a += (slotData.a - slot.a) * alpha;
-					return;
-				}
-				return;
-			}
-
-			float r, g, b, a;
-			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
-				int i = frames.Length;
-				r = frames[i + PREV_R];
-				g = frames[i + PREV_G];
-				b = frames[i + PREV_B];
-				a = frames[i + PREV_A];
-			} else {
-				// Interpolate between the previous frame and the current frame.
-				int frame = Animation.BinarySearch(frames, time, ENTRIES);
-				r = frames[frame + PREV_R];
-				g = frames[frame + PREV_G];
-				b = frames[frame + PREV_B];
-				a = frames[frame + PREV_A];
-				float frameTime = frames[frame];
-				float percent = GetCurvePercent(frame / ENTRIES - 1,
-					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
-				r += (frames[frame + R] - r) * percent;
-				g += (frames[frame + G] - g) * percent;
-				b += (frames[frame + B] - b) * percent;
-				a += (frames[frame + A] - a) * percent;
-			}
-			if (alpha == 1) {
-				slot.r = r;
-				slot.g = g;
-				slot.b = b;
-				slot.a = a;
-			} else {
-				float br, bg, bb, ba;
-				if (blend == MixBlend.Setup) {
-					br = slot.data.r;
-					bg = slot.data.g;
-					bb = slot.data.b;
-					ba = slot.data.a;
-				} else {
-					br = slot.r;
-					bg = slot.g;
-					bb = slot.b;
-					ba = slot.a;
-				}
-				slot.r = br + ((r - br) * alpha);
-				slot.g = bg + ((g - bg) * alpha);
-				slot.b = bb + ((b - bb) * alpha);
-				slot.a = ba + ((a - ba) * alpha);
-			}
-		}
-	}
-
-	public class TwoColorTimeline : CurveTimeline, ISlotTimeline {
-		public const int ENTRIES = 8;
-		protected const int PREV_TIME = -8, PREV_R = -7, PREV_G = -6, PREV_B = -5, PREV_A = -4;
-		protected const int PREV_R2 = -3, PREV_G2 = -2, PREV_B2 = -1;
-		protected const int R = 1, G = 2, B = 3, A = 4, R2 = 5, G2 = 6, B2 = 7;
-
-		internal int slotIndex;
-		internal float[] frames; // time, r, g, b, a, r2, g2, b2, ...
-
-		public int SlotIndex {
-			get { return slotIndex; }
-			set {
-				if (value < 0)
-					throw new ArgumentOutOfRangeException("index must be >= 0.");
-				slotIndex = value;
-			}
-		}
-		
-		public float[] Frames { get { return frames; } }
-
-		override public int PropertyId {
-			get { return ((int)TimelineType.TwoColor << 24) + slotIndex; }
-		}
-
-		public TwoColorTimeline (int frameCount) :
-			base(frameCount) {
-			frames = new float[frameCount * ENTRIES];
-		}
-
-		/// <summary>Sets the time and value of the specified keyframe.</summary>
-		public void SetFrame (int frameIndex, float time, float r, float g, float b, float a, float r2, float g2, float b2) {
-			frameIndex *= ENTRIES;
-			frames[frameIndex] = time;
-			frames[frameIndex + R] = r;
-			frames[frameIndex + G] = g;
-			frames[frameIndex + B] = b;
-			frames[frameIndex + A] = a;
-			frames[frameIndex + R2] = r2;
-			frames[frameIndex + G2] = g2;
-			frames[frameIndex + B2] = b2;
-		}
-
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
-			Slot slot = skeleton.slots.Items[slotIndex];
-			float[] frames = this.frames;
-			if (time < frames[0]) { // Time is before first frame.
-				var slotData = slot.data;
-				switch (blend) {
-				case MixBlend.Setup:
-					//	slot.color.set(slot.data.color);
-					//	slot.darkColor.set(slot.data.darkColor);
-					slot.r = slotData.r;
-					slot.g = slotData.g;
-					slot.b = slotData.b;
-					slot.a = slotData.a;
-					slot.r2 = slotData.r2;
-					slot.g2 = slotData.g2;
-					slot.b2 = slotData.b2;
-					return;
-				case MixBlend.First:
-					slot.r += (slot.r - slotData.r) * alpha;
-					slot.g += (slot.g - slotData.g) * alpha;
-					slot.b += (slot.b - slotData.b) * alpha;
-					slot.a += (slot.a - slotData.a) * alpha;
-					slot.r2 += (slot.r2 - slotData.r2) * alpha;
-					slot.g2 += (slot.g2 - slotData.g2) * alpha;
-					slot.b2 += (slot.b2 - slotData.b2) * alpha;
-					return;
-				}
-				return;
-			}
-
-			float r, g, b, a, r2, g2, b2;
-			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
-				int i = frames.Length;
-				r = frames[i + PREV_R];
-				g = frames[i + PREV_G];
-				b = frames[i + PREV_B];
-				a = frames[i + PREV_A];
-				r2 = frames[i + PREV_R2];
-				g2 = frames[i + PREV_G2];
-				b2 = frames[i + PREV_B2];
-			} else {
-				// Interpolate between the previous frame and the current frame.
-				int frame = Animation.BinarySearch(frames, time, ENTRIES);
-				r = frames[frame + PREV_R];
-				g = frames[frame + PREV_G];
-				b = frames[frame + PREV_B];
-				a = frames[frame + PREV_A];
-				r2 = frames[frame + PREV_R2];
-				g2 = frames[frame + PREV_G2];
-				b2 = frames[frame + PREV_B2];
-				float frameTime = frames[frame];
-				float percent = GetCurvePercent(frame / ENTRIES - 1,
-					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
-				r += (frames[frame + R] - r) * percent;
-				g += (frames[frame + G] - g) * percent;
-				b += (frames[frame + B] - b) * percent;
-				a += (frames[frame + A] - a) * percent;
-				r2 += (frames[frame + R2] - r2) * percent;
-				g2 += (frames[frame + G2] - g2) * percent;
-				b2 += (frames[frame + B2] - b2) * percent;
-			}
-			if (alpha == 1) {
-				slot.r = r;
-				slot.g = g;
-				slot.b = b;
-				slot.a = a;
-				slot.r2 = r2;
-				slot.g2 = g2;
-				slot.b2 = b2;
-			} else {
-				float br, bg, bb, ba, br2, bg2, bb2;
-				if (blend == MixBlend.Setup) {
-					br = slot.data.r;
-					bg = slot.data.g;
-					bb = slot.data.b;
-					ba = slot.data.a;
-					br2 = slot.data.r2;
-					bg2 = slot.data.g2;
-					bb2 = slot.data.b2;
-				} else {
-					br = slot.r;
-					bg = slot.g;
-					bb = slot.b;
-					ba = slot.a;
-					br2 = slot.r2;
-					bg2 = slot.g2;
-					bb2 = slot.b2;
-				}
-				slot.r = br + ((r - br) * alpha);
-				slot.g = bg + ((g - bg) * alpha);
-				slot.b = bb + ((b - bb) * alpha);
-				slot.a = ba + ((a - ba) * alpha);
-				slot.r2 = br2 + ((r2 - br2) * alpha);
-				slot.g2 = bg2 + ((g2 - bg2) * alpha);
-				slot.b2 = bb2 + ((b2 - bb2) * alpha);
-			}
-		}
-
-	}
-
-	public class AttachmentTimeline : Timeline, ISlotTimeline {
-		internal int slotIndex;
-		internal float[] frames;
-		internal string[] attachmentNames;
-
-		public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } }
-		public float[] Frames { get { return frames; } set { frames = value; } } // time, ...
-		public string[] AttachmentNames { get { return attachmentNames; } set { attachmentNames = value; } }
-		public int FrameCount { get { return frames.Length; } }
-
-		public int PropertyId {
-			get { return ((int)TimelineType.Attachment << 24) + slotIndex; }
-		}
-
-		public AttachmentTimeline (int frameCount) {
-			frames = new float[frameCount];
-			attachmentNames = new String[frameCount];
-		}
-
-		/// <summary>Sets the time and value of the specified keyframe.</summary>
-		public void SetFrame (int frameIndex, float time, String attachmentName) {
-			frames[frameIndex] = time;
-			attachmentNames[frameIndex] = attachmentName;
-		}
-
-		public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
-			string attachmentName;
-			Slot slot = skeleton.slots.Items[slotIndex];
-			if (direction == MixDirection.Out && blend == MixBlend.Setup) {
-				attachmentName = slot.data.attachmentName;
-				slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName);
-				return;
-			}
-
-			float[] frames = this.frames;
-			if (time < frames[0]) { // Time is before first frame.
-				if (blend == MixBlend.Setup || blend == MixBlend.First) {
-					attachmentName = slot.data.attachmentName;
-					slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName);
-				}
-				return;
-			}
-
-			int frameIndex;
-			if (time >= frames[frames.Length - 1]) // Time is after last frame.
-				frameIndex = frames.Length - 1;
-			else
-				frameIndex = Animation.BinarySearch(frames, time, 1) - 1;
-
-			attachmentName = attachmentNames[frameIndex];
-			slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName);
-		}
-	}
-
-	public class DeformTimeline : CurveTimeline, ISlotTimeline {
-		internal int slotIndex;
-		internal float[] frames;
-		internal float[][] frameVertices;
-		internal VertexAttachment attachment;
-
-		public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } }
-		public float[] Frames { get { return frames; } set { frames = value; } } // time, ...
-		public float[][] Vertices { get { return frameVertices; } set { frameVertices = value; } }
-		public VertexAttachment Attachment { get { return attachment; } set { attachment = value; } }
-
-		override public int PropertyId {
-			get { return ((int)TimelineType.Deform << 24) + attachment.id + slotIndex; }
-		}
-
-		public DeformTimeline (int frameCount)
-			: base(frameCount) {
-			frames = new float[frameCount];
-			frameVertices = new float[frameCount][];
-		}
-
-		/// <summary>Sets the time and value of the specified keyframe.</summary>
-		public void SetFrame (int frameIndex, float time, float[] vertices) {
-			frames[frameIndex] = time;
-			frameVertices[frameIndex] = vertices;
-		}
-
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
-			Slot slot = skeleton.slots.Items[slotIndex];
-			VertexAttachment vertexAttachment = slot.attachment as VertexAttachment;
-			if (vertexAttachment == null || !vertexAttachment.ApplyDeform(attachment)) return;
-
-			var verticesArray = slot.attachmentVertices;
-			if (verticesArray.Count == 0) blend = MixBlend.Setup;
-
-			float[][] frameVertices = this.frameVertices;
-			int vertexCount = frameVertices[0].Length;
-			float[] frames = this.frames;
-			float[] vertices;
-
-			if (time < frames[0]) {
-				
-				switch (blend) {
-				case MixBlend.Setup:
-					verticesArray.Clear();
-					return;
-				case MixBlend.Replace:
-					if (alpha == 1) {
-						verticesArray.Clear();
-						return;
-					}
-
-					// verticesArray.SetSize(vertexCount) // Ensure size and preemptively set count.
-					if (verticesArray.Capacity < vertexCount) verticesArray.Capacity = vertexCount;	
-					verticesArray.Count = vertexCount;
-					vertices = verticesArray.Items;
-
-					if (vertexAttachment.bones == null) {
-						// Unweighted vertex positions.
-						float[] setupVertices = vertexAttachment.vertices;
-						for (int i = 0; i < vertexCount; i++)
-							vertices[i] += (setupVertices[i] - vertices[i]) * alpha;
-					} else {
-						// Weighted deform offsets.
-						alpha = 1 - alpha;
-						for (int i = 0; i < vertexCount; i++)
-							vertices[i] *= alpha;
-					}
-					return;
-				default:
-					return;
-				}
-
-			}
-
-			// verticesArray.SetSize(vertexCount) // Ensure size and preemptively set count.
-			if (verticesArray.Capacity < vertexCount) verticesArray.Capacity = vertexCount;	
-			verticesArray.Count = vertexCount;
-			vertices = verticesArray.Items;
-
-			if (time >= frames[frames.Length - 1]) { // Time is after last frame.
-
-				float[] lastVertices = frameVertices[frames.Length - 1];
-				if (alpha == 1) {
-					if (blend == MixBlend.Add) {
-						if (vertexAttachment.bones == null) {
-							// Unweighted vertex positions, no alpha.
-							float[] setupVertices = vertexAttachment.vertices;
-							for (int i = 0; i < vertexCount; i++)
-								vertices[i] += lastVertices[i] - setupVertices[i];
-						} else {
-							// Weighted deform offsets, no alpha.
-							for (int i = 0; i < vertexCount; i++)
-								vertices[i] += lastVertices[i];
-						}
-					} else {
-						// Vertex positions or deform offsets, no alpha.
-						Array.Copy(lastVertices, 0, vertices, 0, vertexCount);
-					}
-				} else {
-					switch (blend) {
-						case MixBlend.Setup: {
-							if (vertexAttachment.bones == null) {
-								// Unweighted vertex positions, with alpha.
-								float[] setupVertices = vertexAttachment.vertices;
-								for (int i = 0; i < vertexCount; i++) {
-									float setup = setupVertices[i];
-									vertices[i] = setup + (lastVertices[i] - setup) * alpha;
-								}
-							} else {
-								// Weighted deform offsets, with alpha.
-								for (int i = 0; i < vertexCount; i++)
-									vertices[i] = lastVertices[i] * alpha;
-							}
-							break;
-						}
-						case MixBlend.First:
-						case MixBlend.Replace:
-							// Vertex positions or deform offsets, with alpha.
-							for (int i = 0; i < vertexCount; i++)
-								vertices[i] += (lastVertices[i] - vertices[i]) * alpha;
-							break;
-						case MixBlend.Add:
-							if (vertexAttachment.bones == null) {
-								// Unweighted vertex positions, no alpha.
-								float[] setupVertices = vertexAttachment.vertices;
-								for (int i = 0; i < vertexCount; i++)
-									vertices[i] += (lastVertices[i] - setupVertices[i]) * alpha;
-							} else {
-								// Weighted deform offsets, alpha.
-								for (int i = 0; i < vertexCount; i++)
-									vertices[i] += lastVertices[i] * alpha;
-							}
-							break;
-					}
-				}
-				return;
-			}
-
-			// Interpolate between the previous frame and the current frame.
-			int frame = Animation.BinarySearch(frames, time);
-			float[] prevVertices = frameVertices[frame - 1];
-			float[] nextVertices = frameVertices[frame];
-			float frameTime = frames[frame];
-			float percent = GetCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime));
-
-			if (alpha == 1) {
-				if (blend == MixBlend.Add) {
-					if (vertexAttachment.bones == null) {
-						// Unweighted vertex positions, no alpha.
-						float[] setupVertices = vertexAttachment.vertices;
-						for (int i = 0; i < vertexCount; i++) {
-							float prev = prevVertices[i];
-							vertices[i] += prev + (nextVertices[i] - prev) * percent - setupVertices[i];
-						}
-					} else {
-						// Weighted deform offsets, no alpha.
-						for (int i = 0; i < vertexCount; i++) {
-							float prev = prevVertices[i];
-							vertices[i] += prev + (nextVertices[i] - prev) * percent;
-						}
-					}
-				} else {
-					// Vertex positions or deform offsets, no alpha.
-					for (int i = 0; i < vertexCount; i++) {
-						float prev = prevVertices[i];
-						vertices[i] = prev + (nextVertices[i] - prev) * percent;
-					}
-				}
-			} else {
-				switch (blend) {
-					case MixBlend.Setup: {
-						if (vertexAttachment.bones == null) {
-							// Unweighted vertex positions, with alpha.
-							float[] setupVertices = vertexAttachment.vertices;
-							for (int i = 0; i < vertexCount; i++) {
-								float prev = prevVertices[i], setup = setupVertices[i];
-								vertices[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha;
-							}
-						} else {
-							// Weighted deform offsets, with alpha.
-							for (int i = 0; i < vertexCount; i++) {
-								float prev = prevVertices[i];
-								vertices[i] = (prev + (nextVertices[i] - prev) * percent) * alpha;
-							}
-						}
-						break;
-					}
-					case MixBlend.First:
-					case MixBlend.Replace: {
-						// Vertex positions or deform offsets, with alpha.
-						for (int i = 0; i < vertexCount; i++) {
-							float prev = prevVertices[i];
-							vertices[i] += (prev + (nextVertices[i] - prev) * percent - vertices[i]) * alpha;
-						}
-						break;
-					}
-					case MixBlend.Add: {
-						if (vertexAttachment.bones == null) {
-							// Unweighted vertex positions, with alpha.
-							float[] setupVertices = vertexAttachment.vertices;
-							for (int i = 0; i < vertexCount; i++) {
-								float prev = prevVertices[i];
-								vertices[i] += (prev + (nextVertices[i] - prev) * percent - setupVertices[i]) * alpha;
-							}
-						} else {
-							// Weighted deform offsets, with alpha.
-							for (int i = 0; i < vertexCount; i++) {
-								float prev = prevVertices[i];
-								vertices[i] += (prev + (nextVertices[i] - prev) * percent) * alpha;
-							}
-						}
-						break;
-					}
-				}
-			}
-		}
-	}
-
-	public class EventTimeline : Timeline {
-		internal float[] frames;
-		private Event[] events;
-
-		public float[] Frames { get { return frames; } set { frames = value; } } // time, ...
-		public Event[] Events { get { return events; } set { events = value; } }
-		public int FrameCount { get { return frames.Length; } }
-
-		public int PropertyId {
-			get { return ((int)TimelineType.Event << 24); }
-		}
-
-		public EventTimeline (int frameCount) {
-			frames = new float[frameCount];
-			events = new Event[frameCount];
-		}
-
-		/// <summary>Sets the time and value of the specified keyframe.</summary>
-		public void SetFrame (int frameIndex, Event e) {
-			frames[frameIndex] = e.Time;
-			events[frameIndex] = e;
-		}
-
-		/// <summary>Fires events for frames &gt; lastTime and &lt;= time.</summary>
-		public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
-			if (firedEvents == null) return;
-			float[] frames = this.frames;
-			int frameCount = frames.Length;
-
-			if (lastTime > time) { // Fire events after last time for looped animations.
-				Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha, blend, direction);
-				lastTime = -1f;
-			} else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame.
-				return;
-			if (time < frames[0]) return; // Time is before first frame.
-
-			int frame;
-			if (lastTime < frames[0])
-				frame = 0;
-			else {
-				frame = Animation.BinarySearch(frames, lastTime);
-				float frameTime = frames[frame];
-				while (frame > 0) { // Fire multiple events with the same frame.
-					if (frames[frame - 1] != frameTime) break;
-					frame--;
-				}
-			}
-			for (; frame < frameCount && time >= frames[frame]; frame++)
-				firedEvents.Add(events[frame]);
-		}
-	}
-
-	public class DrawOrderTimeline : Timeline {
-		internal float[] frames;
-		private int[][] drawOrders;
-
-		public float[] Frames { get { return frames; } set { frames = value; } } // time, ...
-		public int[][] DrawOrders { get { return drawOrders; } set { drawOrders = value; } }
-		public int FrameCount { get { return frames.Length; } }
-
-		public int PropertyId {
-			get { return ((int)TimelineType.DrawOrder << 24); }
-		}
-
-		public DrawOrderTimeline (int frameCount) {
-			frames = new float[frameCount];
-			drawOrders = new int[frameCount][];
-		}
-
-		/// <summary>Sets the time and value of the specified keyframe.</summary>
-		/// <param name="drawOrder">May be null to use bind pose draw order.</param>
-		public void SetFrame (int frameIndex, float time, int[] drawOrder) {
-			frames[frameIndex] = time;
-			drawOrders[frameIndex] = drawOrder;
-		}
-
-		public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
-			ExposedList<Slot> drawOrder = skeleton.drawOrder;
-			ExposedList<Slot> slots = skeleton.slots;
-			if (direction == MixDirection.Out && blend == MixBlend.Setup) {
-				Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count);
-				return;
-			}
-
-			float[] frames = this.frames;
-			if (time < frames[0]) {
-				if (blend == MixBlend.Setup || blend == MixBlend.First) Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count);
-				return;
-			}
-
-			int frame;
-			if (time >= frames[frames.Length - 1]) // Time is after last frame.
-				frame = frames.Length - 1;
-			else
-				frame = Animation.BinarySearch(frames, time) - 1;
-			
-			int[] drawOrderToSetupIndex = drawOrders[frame];
-			if (drawOrderToSetupIndex == null) {
-				Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count);
-			} else {
-				var drawOrderItems = drawOrder.Items;
-				var slotsItems = slots.Items;
-				for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++)
-					drawOrderItems[i] = slotsItems[drawOrderToSetupIndex[i]];
-			}
-		}
-	}
-
-	public class IkConstraintTimeline : CurveTimeline {
-		public const int ENTRIES = 5;
-		private const int PREV_TIME = -5, PREV_MIX = -4, PREV_BEND_DIRECTION = -3, PREV_COMPRESS = -2, PREV_STRETCH = -1;
-		private const int MIX = 1, BEND_DIRECTION = 2, COMPRESS = 3, STRETCH = 4;
-
-		internal int ikConstraintIndex;
-		internal float[] frames;
-
-		public int IkConstraintIndex { get { return ikConstraintIndex; } set { ikConstraintIndex = value; } }
-		public float[] Frames { get { return frames; } set { frames = value; } } // time, mix, bendDirection, compress, stretch ...
-
-		override public int PropertyId {
-			get { return ((int)TimelineType.IkConstraint << 24) + ikConstraintIndex; }
-		}
-
-		public IkConstraintTimeline (int frameCount)
-			: base(frameCount) {
-			frames = new float[frameCount * ENTRIES];
-		}
-			
-		/// <summary>Sets the time, mix, bend direction, compress and stretch of the specified keyframe.</summary>
-		public void SetFrame (int frameIndex, float time, float mix, int bendDirection, bool compress, bool stretch) {
-			frameIndex *= ENTRIES;
-			frames[frameIndex] = time;
-			frames[frameIndex + MIX] = mix;
-			frames[frameIndex + BEND_DIRECTION] = bendDirection;
-			frames[frameIndex + COMPRESS] = compress ? 1 : 0;
-			frames[frameIndex + STRETCH] = stretch ? 1 : 0;
-		}
-
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
-			IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex];
-			float[] frames = this.frames;
-			if (time < frames[0]) {
-				switch (blend) {
-				case MixBlend.Setup:
-					constraint.mix = constraint.data.mix;
-					constraint.bendDirection = constraint.data.bendDirection;
-					constraint.compress = constraint.data.compress;
-					constraint.stretch = constraint.data.stretch;
-					return;
-				case MixBlend.First:
-					constraint.mix += (constraint.data.mix - constraint.mix) * alpha;
-					constraint.bendDirection = constraint.data.bendDirection;
-					constraint.compress = constraint.data.compress;
-					constraint.stretch = constraint.data.stretch;
-					return;
-				}
-				return;
-			}
-
-			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
-				if (blend == MixBlend.Setup) {
-					constraint.mix = constraint.data.mix + (frames[frames.Length + PREV_MIX] - constraint.data.mix) * alpha;
-					if (direction == MixDirection.Out) {
-						constraint.bendDirection = constraint.data.bendDirection;
-						constraint.compress = constraint.data.compress;
-						constraint.stretch = constraint.data.stretch;
-					} else {
-						constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION];
-						constraint.compress = frames[frames.Length + PREV_COMPRESS] != 0;
-						constraint.stretch = frames[frames.Length + PREV_STRETCH] != 0;
-					}
-				} else {
-					constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha;
-					if (direction == MixDirection.In) {
-						constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION];
-						constraint.compress = frames[frames.Length + PREV_COMPRESS] != 0;
-						constraint.stretch = frames[frames.Length + PREV_STRETCH] != 0;
-					}
-				}
-				return;
-			}
-
-			// Interpolate between the previous frame and the current frame.
-			int frame = Animation.BinarySearch(frames, time, ENTRIES);
-			float mix = frames[frame + PREV_MIX];
-			float frameTime = frames[frame];
-			float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
-			if (blend == MixBlend.Setup) {
-				constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha;
-				if (direction == MixDirection.Out) {
-					constraint.bendDirection = constraint.data.bendDirection;
-					constraint.compress = constraint.data.compress;
-					constraint.stretch = constraint.data.stretch;
-				} else {
-					constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION];
-					constraint.compress = frames[frame + PREV_COMPRESS] != 0;
-					constraint.stretch = frames[frame + PREV_STRETCH] != 0;
-				}
-			} else {
-				constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha;
-				if (direction == MixDirection.In) {
-					constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION];
-					constraint.compress = frames[frame + PREV_COMPRESS] != 0;
-					constraint.stretch = frames[frame + PREV_STRETCH] != 0;
-				}
-			}
-		}
-	}
-
-	public class TransformConstraintTimeline : CurveTimeline {
-		public const int ENTRIES = 5;
-		private const int PREV_TIME = -5, PREV_ROTATE = -4, PREV_TRANSLATE = -3, PREV_SCALE = -2, PREV_SHEAR = -1;
-		private const int ROTATE = 1, TRANSLATE = 2, SCALE = 3, SHEAR = 4;
-
-		internal int transformConstraintIndex;
-		internal float[] frames;
-
-		public int TransformConstraintIndex { get { return transformConstraintIndex; } set { transformConstraintIndex = value; } }
-		public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, scale mix, shear mix, ...
-
-		override public int PropertyId {
-			get { return ((int)TimelineType.TransformConstraint << 24) + transformConstraintIndex; }
-		}
-
-		public TransformConstraintTimeline (int frameCount)
-			: base(frameCount) {
-			frames = new float[frameCount * ENTRIES];
-		}
-
-		public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix, float scaleMix, float shearMix) {
-			frameIndex *= ENTRIES;
-			frames[frameIndex] = time;
-			frames[frameIndex + ROTATE] = rotateMix;
-			frames[frameIndex + TRANSLATE] = translateMix;
-			frames[frameIndex + SCALE] = scaleMix;
-			frames[frameIndex + SHEAR] = shearMix;
-		}
-
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
-			TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex];
-			float[] frames = this.frames;
-			if (time < frames[0]) {
-				var data = constraint.data;
-				switch (blend) {
-				case MixBlend.Setup:
-					constraint.rotateMix = data.rotateMix;
-					constraint.translateMix = data.translateMix;
-					constraint.scaleMix = data.scaleMix;
-					constraint.shearMix = data.shearMix;
-					return;
-				case MixBlend.First:
-					constraint.rotateMix += (data.rotateMix - constraint.rotateMix) * alpha;
-					constraint.translateMix += (data.translateMix - constraint.translateMix) * alpha;
-					constraint.scaleMix += (data.scaleMix - constraint.scaleMix) * alpha;
-					constraint.shearMix += (data.shearMix - constraint.shearMix) * alpha;
-					return;
-				}
-				return;
-			}
-
-			float rotate, translate, scale, shear;
-			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
-				int i = frames.Length;
-				rotate = frames[i + PREV_ROTATE];
-				translate = frames[i + PREV_TRANSLATE];
-				scale = frames[i + PREV_SCALE];
-				shear = frames[i + PREV_SHEAR];
-			} else {
-				// Interpolate between the previous frame and the current frame.
-				int frame = Animation.BinarySearch(frames, time, ENTRIES);
-				rotate = frames[frame + PREV_ROTATE];
-				translate = frames[frame + PREV_TRANSLATE];
-				scale = frames[frame + PREV_SCALE];
-				shear = frames[frame + PREV_SHEAR];
-				float frameTime = frames[frame];
-				float percent = GetCurvePercent(frame / ENTRIES - 1,
-					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
-				rotate += (frames[frame + ROTATE] - rotate) * percent;
-				translate += (frames[frame + TRANSLATE] - translate) * percent;
-				scale += (frames[frame + SCALE] - scale) * percent;
-				shear += (frames[frame + SHEAR] - shear) * percent;
-			}
-			if (blend == MixBlend.Setup) {
-				TransformConstraintData data = constraint.data;
-				constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha;
-				constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha;
-				constraint.scaleMix = data.scaleMix + (scale - data.scaleMix) * alpha;
-				constraint.shearMix = data.shearMix + (shear - data.shearMix) * alpha;
-			} else {
-				constraint.rotateMix += (rotate - constraint.rotateMix) * alpha;
-				constraint.translateMix += (translate - constraint.translateMix) * alpha;
-				constraint.scaleMix += (scale - constraint.scaleMix) * alpha;
-				constraint.shearMix += (shear - constraint.shearMix) * alpha;
-			}
-		}
-	}
-
-	public class PathConstraintPositionTimeline : CurveTimeline {
-		public const int ENTRIES = 2;
-		protected const int PREV_TIME = -2, PREV_VALUE = -1;
-		protected const int VALUE = 1;
-
-		internal int pathConstraintIndex;
-		internal float[] frames;
-
-		override public int PropertyId {
-			get { return ((int)TimelineType.PathConstraintPosition << 24) + pathConstraintIndex; }
-		}
-
-		public PathConstraintPositionTimeline (int frameCount)
-			: base(frameCount) {
-			frames = new float[frameCount * ENTRIES];
-		}
-
-		public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } }
-		public float[] Frames { get { return frames; } set { frames = value; } } // time, position, ...
-
-		/// <summary>Sets the time and value of the specified keyframe.</summary>
-		public void SetFrame (int frameIndex, float time, float value) {
-			frameIndex *= ENTRIES;
-			frames[frameIndex] = time;
-			frames[frameIndex + VALUE] = value;
-		}
-
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
-			PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
-			float[] frames = this.frames;
-			if (time < frames[0]) {
-				switch (blend) {
-				case MixBlend.Setup:
-					constraint.position = constraint.data.position;
-					return;
-				case MixBlend.First:
-					constraint.position += (constraint.data.position - constraint.position) * alpha;
-					return;
-				}
-				return;
-			}
-
-			float position;
-			if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame.
-				position = frames[frames.Length + PREV_VALUE];
-			else {
-				// Interpolate between the previous frame and the current frame.
-				int frame = Animation.BinarySearch(frames, time, ENTRIES);
-				position = frames[frame + PREV_VALUE];
-				float frameTime = frames[frame];
-				float percent = GetCurvePercent(frame / ENTRIES - 1,
-					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
-				position += (frames[frame + VALUE] - position) * percent;
-			}
-			if (blend == MixBlend.Setup)
-				constraint.position = constraint.data.position + (position - constraint.data.position) * alpha;
-			else
-				constraint.position += (position - constraint.position) * alpha;
-		}
-	}
-
-	public class PathConstraintSpacingTimeline : PathConstraintPositionTimeline {
-		override public int PropertyId {
-			get { return ((int)TimelineType.PathConstraintSpacing << 24) + pathConstraintIndex; }
-		}
-
-		public PathConstraintSpacingTimeline (int frameCount)
-			: base(frameCount) {
-		}
-
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
-			PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
-			float[] frames = this.frames;
-			if (time < frames[0]) {
-				switch (blend) {
-				case MixBlend.Setup:
-					constraint.spacing = constraint.data.spacing;
-					return;
-				case MixBlend.First:
-					constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha;
-					return;
-				}
-				return;
-			}
-
-			float spacing;
-			if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame.
-				spacing = frames[frames.Length + PREV_VALUE];
-			else {
-				// Interpolate between the previous frame and the current frame.
-				int frame = Animation.BinarySearch(frames, time, ENTRIES);
-				spacing = frames[frame + PREV_VALUE];
-				float frameTime = frames[frame];
-				float percent = GetCurvePercent(frame / ENTRIES - 1,
-					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
-				spacing += (frames[frame + VALUE] - spacing) * percent;
-			}
-
-			if (blend == MixBlend.Setup)
-				constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha;
-			else
-				constraint.spacing += (spacing - constraint.spacing) * alpha;
-		}
-	}
-
-	public class PathConstraintMixTimeline : CurveTimeline {
-		public const int ENTRIES = 3;
-		private const int PREV_TIME = -3, PREV_ROTATE = -2, PREV_TRANSLATE = -1;
-		private const int ROTATE = 1, TRANSLATE = 2;
-
-		internal int pathConstraintIndex;
-		internal float[] frames;
-
-		public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } }
-		public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, ...
-
-		override public int PropertyId {
-			get { return ((int)TimelineType.PathConstraintMix << 24) + pathConstraintIndex; }
-		}
-
-		public PathConstraintMixTimeline (int frameCount)
-			: base(frameCount) {
-			frames = new float[frameCount * ENTRIES];
-		}			
-
-		/// <summary>Sets the time and mixes of the specified keyframe.</summary>
-		public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix) {
-			frameIndex *= ENTRIES;
-			frames[frameIndex] = time;
-			frames[frameIndex + ROTATE] = rotateMix;
-			frames[frameIndex + TRANSLATE] = translateMix;
-		}
-
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
-			PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
-			float[] frames = this.frames;
-			if (time < frames[0]) {
-				switch (blend) {
-				case MixBlend.Setup:
-					constraint.rotateMix = constraint.data.rotateMix;
-					constraint.translateMix = constraint.data.translateMix;
-					return;
-				case MixBlend.First:
-					constraint.rotateMix += (constraint.data.rotateMix - constraint.rotateMix) * alpha;
-					constraint.translateMix += (constraint.data.translateMix - constraint.translateMix) * alpha;
-					return;
-				}
-				return;
-			}
-
-			float rotate, translate;
-			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
-				rotate = frames[frames.Length + PREV_ROTATE];
-				translate = frames[frames.Length + PREV_TRANSLATE];
-			} else {
-				// Interpolate between the previous frame and the current frame.
-				int frame = Animation.BinarySearch(frames, time, ENTRIES);
-				rotate = frames[frame + PREV_ROTATE];
-				translate = frames[frame + PREV_TRANSLATE];
-				float frameTime = frames[frame];
-				float percent = GetCurvePercent(frame / ENTRIES - 1,
-					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
-				rotate += (frames[frame + ROTATE] - rotate) * percent;
-				translate += (frames[frame + TRANSLATE] - translate) * percent;
-			}
-
-			if (blend == MixBlend.Setup) {
-				constraint.rotateMix = constraint.data.rotateMix + (rotate - constraint.data.rotateMix) * alpha;
-				constraint.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha;
-			} else {
-				constraint.rotateMix += (rotate - constraint.rotateMix) * alpha;
-				constraint.translateMix += (translate - constraint.translateMix) * alpha;
-			}
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+using System.Collections.Generic;
+
+namespace Spine {
+	public class Animation {
+		internal ExposedList<Timeline> timelines;
+		internal float duration;
+		internal String name;
+
+		public string Name { get { return name; } }
+		public ExposedList<Timeline> Timelines { get { return timelines; } set { timelines = value; } }
+		public float Duration { get { return duration; } set { duration = value; } }
+
+		public Animation (string name, ExposedList<Timeline> timelines, float duration) {
+			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
+			if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null.");
+			this.name = name;
+			this.timelines = timelines;
+			this.duration = duration;
+		}
+
+		/// <summary>Applies all the animation's timelines to the specified skeleton.</summary>
+		/// <seealso cref="Timeline.Apply(Skeleton, float, float, ExposedList, float, MixBlend, MixDirection)"/>
+		public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList<Event> events, float alpha, MixBlend pose, MixDirection direction) {
+			if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
+
+			if (loop && duration != 0) {
+				time %= duration;
+				if (lastTime > 0) lastTime %= duration;
+			}
+
+			ExposedList<Timeline> timelines = this.timelines;
+			for (int i = 0, n = timelines.Count; i < n; i++)
+				timelines.Items[i].Apply(skeleton, lastTime, time, events, alpha, pose, direction);
+		}
+
+		/// <param name="target">After the first and before the last entry.</param>
+		internal static int BinarySearch (float[] values, float target, int step) {
+			int low = 0;
+			int high = values.Length / step - 2;
+			if (high == 0) return step;
+			int current = (int)((uint)high >> 1);
+			while (true) {
+				if (values[(current + 1) * step] <= target)
+					low = current + 1;
+				else
+					high = current;
+				if (low == high) return (low + 1) * step;
+				current = (int)((uint)(low + high) >> 1);
+			}
+		}
+
+		/// <param name="target">After the first and before the last entry.</param>
+		internal static int BinarySearch (float[] values, float target) {
+			int low = 0;
+			int high = values.Length - 2;
+			if (high == 0) return 1;
+			int current = (int)((uint)high >> 1);
+			while (true) {
+				if (values[(current + 1)] <= target)
+					low = current + 1;
+				else
+					high = current;
+				if (low == high) return (low + 1);
+				current = (int)((uint)(low + high) >> 1);
+			}
+		}
+
+		internal static int LinearSearch (float[] values, float target, int step) {
+			for (int i = 0, last = values.Length - step; i <= last; i += step)
+				if (values[i] > target) return i;
+			return -1;
+		}
+	}
+
+	public interface Timeline {
+		/// <summary>Sets the value(s) for the specified time.</summary>
+		/// <param name="skeleton">The skeleton the timeline is being applied to. This provides access to the bones, slots, and other skeleton components the timeline may change.</param>
+		/// <param name="lastTime">lastTime The time this timeline was last applied. Timelines such as EventTimeline trigger only at specific times rather than every frame. In that case, the timeline triggers everything between lastTime (exclusive) and <code>time</code> (inclusive).</param>
+		/// <param name="time">The time within the animation. Most timelines find the key before and the key after this time so they can interpolate between the keys.</param>
+		/// <param name="events">If any events are fired, they are added to this list. Can be null to ignore firing events or if the timeline does not fire events. May be null.</param>
+		/// <param name="alpha">alpha 0 applies the current or setup pose value (depending on pose parameter). 1 applies the timeline 
+		/// 	value. Between 0 and 1 applies a value between the current or setup pose and the timeline value. By adjusting
+		/// 	alpha over time, an animation can be mixed in or out. <code>alpha</code> can also be useful to
+		/// 	 apply animations on top of each other (layered).</param>
+		/// <param name="blend">Controls how mixing is applied when alpha is than 1.</param>
+		/// <param name="direction">Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions such as DrawOrderTimeline and AttachmentTimeline.</param>
+		void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> events, float alpha, MixBlend blend, MixDirection direction);
+		int PropertyId { get; }
+	}
+
+	/// <summary>
+	/// Controls how a timeline is mixed with the setup or current pose.</summary>
+	/// <seealso cref="Timeline.Apply(Skeleton, float, float, ExposedList, float, MixBlend, MixDirection)"/>
+	public enum MixBlend {
+
+		/// <summary> Transitions from the setup value to the timeline value (the current value is not used). Before the first key, the setup value is set..</summary>
+		Setup,
+
+		/// <summary>
+		/// <para>Transitions from the current value to the timeline value. Before the first key, transitions from the current value to 
+		/// the setup value. Timelines which perform instant transitions, such as <see cref="DrawOrderTimeline"/> or <see cref="AttachmentTimeline"/>, use the setup value before the first key.</para>
+		/// <para>
+		/// <code>First</code> is intended for the first animations applied, not for animations layered on top of those.</para>
+		/// </summary>
+		First,
+
+		/// <summary>
+		/// <para>
+		/// Transitions from the current value to the timeline value. No change is made before the first key (the current value is kept until the first key).</para>
+		/// <para>
+		/// <code>Replace</code> is intended for animations layered on top of others, not for the first animations applied.</para>
+		/// </summary>
+		Replace,
+
+		/// <summary>
+		/// <para>
+		/// Transitions from the current value to the current value plus the timeline value. No change is made before the first key (the current value is kept until the first key).</para>
+		/// <para>
+		/// <code>Add</code> is intended for animations layered on top of others, not for the first animations applied.</para>
+		/// </summary>
+		Add
+	}
+
+	/// <summary>
+	/// Indicates whether a timeline's <code>alpha</code> is mixing out over time toward 0 (the setup or current pose value) or mixing in toward 1 (the timeline's value).</summary>
+	/// <seealso cref="Timeline.Apply(Skeleton, float, float, ExposedList, float, MixBlend, MixDirection)"/>
+	public enum MixDirection {
+		In,
+		Out
+	}
+
+	internal enum TimelineType {
+		Rotate = 0, Translate, Scale, Shear, //
+		Attachment, Color, Deform, //
+		Event, DrawOrder, //
+		IkConstraint, TransformConstraint, //
+		PathConstraintPosition, PathConstraintSpacing, PathConstraintMix, //
+		TwoColor
+	}
+
+	public interface IBoneTimeline {
+		int BoneIndex { get; }
+	}
+
+	public interface ISlotTimeline {
+		int SlotIndex { get; }
+	}
+
+	/// <summary>Base class for frames that use an interpolation bezier curve.</summary>
+	abstract public class CurveTimeline : Timeline {
+		protected const float LINEAR = 0, STEPPED = 1, BEZIER = 2;
+		protected const int BEZIER_SIZE = 10 * 2 - 1;
+
+		internal float[] curves; // type, x, y, ...
+		public int FrameCount { get { return curves.Length / BEZIER_SIZE + 1; } }
+
+		public CurveTimeline (int frameCount) {
+			if (frameCount <= 0) throw new ArgumentException("frameCount must be > 0: " + frameCount, "frameCount");
+			curves = new float[(frameCount - 1) * BEZIER_SIZE];
+		}
+
+		abstract public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend pose, MixDirection direction);
+
+		abstract public int PropertyId { get; }
+
+		public void SetLinear (int frameIndex) {
+			curves[frameIndex * BEZIER_SIZE] = LINEAR;
+		}
+
+		public void SetStepped (int frameIndex) {
+			curves[frameIndex * BEZIER_SIZE] = STEPPED;
+		}
+
+		/// <summary>Sets the control handle positions for an interpolation bezier curve used to transition from this keyframe to the next.
+		/// cx1 and cx2 are from 0 to 1, representing the percent of time between the two keyframes. cy1 and cy2 are the percent of
+		/// the difference between the keyframe's values.</summary>
+		public void SetCurve (int frameIndex, float cx1, float cy1, float cx2, float cy2) {
+			float tmpx = (-cx1 * 2 + cx2) * 0.03f, tmpy = (-cy1 * 2 + cy2) * 0.03f;
+			float dddfx = ((cx1 - cx2) * 3 + 1) * 0.006f, dddfy = ((cy1 - cy2) * 3 + 1) * 0.006f;
+			float ddfx = tmpx * 2 + dddfx, ddfy = tmpy * 2 + dddfy;
+			float dfx = cx1 * 0.3f + tmpx + dddfx * 0.16666667f, dfy = cy1 * 0.3f + tmpy + dddfy * 0.16666667f;
+
+			int i = frameIndex * BEZIER_SIZE;
+			float[] curves = this.curves;
+			curves[i++] = BEZIER;
+
+			float x = dfx, y = dfy;
+			for (int n = i + BEZIER_SIZE - 1; i < n; i += 2) {
+				curves[i] = x;
+				curves[i + 1] = y;
+				dfx += ddfx;
+				dfy += ddfy;
+				ddfx += dddfx;
+				ddfy += dddfy;
+				x += dfx;
+				y += dfy;
+			}
+		}
+
+		public float GetCurvePercent (int frameIndex, float percent) {
+			percent = MathUtils.Clamp (percent, 0, 1);
+			float[] curves = this.curves;
+			int i = frameIndex * BEZIER_SIZE;
+			float type = curves[i];
+			if (type == LINEAR) return percent;
+			if (type == STEPPED) return 0;
+			i++;
+			float x = 0;
+			for (int start = i, n = i + BEZIER_SIZE - 1; i < n; i += 2) {
+				x = curves[i];
+				if (x >= percent) {
+					float prevX, prevY;
+					if (i == start) {
+						prevX = 0;
+						prevY = 0;
+					} else {
+						prevX = curves[i - 2];
+						prevY = curves[i - 1];
+					}
+					return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX);
+				}
+			}
+			float y = curves[i - 1];
+			return y + (1 - y) * (percent - x) / (1 - x); // Last point is 1,1.
+		}
+		public float GetCurveType (int frameIndex) {
+			return curves[frameIndex * BEZIER_SIZE];
+		}
+	}
+
+	public class RotateTimeline : CurveTimeline, IBoneTimeline {
+		public const int ENTRIES = 2;
+		internal const int PREV_TIME = -2, PREV_ROTATION = -1;
+		internal const int ROTATION = 1;
+
+		internal int boneIndex;
+		internal float[] frames;
+
+		public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } }
+		public float[] Frames { get { return frames; } set { frames = value; } } // time, angle, ...
+
+		override public int PropertyId {
+			get { return ((int)TimelineType.Rotate << 24) + boneIndex; }
+		}
+
+		public RotateTimeline (int frameCount)
+			: base(frameCount) {
+			frames = new float[frameCount << 1];
+		}
+
+		/// <summary>Sets the time and value of the specified keyframe.</summary>
+		public void SetFrame (int frameIndex, float time, float degrees) {
+			frameIndex <<= 1;
+			frames[frameIndex] = time;
+			frames[frameIndex + ROTATION] = degrees;
+		}
+
+		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
+			Bone bone = skeleton.bones.Items[boneIndex];
+
+			float[] frames = this.frames;
+			if (time < frames[0]) {
+				switch (blend) {
+				case MixBlend.Setup:
+					bone.rotation = bone.data.rotation;
+					return;
+				case MixBlend.First:
+						float r = bone.data.rotation - bone.rotation;
+						bone.rotation += (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha;
+						return;
+				}
+				return;
+			}
+
+			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
+				float r = frames[frames.Length + PREV_ROTATION];
+				switch (blend) {
+					case MixBlend.Setup:
+						bone.rotation = bone.data.rotation + r * alpha;
+						break;
+					case MixBlend.First:
+					case MixBlend.Replace:
+						r += bone.data.rotation - bone.rotation;
+						r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360;
+						goto case MixBlend.Add; // Fall through.
+
+					case MixBlend.Add:
+						bone.rotation += r * alpha;
+						break;
+				}
+				return;
+			}
+
+			// Interpolate between the previous frame and the current frame.
+			int frame = Animation.BinarySearch(frames, time, ENTRIES);
+			float prevRotation = frames[frame + PREV_ROTATION];
+			float frameTime = frames[frame];
+			float percent = GetCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+			{
+				float r = frames[frame + ROTATION] - prevRotation;
+				r = prevRotation + (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * percent;
+				switch (blend) {
+					case MixBlend.Setup:
+						bone.rotation = bone.data.rotation + (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha;
+						break;
+					case MixBlend.First:
+					case MixBlend.Replace:
+						r += bone.data.rotation - bone.rotation;
+						goto case MixBlend.Add; // Fall through.
+					case MixBlend.Add:
+						bone.rotation += (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha;
+						break;
+				}
+			}
+			
+		}
+	}
+
+	public class TranslateTimeline : CurveTimeline, IBoneTimeline {
+		public const int ENTRIES = 3;
+		protected const int PREV_TIME = -3, PREV_X = -2, PREV_Y = -1;
+		protected const int X = 1, Y = 2;
+
+		internal int boneIndex;
+		internal float[] frames;
+
+		public int BoneIndex { get { return boneIndex; } set { boneIndex = value; } }
+		public float[] Frames { get { return frames; } set { frames = value; } } // time, value, value, ...
+
+		override public int PropertyId {
+			get { return ((int)TimelineType.Translate << 24) + boneIndex; }
+		}
+
+		public TranslateTimeline (int frameCount)
+			: base(frameCount) {
+			frames = new float[frameCount * ENTRIES];
+		}
+
+		/// <summary>Sets the time and value of the specified keyframe.</summary>
+		public void SetFrame (int frameIndex, float time, float x, float y) {
+			frameIndex *= ENTRIES;
+			frames[frameIndex] = time;
+			frames[frameIndex + X] = x;
+			frames[frameIndex + Y] = y;
+		}
+
+		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
+			Bone bone = skeleton.bones.Items[boneIndex];
+
+			float[] frames = this.frames;
+			if (time < frames[0]) {
+				switch (blend) {
+				case MixBlend.Setup:
+					bone.x = bone.data.x;
+					bone.y = bone.data.y;
+					return;
+				case MixBlend.First:
+					bone.x += (bone.data.x - bone.x) * alpha;
+					bone.y += (bone.data.y - bone.y) * alpha;
+					return;
+				}
+				return;
+			}
+
+			float x, y;
+			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
+				x = frames[frames.Length + PREV_X];
+				y = frames[frames.Length + PREV_Y];
+			} else {
+				// Interpolate between the previous frame and the current frame.
+				int frame = Animation.BinarySearch(frames, time, ENTRIES);
+				x = frames[frame + PREV_X];
+				y = frames[frame + PREV_Y];
+				float frameTime = frames[frame];
+				float percent = GetCurvePercent(frame / ENTRIES - 1,
+					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+
+				x += (frames[frame + X] - x) * percent;
+				y += (frames[frame + Y] - y) * percent;
+			}
+			switch (blend) {
+				case MixBlend.Setup:
+					bone.x = bone.data.x + x * alpha;
+					bone.y = bone.data.y + y * alpha;
+					break;
+				case MixBlend.First:
+				case MixBlend.Replace:
+					bone.x += (bone.data.x + x - bone.x) * alpha;
+					bone.y += (bone.data.y + y - bone.y) * alpha;
+					break;
+				case MixBlend.Add:
+					bone.x += x * alpha;
+					bone.y += y * alpha;
+					break;
+			}
+		}
+	}
+
+	public class ScaleTimeline : TranslateTimeline, IBoneTimeline {
+		override public int PropertyId {
+			get { return ((int)TimelineType.Scale << 24) + boneIndex; }
+		}
+
+		public ScaleTimeline (int frameCount)
+			: base(frameCount) {
+		}
+
+		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
+			Bone bone = skeleton.bones.Items[boneIndex];
+
+			float[] frames = this.frames;
+			if (time < frames[0]) {
+				switch (blend) {
+				case MixBlend.Setup:
+					bone.scaleX = bone.data.scaleX;
+					bone.scaleY = bone.data.scaleY;
+					return;
+				case MixBlend.First:
+					bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha;
+					bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha;
+					return;
+				}
+				return;
+			}
+
+			float x, y;
+			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
+				x = frames[frames.Length + PREV_X] * bone.data.scaleX;
+				y = frames[frames.Length + PREV_Y] * bone.data.scaleY;
+			} else {
+				// Interpolate between the previous frame and the current frame.
+				int frame = Animation.BinarySearch(frames, time, ENTRIES);
+				x = frames[frame + PREV_X];
+				y = frames[frame + PREV_Y];
+				float frameTime = frames[frame];
+				float percent = GetCurvePercent(frame / ENTRIES - 1,
+					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+
+				x = (x + (frames[frame + X] - x) * percent) * bone.data.scaleX;
+				y = (y + (frames[frame + Y] - y) * percent) * bone.data.scaleY;
+			}
+			if (alpha == 1) {
+				if (blend == MixBlend.Add) {
+					bone.scaleX += x - bone.data.scaleX;
+					bone.scaleY += y - bone.data.scaleY;
+				} else {
+					bone.scaleX = x;
+					bone.scaleY = y;
+				}
+			} else {
+				// Mixing out uses sign of setup or current pose, else use sign of key.
+				float bx, by;
+				if (direction == MixDirection.Out) {
+					switch (blend) {
+						case MixBlend.Setup:
+							bx = bone.data.scaleX;
+							by = bone.data.scaleY;
+							bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha;
+							bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha;
+							break;
+						case MixBlend.First:
+						case MixBlend.Replace:
+							bx = bone.scaleX;
+							by = bone.scaleY;
+							bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bx) * alpha;
+							bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - by) * alpha;
+							break;
+						case MixBlend.Add:
+							bx = bone.scaleX;
+							by = bone.scaleY;
+							bone.scaleX = bx + (Math.Abs(x) * Math.Sign(bx) - bone.data.scaleX) * alpha;
+							bone.scaleY = by + (Math.Abs(y) * Math.Sign(by) - bone.data.scaleY) * alpha;
+							break;
+					}
+				} else {
+					switch (blend) {
+						case MixBlend.Setup:
+							bx = Math.Abs(bone.data.scaleX) * Math.Sign(x);
+							by = Math.Abs(bone.data.scaleY) * Math.Sign(y);
+							bone.scaleX = bx + (x - bx) * alpha;
+							bone.scaleY = by + (y - by) * alpha;
+							break;
+						case MixBlend.First:
+						case MixBlend.Replace:
+							bx = Math.Abs(bone.scaleX) * Math.Sign(x);
+							by = Math.Abs(bone.scaleY) * Math.Sign(y);
+							bone.scaleX = bx + (x - bx) * alpha;
+							bone.scaleY = by + (y - by) * alpha;
+							break;
+						case MixBlend.Add:
+							bx = Math.Sign(x);
+							by = Math.Sign(y);
+							bone.scaleX = Math.Abs(bone.scaleX) * bx + (x - Math.Abs(bone.data.scaleX) * bx) * alpha;
+							bone.scaleY = Math.Abs(bone.scaleY) * by + (y - Math.Abs(bone.data.scaleY) * by) * alpha;
+							break;
+					}
+				}
+			}
+		}
+	}
+
+	public class ShearTimeline : TranslateTimeline, IBoneTimeline {
+		override public int PropertyId {
+			get { return ((int)TimelineType.Shear << 24) + boneIndex; }
+		}
+
+		public ShearTimeline (int frameCount)
+			: base(frameCount) {
+		}
+
+		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
+			Bone bone = skeleton.bones.Items[boneIndex];
+			float[] frames = this.frames;
+			if (time < frames[0]) {
+				switch (blend) {
+				case MixBlend.Setup:
+					bone.shearX = bone.data.shearX;
+					bone.shearY = bone.data.shearY;
+					return;
+				case MixBlend.First:
+					bone.shearX += (bone.data.shearX - bone.shearX) * alpha;
+					bone.shearY += (bone.data.shearY - bone.shearY) * alpha;
+					return;
+				}
+				return;
+			}
+
+			float x, y;
+			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
+				x = frames[frames.Length + PREV_X];
+				y = frames[frames.Length + PREV_Y];
+			} else {
+				// Interpolate between the previous frame and the current frame.
+				int frame = Animation.BinarySearch(frames, time, ENTRIES);
+				x = frames[frame + PREV_X];
+				y = frames[frame + PREV_Y];
+				float frameTime = frames[frame];
+				float percent = GetCurvePercent(frame / ENTRIES - 1,
+					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+
+				x = x + (frames[frame + X] - x) * percent;
+				y = y + (frames[frame + Y] - y) * percent;
+			}
+			switch (blend) {
+				case MixBlend.Setup:
+					bone.shearX = bone.data.shearX + x * alpha;
+					bone.shearY = bone.data.shearY + y * alpha;
+					break;
+				case MixBlend.First:
+				case MixBlend.Replace:
+					bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha;
+					bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha;
+					break;
+				case MixBlend.Add:
+					bone.shearX += x * alpha;
+					bone.shearY += y * alpha;
+					break;
+			}
+		}
+	}
+
+	public class ColorTimeline : CurveTimeline, ISlotTimeline {
+		public const int ENTRIES = 5;
+		protected const int PREV_TIME = -5, PREV_R = -4, PREV_G = -3, PREV_B = -2, PREV_A = -1;
+		protected const int R = 1, G = 2, B = 3, A = 4;
+
+		internal int slotIndex;
+		internal float[] frames;
+
+		public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } }
+		public float[] Frames { get { return frames; } set { frames = value; } } // time, r, g, b, a, ...
+
+		override public int PropertyId {
+			get { return ((int)TimelineType.Color << 24) + slotIndex; }
+		}
+
+		public ColorTimeline (int frameCount)
+			: base(frameCount) {
+			frames = new float[frameCount * ENTRIES];
+		}
+
+		/// <summary>Sets the time and value of the specified keyframe.</summary>
+		public void SetFrame (int frameIndex, float time, float r, float g, float b, float a) {
+			frameIndex *= ENTRIES;
+			frames[frameIndex] = time;
+			frames[frameIndex + R] = r;
+			frames[frameIndex + G] = g;
+			frames[frameIndex + B] = b;
+			frames[frameIndex + A] = a;
+		}
+
+		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
+			Slot slot = skeleton.slots.Items[slotIndex];
+			float[] frames = this.frames;
+			if (time < frames[0]) {
+				var slotData = slot.data;
+				switch (blend) {
+				case MixBlend.Setup:
+					slot.r = slotData.r;
+					slot.g = slotData.g;
+					slot.b = slotData.b;
+					slot.a = slotData.a;
+					return;
+				case MixBlend.First:
+					slot.r += (slotData.r - slot.r) * alpha;
+					slot.g += (slotData.g - slot.g) * alpha;
+					slot.b += (slotData.b - slot.b) * alpha;
+					slot.a += (slotData.a - slot.a) * alpha;
+					return;
+				}
+				return;
+			}
+
+			float r, g, b, a;
+			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
+				int i = frames.Length;
+				r = frames[i + PREV_R];
+				g = frames[i + PREV_G];
+				b = frames[i + PREV_B];
+				a = frames[i + PREV_A];
+			} else {
+				// Interpolate between the previous frame and the current frame.
+				int frame = Animation.BinarySearch(frames, time, ENTRIES);
+				r = frames[frame + PREV_R];
+				g = frames[frame + PREV_G];
+				b = frames[frame + PREV_B];
+				a = frames[frame + PREV_A];
+				float frameTime = frames[frame];
+				float percent = GetCurvePercent(frame / ENTRIES - 1,
+					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+
+				r += (frames[frame + R] - r) * percent;
+				g += (frames[frame + G] - g) * percent;
+				b += (frames[frame + B] - b) * percent;
+				a += (frames[frame + A] - a) * percent;
+			}
+			if (alpha == 1) {
+				slot.r = r;
+				slot.g = g;
+				slot.b = b;
+				slot.a = a;
+			} else {
+				float br, bg, bb, ba;
+				if (blend == MixBlend.Setup) {
+					br = slot.data.r;
+					bg = slot.data.g;
+					bb = slot.data.b;
+					ba = slot.data.a;
+				} else {
+					br = slot.r;
+					bg = slot.g;
+					bb = slot.b;
+					ba = slot.a;
+				}
+				slot.r = br + ((r - br) * alpha);
+				slot.g = bg + ((g - bg) * alpha);
+				slot.b = bb + ((b - bb) * alpha);
+				slot.a = ba + ((a - ba) * alpha);
+			}
+		}
+	}
+
+	public class TwoColorTimeline : CurveTimeline, ISlotTimeline {
+		public const int ENTRIES = 8;
+		protected const int PREV_TIME = -8, PREV_R = -7, PREV_G = -6, PREV_B = -5, PREV_A = -4;
+		protected const int PREV_R2 = -3, PREV_G2 = -2, PREV_B2 = -1;
+		protected const int R = 1, G = 2, B = 3, A = 4, R2 = 5, G2 = 6, B2 = 7;
+
+		internal int slotIndex;
+		internal float[] frames; // time, r, g, b, a, r2, g2, b2, ...
+
+		public int SlotIndex {
+			get { return slotIndex; }
+			set {
+				if (value < 0)
+					throw new ArgumentOutOfRangeException("index must be >= 0.");
+				slotIndex = value;
+			}
+		}
+		
+		public float[] Frames { get { return frames; } }
+
+		override public int PropertyId {
+			get { return ((int)TimelineType.TwoColor << 24) + slotIndex; }
+		}
+
+		public TwoColorTimeline (int frameCount) :
+			base(frameCount) {
+			frames = new float[frameCount * ENTRIES];
+		}
+
+		/// <summary>Sets the time and value of the specified keyframe.</summary>
+		public void SetFrame (int frameIndex, float time, float r, float g, float b, float a, float r2, float g2, float b2) {
+			frameIndex *= ENTRIES;
+			frames[frameIndex] = time;
+			frames[frameIndex + R] = r;
+			frames[frameIndex + G] = g;
+			frames[frameIndex + B] = b;
+			frames[frameIndex + A] = a;
+			frames[frameIndex + R2] = r2;
+			frames[frameIndex + G2] = g2;
+			frames[frameIndex + B2] = b2;
+		}
+
+		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
+			Slot slot = skeleton.slots.Items[slotIndex];
+			float[] frames = this.frames;
+			if (time < frames[0]) { // Time is before first frame.
+				var slotData = slot.data;
+				switch (blend) {
+				case MixBlend.Setup:
+					//	slot.color.set(slot.data.color);
+					//	slot.darkColor.set(slot.data.darkColor);
+					slot.r = slotData.r;
+					slot.g = slotData.g;
+					slot.b = slotData.b;
+					slot.a = slotData.a;
+					slot.r2 = slotData.r2;
+					slot.g2 = slotData.g2;
+					slot.b2 = slotData.b2;
+					return;
+				case MixBlend.First:
+					slot.r += (slot.r - slotData.r) * alpha;
+					slot.g += (slot.g - slotData.g) * alpha;
+					slot.b += (slot.b - slotData.b) * alpha;
+					slot.a += (slot.a - slotData.a) * alpha;
+					slot.r2 += (slot.r2 - slotData.r2) * alpha;
+					slot.g2 += (slot.g2 - slotData.g2) * alpha;
+					slot.b2 += (slot.b2 - slotData.b2) * alpha;
+					return;
+				}
+				return;
+			}
+
+			float r, g, b, a, r2, g2, b2;
+			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
+				int i = frames.Length;
+				r = frames[i + PREV_R];
+				g = frames[i + PREV_G];
+				b = frames[i + PREV_B];
+				a = frames[i + PREV_A];
+				r2 = frames[i + PREV_R2];
+				g2 = frames[i + PREV_G2];
+				b2 = frames[i + PREV_B2];
+			} else {
+				// Interpolate between the previous frame and the current frame.
+				int frame = Animation.BinarySearch(frames, time, ENTRIES);
+				r = frames[frame + PREV_R];
+				g = frames[frame + PREV_G];
+				b = frames[frame + PREV_B];
+				a = frames[frame + PREV_A];
+				r2 = frames[frame + PREV_R2];
+				g2 = frames[frame + PREV_G2];
+				b2 = frames[frame + PREV_B2];
+				float frameTime = frames[frame];
+				float percent = GetCurvePercent(frame / ENTRIES - 1,
+					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+
+				r += (frames[frame + R] - r) * percent;
+				g += (frames[frame + G] - g) * percent;
+				b += (frames[frame + B] - b) * percent;
+				a += (frames[frame + A] - a) * percent;
+				r2 += (frames[frame + R2] - r2) * percent;
+				g2 += (frames[frame + G2] - g2) * percent;
+				b2 += (frames[frame + B2] - b2) * percent;
+			}
+			if (alpha == 1) {
+				slot.r = r;
+				slot.g = g;
+				slot.b = b;
+				slot.a = a;
+				slot.r2 = r2;
+				slot.g2 = g2;
+				slot.b2 = b2;
+			} else {
+				float br, bg, bb, ba, br2, bg2, bb2;
+				if (blend == MixBlend.Setup) {
+					br = slot.data.r;
+					bg = slot.data.g;
+					bb = slot.data.b;
+					ba = slot.data.a;
+					br2 = slot.data.r2;
+					bg2 = slot.data.g2;
+					bb2 = slot.data.b2;
+				} else {
+					br = slot.r;
+					bg = slot.g;
+					bb = slot.b;
+					ba = slot.a;
+					br2 = slot.r2;
+					bg2 = slot.g2;
+					bb2 = slot.b2;
+				}
+				slot.r = br + ((r - br) * alpha);
+				slot.g = bg + ((g - bg) * alpha);
+				slot.b = bb + ((b - bb) * alpha);
+				slot.a = ba + ((a - ba) * alpha);
+				slot.r2 = br2 + ((r2 - br2) * alpha);
+				slot.g2 = bg2 + ((g2 - bg2) * alpha);
+				slot.b2 = bb2 + ((b2 - bb2) * alpha);
+			}
+		}
+
+	}
+
+	public class AttachmentTimeline : Timeline, ISlotTimeline {
+		internal int slotIndex;
+		internal float[] frames;
+		internal string[] attachmentNames;
+
+		public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } }
+		public float[] Frames { get { return frames; } set { frames = value; } } // time, ...
+		public string[] AttachmentNames { get { return attachmentNames; } set { attachmentNames = value; } }
+		public int FrameCount { get { return frames.Length; } }
+
+		public int PropertyId {
+			get { return ((int)TimelineType.Attachment << 24) + slotIndex; }
+		}
+
+		public AttachmentTimeline (int frameCount) {
+			frames = new float[frameCount];
+			attachmentNames = new String[frameCount];
+		}
+
+		/// <summary>Sets the time and value of the specified keyframe.</summary>
+		public void SetFrame (int frameIndex, float time, String attachmentName) {
+			frames[frameIndex] = time;
+			attachmentNames[frameIndex] = attachmentName;
+		}
+
+		public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
+			string attachmentName;
+			Slot slot = skeleton.slots.Items[slotIndex];
+			if (direction == MixDirection.Out && blend == MixBlend.Setup) {
+				attachmentName = slot.data.attachmentName;
+				slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName);
+				return;
+			}
+
+			float[] frames = this.frames;
+			if (time < frames[0]) { // Time is before first frame.
+				if (blend == MixBlend.Setup || blend == MixBlend.First) {
+					attachmentName = slot.data.attachmentName;
+					slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName);
+				}
+				return;
+			}
+
+			int frameIndex;
+			if (time >= frames[frames.Length - 1]) // Time is after last frame.
+				frameIndex = frames.Length - 1;
+			else
+				frameIndex = Animation.BinarySearch(frames, time, 1) - 1;
+
+			attachmentName = attachmentNames[frameIndex];
+			slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName);
+		}
+	}
+
+	public class DeformTimeline : CurveTimeline, ISlotTimeline {
+		internal int slotIndex;
+		internal float[] frames;
+		internal float[][] frameVertices;
+		internal VertexAttachment attachment;
+
+		public int SlotIndex { get { return slotIndex; } set { slotIndex = value; } }
+		public float[] Frames { get { return frames; } set { frames = value; } } // time, ...
+		public float[][] Vertices { get { return frameVertices; } set { frameVertices = value; } }
+		public VertexAttachment Attachment { get { return attachment; } set { attachment = value; } }
+
+		override public int PropertyId {
+			get { return ((int)TimelineType.Deform << 24) + attachment.id + slotIndex; }
+		}
+
+		public DeformTimeline (int frameCount)
+			: base(frameCount) {
+			frames = new float[frameCount];
+			frameVertices = new float[frameCount][];
+		}
+
+		/// <summary>Sets the time and value of the specified keyframe.</summary>
+		public void SetFrame (int frameIndex, float time, float[] vertices) {
+			frames[frameIndex] = time;
+			frameVertices[frameIndex] = vertices;
+		}
+
+		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
+			Slot slot = skeleton.slots.Items[slotIndex];
+			VertexAttachment vertexAttachment = slot.attachment as VertexAttachment;
+			if (vertexAttachment == null || !vertexAttachment.ApplyDeform(attachment)) return;
+
+			var verticesArray = slot.attachmentVertices;
+			if (verticesArray.Count == 0) blend = MixBlend.Setup;
+
+			float[][] frameVertices = this.frameVertices;
+			int vertexCount = frameVertices[0].Length;
+			float[] frames = this.frames;
+			float[] vertices;
+
+			if (time < frames[0]) {
+				
+				switch (blend) {
+				case MixBlend.Setup:
+					verticesArray.Clear();
+					return;
+				case MixBlend.Replace:
+					if (alpha == 1) {
+						verticesArray.Clear();
+						return;
+					}
+
+					// verticesArray.SetSize(vertexCount) // Ensure size and preemptively set count.
+					if (verticesArray.Capacity < vertexCount) verticesArray.Capacity = vertexCount;	
+					verticesArray.Count = vertexCount;
+					vertices = verticesArray.Items;
+
+					if (vertexAttachment.bones == null) {
+						// Unweighted vertex positions.
+						float[] setupVertices = vertexAttachment.vertices;
+						for (int i = 0; i < vertexCount; i++)
+							vertices[i] += (setupVertices[i] - vertices[i]) * alpha;
+					} else {
+						// Weighted deform offsets.
+						alpha = 1 - alpha;
+						for (int i = 0; i < vertexCount; i++)
+							vertices[i] *= alpha;
+					}
+					return;
+				default:
+					return;
+				}
+
+			}
+
+			// verticesArray.SetSize(vertexCount) // Ensure size and preemptively set count.
+			if (verticesArray.Capacity < vertexCount) verticesArray.Capacity = vertexCount;	
+			verticesArray.Count = vertexCount;
+			vertices = verticesArray.Items;
+
+			if (time >= frames[frames.Length - 1]) { // Time is after last frame.
+
+				float[] lastVertices = frameVertices[frames.Length - 1];
+				if (alpha == 1) {
+					if (blend == MixBlend.Add) {
+						if (vertexAttachment.bones == null) {
+							// Unweighted vertex positions, no alpha.
+							float[] setupVertices = vertexAttachment.vertices;
+							for (int i = 0; i < vertexCount; i++)
+								vertices[i] += lastVertices[i] - setupVertices[i];
+						} else {
+							// Weighted deform offsets, no alpha.
+							for (int i = 0; i < vertexCount; i++)
+								vertices[i] += lastVertices[i];
+						}
+					} else {
+						// Vertex positions or deform offsets, no alpha.
+						Array.Copy(lastVertices, 0, vertices, 0, vertexCount);
+					}
+				} else {
+					switch (blend) {
+						case MixBlend.Setup: {
+							if (vertexAttachment.bones == null) {
+								// Unweighted vertex positions, with alpha.
+								float[] setupVertices = vertexAttachment.vertices;
+								for (int i = 0; i < vertexCount; i++) {
+									float setup = setupVertices[i];
+									vertices[i] = setup + (lastVertices[i] - setup) * alpha;
+								}
+							} else {
+								// Weighted deform offsets, with alpha.
+								for (int i = 0; i < vertexCount; i++)
+									vertices[i] = lastVertices[i] * alpha;
+							}
+							break;
+						}
+						case MixBlend.First:
+						case MixBlend.Replace:
+							// Vertex positions or deform offsets, with alpha.
+							for (int i = 0; i < vertexCount; i++)
+								vertices[i] += (lastVertices[i] - vertices[i]) * alpha;
+							break;
+						case MixBlend.Add:
+							if (vertexAttachment.bones == null) {
+								// Unweighted vertex positions, no alpha.
+								float[] setupVertices = vertexAttachment.vertices;
+								for (int i = 0; i < vertexCount; i++)
+									vertices[i] += (lastVertices[i] - setupVertices[i]) * alpha;
+							} else {
+								// Weighted deform offsets, alpha.
+								for (int i = 0; i < vertexCount; i++)
+									vertices[i] += lastVertices[i] * alpha;
+							}
+							break;
+					}
+				}
+				return;
+			}
+
+			// Interpolate between the previous frame and the current frame.
+			int frame = Animation.BinarySearch(frames, time);
+			float[] prevVertices = frameVertices[frame - 1];
+			float[] nextVertices = frameVertices[frame];
+			float frameTime = frames[frame];
+			float percent = GetCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime));
+
+			if (alpha == 1) {
+				if (blend == MixBlend.Add) {
+					if (vertexAttachment.bones == null) {
+						// Unweighted vertex positions, no alpha.
+						float[] setupVertices = vertexAttachment.vertices;
+						for (int i = 0; i < vertexCount; i++) {
+							float prev = prevVertices[i];
+							vertices[i] += prev + (nextVertices[i] - prev) * percent - setupVertices[i];
+						}
+					} else {
+						// Weighted deform offsets, no alpha.
+						for (int i = 0; i < vertexCount; i++) {
+							float prev = prevVertices[i];
+							vertices[i] += prev + (nextVertices[i] - prev) * percent;
+						}
+					}
+				} else {
+					// Vertex positions or deform offsets, no alpha.
+					for (int i = 0; i < vertexCount; i++) {
+						float prev = prevVertices[i];
+						vertices[i] = prev + (nextVertices[i] - prev) * percent;
+					}
+				}
+			} else {
+				switch (blend) {
+					case MixBlend.Setup: {
+						if (vertexAttachment.bones == null) {
+							// Unweighted vertex positions, with alpha.
+							float[] setupVertices = vertexAttachment.vertices;
+							for (int i = 0; i < vertexCount; i++) {
+								float prev = prevVertices[i], setup = setupVertices[i];
+								vertices[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha;
+							}
+						} else {
+							// Weighted deform offsets, with alpha.
+							for (int i = 0; i < vertexCount; i++) {
+								float prev = prevVertices[i];
+								vertices[i] = (prev + (nextVertices[i] - prev) * percent) * alpha;
+							}
+						}
+						break;
+					}
+					case MixBlend.First:
+					case MixBlend.Replace: {
+						// Vertex positions or deform offsets, with alpha.
+						for (int i = 0; i < vertexCount; i++) {
+							float prev = prevVertices[i];
+							vertices[i] += (prev + (nextVertices[i] - prev) * percent - vertices[i]) * alpha;
+						}
+						break;
+					}
+					case MixBlend.Add: {
+						if (vertexAttachment.bones == null) {
+							// Unweighted vertex positions, with alpha.
+							float[] setupVertices = vertexAttachment.vertices;
+							for (int i = 0; i < vertexCount; i++) {
+								float prev = prevVertices[i];
+								vertices[i] += (prev + (nextVertices[i] - prev) * percent - setupVertices[i]) * alpha;
+							}
+						} else {
+							// Weighted deform offsets, with alpha.
+							for (int i = 0; i < vertexCount; i++) {
+								float prev = prevVertices[i];
+								vertices[i] += (prev + (nextVertices[i] - prev) * percent) * alpha;
+							}
+						}
+						break;
+					}
+				}
+			}
+		}
+	}
+
+	public class EventTimeline : Timeline {
+		internal float[] frames;
+		private Event[] events;
+
+		public float[] Frames { get { return frames; } set { frames = value; } } // time, ...
+		public Event[] Events { get { return events; } set { events = value; } }
+		public int FrameCount { get { return frames.Length; } }
+
+		public int PropertyId {
+			get { return ((int)TimelineType.Event << 24); }
+		}
+
+		public EventTimeline (int frameCount) {
+			frames = new float[frameCount];
+			events = new Event[frameCount];
+		}
+
+		/// <summary>Sets the time and value of the specified keyframe.</summary>
+		public void SetFrame (int frameIndex, Event e) {
+			frames[frameIndex] = e.Time;
+			events[frameIndex] = e;
+		}
+
+		/// <summary>Fires events for frames &gt; lastTime and &lt;= time.</summary>
+		public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
+			if (firedEvents == null) return;
+			float[] frames = this.frames;
+			int frameCount = frames.Length;
+
+			if (lastTime > time) { // Fire events after last time for looped animations.
+				Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha, blend, direction);
+				lastTime = -1f;
+			} else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame.
+				return;
+			if (time < frames[0]) return; // Time is before first frame.
+
+			int frame;
+			if (lastTime < frames[0])
+				frame = 0;
+			else {
+				frame = Animation.BinarySearch(frames, lastTime);
+				float frameTime = frames[frame];
+				while (frame > 0) { // Fire multiple events with the same frame.
+					if (frames[frame - 1] != frameTime) break;
+					frame--;
+				}
+			}
+			for (; frame < frameCount && time >= frames[frame]; frame++)
+				firedEvents.Add(events[frame]);
+		}
+	}
+
+	public class DrawOrderTimeline : Timeline {
+		internal float[] frames;
+		private int[][] drawOrders;
+
+		public float[] Frames { get { return frames; } set { frames = value; } } // time, ...
+		public int[][] DrawOrders { get { return drawOrders; } set { drawOrders = value; } }
+		public int FrameCount { get { return frames.Length; } }
+
+		public int PropertyId {
+			get { return ((int)TimelineType.DrawOrder << 24); }
+		}
+
+		public DrawOrderTimeline (int frameCount) {
+			frames = new float[frameCount];
+			drawOrders = new int[frameCount][];
+		}
+
+		/// <summary>Sets the time and value of the specified keyframe.</summary>
+		/// <param name="drawOrder">May be null to use bind pose draw order.</param>
+		public void SetFrame (int frameIndex, float time, int[] drawOrder) {
+			frames[frameIndex] = time;
+			drawOrders[frameIndex] = drawOrder;
+		}
+
+		public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
+			ExposedList<Slot> drawOrder = skeleton.drawOrder;
+			ExposedList<Slot> slots = skeleton.slots;
+			if (direction == MixDirection.Out && blend == MixBlend.Setup) {
+				Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count);
+				return;
+			}
+
+			float[] frames = this.frames;
+			if (time < frames[0]) {
+				if (blend == MixBlend.Setup || blend == MixBlend.First) Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count);
+				return;
+			}
+
+			int frame;
+			if (time >= frames[frames.Length - 1]) // Time is after last frame.
+				frame = frames.Length - 1;
+			else
+				frame = Animation.BinarySearch(frames, time) - 1;
+			
+			int[] drawOrderToSetupIndex = drawOrders[frame];
+			if (drawOrderToSetupIndex == null) {
+				Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count);
+			} else {
+				var drawOrderItems = drawOrder.Items;
+				var slotsItems = slots.Items;
+				for (int i = 0, n = drawOrderToSetupIndex.Length; i < n; i++)
+					drawOrderItems[i] = slotsItems[drawOrderToSetupIndex[i]];
+			}
+		}
+	}
+
+	public class IkConstraintTimeline : CurveTimeline {
+		public const int ENTRIES = 5;
+		private const int PREV_TIME = -5, PREV_MIX = -4, PREV_BEND_DIRECTION = -3, PREV_COMPRESS = -2, PREV_STRETCH = -1;
+		private const int MIX = 1, BEND_DIRECTION = 2, COMPRESS = 3, STRETCH = 4;
+
+		internal int ikConstraintIndex;
+		internal float[] frames;
+
+		public int IkConstraintIndex { get { return ikConstraintIndex; } set { ikConstraintIndex = value; } }
+		public float[] Frames { get { return frames; } set { frames = value; } } // time, mix, bendDirection, compress, stretch ...
+
+		override public int PropertyId {
+			get { return ((int)TimelineType.IkConstraint << 24) + ikConstraintIndex; }
+		}
+
+		public IkConstraintTimeline (int frameCount)
+			: base(frameCount) {
+			frames = new float[frameCount * ENTRIES];
+		}
+			
+		/// <summary>Sets the time, mix, bend direction, compress and stretch of the specified keyframe.</summary>
+		public void SetFrame (int frameIndex, float time, float mix, int bendDirection, bool compress, bool stretch) {
+			frameIndex *= ENTRIES;
+			frames[frameIndex] = time;
+			frames[frameIndex + MIX] = mix;
+			frames[frameIndex + BEND_DIRECTION] = bendDirection;
+			frames[frameIndex + COMPRESS] = compress ? 1 : 0;
+			frames[frameIndex + STRETCH] = stretch ? 1 : 0;
+		}
+
+		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
+			IkConstraint constraint = skeleton.ikConstraints.Items[ikConstraintIndex];
+			float[] frames = this.frames;
+			if (time < frames[0]) {
+				switch (blend) {
+				case MixBlend.Setup:
+					constraint.mix = constraint.data.mix;
+					constraint.bendDirection = constraint.data.bendDirection;
+					constraint.compress = constraint.data.compress;
+					constraint.stretch = constraint.data.stretch;
+					return;
+				case MixBlend.First:
+					constraint.mix += (constraint.data.mix - constraint.mix) * alpha;
+					constraint.bendDirection = constraint.data.bendDirection;
+					constraint.compress = constraint.data.compress;
+					constraint.stretch = constraint.data.stretch;
+					return;
+				}
+				return;
+			}
+
+			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
+				if (blend == MixBlend.Setup) {
+					constraint.mix = constraint.data.mix + (frames[frames.Length + PREV_MIX] - constraint.data.mix) * alpha;
+					if (direction == MixDirection.Out) {
+						constraint.bendDirection = constraint.data.bendDirection;
+						constraint.compress = constraint.data.compress;
+						constraint.stretch = constraint.data.stretch;
+					} else {
+						constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION];
+						constraint.compress = frames[frames.Length + PREV_COMPRESS] != 0;
+						constraint.stretch = frames[frames.Length + PREV_STRETCH] != 0;
+					}
+				} else {
+					constraint.mix += (frames[frames.Length + PREV_MIX] - constraint.mix) * alpha;
+					if (direction == MixDirection.In) {
+						constraint.bendDirection = (int)frames[frames.Length + PREV_BEND_DIRECTION];
+						constraint.compress = frames[frames.Length + PREV_COMPRESS] != 0;
+						constraint.stretch = frames[frames.Length + PREV_STRETCH] != 0;
+					}
+				}
+				return;
+			}
+
+			// Interpolate between the previous frame and the current frame.
+			int frame = Animation.BinarySearch(frames, time, ENTRIES);
+			float mix = frames[frame + PREV_MIX];
+			float frameTime = frames[frame];
+			float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+
+			if (blend == MixBlend.Setup) {
+				constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha;
+				if (direction == MixDirection.Out) {
+					constraint.bendDirection = constraint.data.bendDirection;
+					constraint.compress = constraint.data.compress;
+					constraint.stretch = constraint.data.stretch;
+				} else {
+					constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION];
+					constraint.compress = frames[frame + PREV_COMPRESS] != 0;
+					constraint.stretch = frames[frame + PREV_STRETCH] != 0;
+				}
+			} else {
+				constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha;
+				if (direction == MixDirection.In) {
+					constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION];
+					constraint.compress = frames[frame + PREV_COMPRESS] != 0;
+					constraint.stretch = frames[frame + PREV_STRETCH] != 0;
+				}
+			}
+		}
+	}
+
+	public class TransformConstraintTimeline : CurveTimeline {
+		public const int ENTRIES = 5;
+		private const int PREV_TIME = -5, PREV_ROTATE = -4, PREV_TRANSLATE = -3, PREV_SCALE = -2, PREV_SHEAR = -1;
+		private const int ROTATE = 1, TRANSLATE = 2, SCALE = 3, SHEAR = 4;
+
+		internal int transformConstraintIndex;
+		internal float[] frames;
+
+		public int TransformConstraintIndex { get { return transformConstraintIndex; } set { transformConstraintIndex = value; } }
+		public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, scale mix, shear mix, ...
+
+		override public int PropertyId {
+			get { return ((int)TimelineType.TransformConstraint << 24) + transformConstraintIndex; }
+		}
+
+		public TransformConstraintTimeline (int frameCount)
+			: base(frameCount) {
+			frames = new float[frameCount * ENTRIES];
+		}
+
+		public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix, float scaleMix, float shearMix) {
+			frameIndex *= ENTRIES;
+			frames[frameIndex] = time;
+			frames[frameIndex + ROTATE] = rotateMix;
+			frames[frameIndex + TRANSLATE] = translateMix;
+			frames[frameIndex + SCALE] = scaleMix;
+			frames[frameIndex + SHEAR] = shearMix;
+		}
+
+		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
+			TransformConstraint constraint = skeleton.transformConstraints.Items[transformConstraintIndex];
+			float[] frames = this.frames;
+			if (time < frames[0]) {
+				var data = constraint.data;
+				switch (blend) {
+				case MixBlend.Setup:
+					constraint.rotateMix = data.rotateMix;
+					constraint.translateMix = data.translateMix;
+					constraint.scaleMix = data.scaleMix;
+					constraint.shearMix = data.shearMix;
+					return;
+				case MixBlend.First:
+					constraint.rotateMix += (data.rotateMix - constraint.rotateMix) * alpha;
+					constraint.translateMix += (data.translateMix - constraint.translateMix) * alpha;
+					constraint.scaleMix += (data.scaleMix - constraint.scaleMix) * alpha;
+					constraint.shearMix += (data.shearMix - constraint.shearMix) * alpha;
+					return;
+				}
+				return;
+			}
+
+			float rotate, translate, scale, shear;
+			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
+				int i = frames.Length;
+				rotate = frames[i + PREV_ROTATE];
+				translate = frames[i + PREV_TRANSLATE];
+				scale = frames[i + PREV_SCALE];
+				shear = frames[i + PREV_SHEAR];
+			} else {
+				// Interpolate between the previous frame and the current frame.
+				int frame = Animation.BinarySearch(frames, time, ENTRIES);
+				rotate = frames[frame + PREV_ROTATE];
+				translate = frames[frame + PREV_TRANSLATE];
+				scale = frames[frame + PREV_SCALE];
+				shear = frames[frame + PREV_SHEAR];
+				float frameTime = frames[frame];
+				float percent = GetCurvePercent(frame / ENTRIES - 1,
+					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+
+				rotate += (frames[frame + ROTATE] - rotate) * percent;
+				translate += (frames[frame + TRANSLATE] - translate) * percent;
+				scale += (frames[frame + SCALE] - scale) * percent;
+				shear += (frames[frame + SHEAR] - shear) * percent;
+			}
+			if (blend == MixBlend.Setup) {
+				TransformConstraintData data = constraint.data;
+				constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha;
+				constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha;
+				constraint.scaleMix = data.scaleMix + (scale - data.scaleMix) * alpha;
+				constraint.shearMix = data.shearMix + (shear - data.shearMix) * alpha;
+			} else {
+				constraint.rotateMix += (rotate - constraint.rotateMix) * alpha;
+				constraint.translateMix += (translate - constraint.translateMix) * alpha;
+				constraint.scaleMix += (scale - constraint.scaleMix) * alpha;
+				constraint.shearMix += (shear - constraint.shearMix) * alpha;
+			}
+		}
+	}
+
+	public class PathConstraintPositionTimeline : CurveTimeline {
+		public const int ENTRIES = 2;
+		protected const int PREV_TIME = -2, PREV_VALUE = -1;
+		protected const int VALUE = 1;
+
+		internal int pathConstraintIndex;
+		internal float[] frames;
+
+		override public int PropertyId {
+			get { return ((int)TimelineType.PathConstraintPosition << 24) + pathConstraintIndex; }
+		}
+
+		public PathConstraintPositionTimeline (int frameCount)
+			: base(frameCount) {
+			frames = new float[frameCount * ENTRIES];
+		}
+
+		public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } }
+		public float[] Frames { get { return frames; } set { frames = value; } } // time, position, ...
+
+		/// <summary>Sets the time and value of the specified keyframe.</summary>
+		public void SetFrame (int frameIndex, float time, float value) {
+			frameIndex *= ENTRIES;
+			frames[frameIndex] = time;
+			frames[frameIndex + VALUE] = value;
+		}
+
+		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
+			PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
+			float[] frames = this.frames;
+			if (time < frames[0]) {
+				switch (blend) {
+				case MixBlend.Setup:
+					constraint.position = constraint.data.position;
+					return;
+				case MixBlend.First:
+					constraint.position += (constraint.data.position - constraint.position) * alpha;
+					return;
+				}
+				return;
+			}
+
+			float position;
+			if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame.
+				position = frames[frames.Length + PREV_VALUE];
+			else {
+				// Interpolate between the previous frame and the current frame.
+				int frame = Animation.BinarySearch(frames, time, ENTRIES);
+				position = frames[frame + PREV_VALUE];
+				float frameTime = frames[frame];
+				float percent = GetCurvePercent(frame / ENTRIES - 1,
+					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+
+				position += (frames[frame + VALUE] - position) * percent;
+			}
+			if (blend == MixBlend.Setup)
+				constraint.position = constraint.data.position + (position - constraint.data.position) * alpha;
+			else
+				constraint.position += (position - constraint.position) * alpha;
+		}
+	}
+
+	public class PathConstraintSpacingTimeline : PathConstraintPositionTimeline {
+		override public int PropertyId {
+			get { return ((int)TimelineType.PathConstraintSpacing << 24) + pathConstraintIndex; }
+		}
+
+		public PathConstraintSpacingTimeline (int frameCount)
+			: base(frameCount) {
+		}
+
+		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
+			PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
+			float[] frames = this.frames;
+			if (time < frames[0]) {
+				switch (blend) {
+				case MixBlend.Setup:
+					constraint.spacing = constraint.data.spacing;
+					return;
+				case MixBlend.First:
+					constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha;
+					return;
+				}
+				return;
+			}
+
+			float spacing;
+			if (time >= frames[frames.Length - ENTRIES]) // Time is after last frame.
+				spacing = frames[frames.Length + PREV_VALUE];
+			else {
+				// Interpolate between the previous frame and the current frame.
+				int frame = Animation.BinarySearch(frames, time, ENTRIES);
+				spacing = frames[frame + PREV_VALUE];
+				float frameTime = frames[frame];
+				float percent = GetCurvePercent(frame / ENTRIES - 1,
+					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+
+				spacing += (frames[frame + VALUE] - spacing) * percent;
+			}
+
+			if (blend == MixBlend.Setup)
+				constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha;
+			else
+				constraint.spacing += (spacing - constraint.spacing) * alpha;
+		}
+	}
+
+	public class PathConstraintMixTimeline : CurveTimeline {
+		public const int ENTRIES = 3;
+		private const int PREV_TIME = -3, PREV_ROTATE = -2, PREV_TRANSLATE = -1;
+		private const int ROTATE = 1, TRANSLATE = 2;
+
+		internal int pathConstraintIndex;
+		internal float[] frames;
+
+		public int PathConstraintIndex { get { return pathConstraintIndex; } set { pathConstraintIndex = value; } }
+		public float[] Frames { get { return frames; } set { frames = value; } } // time, rotate mix, translate mix, ...
+
+		override public int PropertyId {
+			get { return ((int)TimelineType.PathConstraintMix << 24) + pathConstraintIndex; }
+		}
+
+		public PathConstraintMixTimeline (int frameCount)
+			: base(frameCount) {
+			frames = new float[frameCount * ENTRIES];
+		}			
+
+		/// <summary>Sets the time and mixes of the specified keyframe.</summary>
+		public void SetFrame (int frameIndex, float time, float rotateMix, float translateMix) {
+			frameIndex *= ENTRIES;
+			frames[frameIndex] = time;
+			frames[frameIndex + ROTATE] = rotateMix;
+			frames[frameIndex + TRANSLATE] = translateMix;
+		}
+
+		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend blend, MixDirection direction) {
+			PathConstraint constraint = skeleton.pathConstraints.Items[pathConstraintIndex];
+			float[] frames = this.frames;
+			if (time < frames[0]) {
+				switch (blend) {
+				case MixBlend.Setup:
+					constraint.rotateMix = constraint.data.rotateMix;
+					constraint.translateMix = constraint.data.translateMix;
+					return;
+				case MixBlend.First:
+					constraint.rotateMix += (constraint.data.rotateMix - constraint.rotateMix) * alpha;
+					constraint.translateMix += (constraint.data.translateMix - constraint.translateMix) * alpha;
+					return;
+				}
+				return;
+			}
+
+			float rotate, translate;
+			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
+				rotate = frames[frames.Length + PREV_ROTATE];
+				translate = frames[frames.Length + PREV_TRANSLATE];
+			} else {
+				// Interpolate between the previous frame and the current frame.
+				int frame = Animation.BinarySearch(frames, time, ENTRIES);
+				rotate = frames[frame + PREV_ROTATE];
+				translate = frames[frame + PREV_TRANSLATE];
+				float frameTime = frames[frame];
+				float percent = GetCurvePercent(frame / ENTRIES - 1,
+					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+
+				rotate += (frames[frame + ROTATE] - rotate) * percent;
+				translate += (frames[frame + TRANSLATE] - translate) * percent;
+			}
+
+			if (blend == MixBlend.Setup) {
+				constraint.rotateMix = constraint.data.rotateMix + (rotate - constraint.data.rotateMix) * alpha;
+				constraint.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha;
+			} else {
+				constraint.rotateMix += (rotate - constraint.rotateMix) * alpha;
+				constraint.translateMix += (translate - constraint.translateMix) * alpha;
+			}
+		}
+	}
+}

+ 1134 - 1134
spine-csharp/src/AnimationState.cs

@@ -1,1134 +1,1134 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-using System;
-using System.Collections.Generic;
-
-namespace Spine {
-	public class AnimationState {
-		static readonly Animation EmptyAnimation = new Animation("<empty>", new ExposedList<Timeline>(), 0);
-		internal const int Subsequent = 0, First = 1, Hold = 2, HoldMix = 3;
-
-		protected AnimationStateData data;
-
-		private readonly Pool<TrackEntry> trackEntryPool = new Pool<TrackEntry>();
-		private readonly ExposedList<TrackEntry> tracks = new ExposedList<TrackEntry>();
-		private readonly ExposedList<Event> events = new ExposedList<Event>();
-		private readonly EventQueue queue; // Initialized by constructor.
-
-		private readonly HashSet<int> propertyIDs = new HashSet<int>();
-		private bool animationsChanged;
-
-		private float timeScale = 1;
-
-		public AnimationStateData Data { get { return data; } }
-
-		/// <summary>A list of tracks that have animations, which may contain nulls.</summary>
-		public ExposedList<TrackEntry> Tracks { get { return tracks; } }
-		public float TimeScale { get { return timeScale; } set { timeScale = value; } }
-
-		public delegate void TrackEntryDelegate(TrackEntry trackEntry);
-		public event TrackEntryDelegate Start, Interrupt, End, Dispose, Complete;
-
-		public delegate void TrackEntryEventDelegate(TrackEntry trackEntry, Event e);
-		public event TrackEntryEventDelegate Event;
-
-		public AnimationState(AnimationStateData data) {
-			if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
-			this.data = data;
-			this.queue = new EventQueue(
-				this,
-				delegate { this.animationsChanged = true; },
-				trackEntryPool
-			);
-		}
-
-		/// <summary>
-		/// Increments the track entry trackTimes, setting queued animations as current if needed</summary>
-		/// <param name="delta">delta time</param>
-		public void Update (float delta) {
-			delta *= timeScale;
-			var tracksItems = tracks.Items;
-			for (int i = 0, n = tracks.Count; i < n; i++) {
-				TrackEntry current = tracksItems[i];
-				if (current == null) continue;
-
-				current.animationLast = current.nextAnimationLast;
-				current.trackLast = current.nextTrackLast;
-
-				float currentDelta = delta * current.timeScale;
-
-				if (current.delay > 0) {
-					current.delay -= currentDelta;
-					if (current.delay > 0) continue;
-					currentDelta = -current.delay;
-					current.delay = 0;
-				}
-
-				TrackEntry next = current.next;
-				if (next != null) {
-					// When the next entry's delay is passed, change to the next entry, preserving leftover time.
-					float nextTime = current.trackLast - next.delay;
-					if (nextTime >= 0) {
-						next.delay = 0;
-						next.trackTime = (nextTime / current.timeScale + delta) * next.timeScale;
-						current.trackTime += currentDelta;
-						SetCurrent(i, next, true);
-						while (next.mixingFrom != null) {
-							next.mixTime += delta;
-							next = next.mixingFrom;
-						}
-						continue;
-					}
-				} else if (current.trackLast >= current.trackEnd && current.mixingFrom == null) {
-					// Clear the track when there is no next entry, the track end time is reached, and there is no mixingFrom.
-					tracksItems[i] = null;
-					queue.End(current);
-					DisposeNext(current);
-					continue;
-				}
-				if (current.mixingFrom != null && UpdateMixingFrom(current, delta)) {
-					// End mixing from entries once all have completed.
-					var from = current.mixingFrom;
-					current.mixingFrom = null;
-					if (from != null) from.mixingTo = null;
-					while (from != null) {
-						queue.End(from);
-						from = from.mixingFrom;
-					}
-				}
-
-				current.trackTime += currentDelta;
-			}
-
-			queue.Drain();
-		}
-
-		/// <summary>Returns true when all mixing from entries are complete.</summary>
-		private bool UpdateMixingFrom (TrackEntry to, float delta) {
-			TrackEntry from = to.mixingFrom;
-			if (from == null) return true;
-
-			bool finished = UpdateMixingFrom(from, delta);
-
-			from.animationLast = from.nextAnimationLast;
-			from.trackLast = from.nextTrackLast;
-
-			// Require mixTime > 0 to ensure the mixing from entry was applied at least once.
-			if (to.mixTime > 0 && to.mixTime >= to.mixDuration) {
-				// Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame).
-				if (from.totalAlpha == 0 || to.mixDuration == 0) {
-					to.mixingFrom = from.mixingFrom;
-					if (from.mixingFrom != null) from.mixingFrom.mixingTo = to;
-					to.interruptAlpha = from.interruptAlpha;
-					queue.End(from);
-				}
-				return finished;
-			}
-
-			from.trackTime += delta * from.timeScale;
-			to.mixTime += delta;
-			return false;
-		}
-
-		/// <summary>
-		/// Poses the skeleton using the track entry animations. There are no side effects other than invoking listeners, so the
-		/// animation state can be applied to multiple skeletons to pose them identically.</summary>
-		public bool Apply (Skeleton skeleton) {
-			if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
-			if (animationsChanged) AnimationsChanged();
-
-			var events = this.events;
-			bool applied = false;
-			var tracksItems = tracks.Items;
-			for (int i = 0, m = tracks.Count; i < m; i++) {
-				TrackEntry current = tracksItems[i];
-				if (current == null || current.delay > 0) continue;
-				applied = true;
-
-				// Track 0 animations aren't for layering, so do not show the previously applied animations before the first key.
-				MixBlend blend = i == 0 ? MixBlend.First : current.mixBlend;
-
-				// Apply mixing from entries first.
-				float mix = current.alpha;
-				if (current.mixingFrom != null)
-					mix *= ApplyMixingFrom(current, skeleton, blend);
-				else if (current.trackTime >= current.trackEnd && current.next == null) //
-					mix = 0; // Set to setup pose the last time the entry will be applied.
-
-				// Apply current entry.
-				float animationLast = current.animationLast, animationTime = current.AnimationTime;
-				int timelineCount = current.animation.timelines.Count;
-				var timelines = current.animation.timelines;
-				var timelinesItems = timelines.Items;
-				if (i == 0 && (mix == 1 || blend == MixBlend.Add)) {
-					for (int ii = 0; ii < timelineCount; ii++)
-						timelinesItems[ii].Apply(skeleton, animationLast, animationTime, events, mix, blend, MixDirection.In);
-				} else {
-					var timelineMode = current.timelineMode.Items;
-
-					bool firstFrame = current.timelinesRotation.Count == 0;
-					if (firstFrame) current.timelinesRotation.EnsureCapacity(timelines.Count << 1);
-					var timelinesRotation = current.timelinesRotation.Items;
-
-					for (int ii = 0; ii < timelineCount; ii++) {
-						Timeline timeline = timelinesItems[ii];
-						MixBlend timelineBlend = timelineMode[ii] >= AnimationState.Subsequent ? blend : MixBlend.Setup;
-						var rotateTimeline = timeline as RotateTimeline;
-						if (rotateTimeline != null)
-							ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, ii << 1, firstFrame);
-						else
-							timeline.Apply(skeleton, animationLast, animationTime, events, mix, timelineBlend, MixDirection.In);
-					}
-				}
-				QueueEvents(current, animationTime);
-				events.Clear(false);
-				current.nextAnimationLast = animationTime;
-				current.nextTrackLast = current.trackTime;
-			}
-
-			queue.Drain();
-			return applied;
-		}
-
-		private float ApplyMixingFrom (TrackEntry to, Skeleton skeleton, MixBlend blend) {
-			TrackEntry from = to.mixingFrom;
-			if (from.mixingFrom != null) ApplyMixingFrom(from, skeleton, blend);
-
-			float mix;
-			if (to.mixDuration == 0) { // Single frame mix to undo mixingFrom changes.
-				mix = 1;
-				if (blend == MixBlend.First) blend = MixBlend.Setup; // Tracks > 0 are transparent and can't reset to setup pose.
-			} else {
-				mix = to.mixTime / to.mixDuration;
-				if (mix > 1) mix = 1;
-				if (blend != MixBlend.First) blend = from.mixBlend; // Track 0 ignores frack mix blend.
-			}
-
-			var eventBuffer = mix < from.eventThreshold ? this.events : null;
-			bool attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold;
-			float animationLast = from.animationLast, animationTime = from.AnimationTime;
-			var timelines = from.animation.timelines;
-			int timelineCount = timelines.Count;
-			var timelinesItems = timelines.Items;
-			float alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix);
-
-			if (blend == MixBlend.Add) {
-				for (int i = 0; i < timelineCount; i++)
-					timelinesItems[i].Apply(skeleton, animationLast, animationTime, eventBuffer, alphaMix, blend, MixDirection.Out);
-			} else {
-				var timelineMode = from.timelineMode.Items;
-				var timelineHoldMix = from.timelineHoldMix.Items;
-
-				bool firstFrame = from.timelinesRotation.Count == 0;
-				if (firstFrame)	from.timelinesRotation.Resize(timelines.Count << 1); // from.timelinesRotation.setSize
-				var timelinesRotation = from.timelinesRotation.Items;
-
-				from.totalAlpha = 0;
-				for (int i = 0; i < timelineCount; i++) {
-					Timeline timeline = timelinesItems[i];
-					MixDirection direction = MixDirection.Out;
-					MixBlend timelineBlend;
-					float alpha;
-					switch (timelineMode[i]) {
-						case AnimationState.Subsequent:
-							if (!attachments && timeline is AttachmentTimeline) continue;
-							if (!drawOrder && timeline is DrawOrderTimeline) continue;
-							timelineBlend = blend;
-							alpha = alphaMix;
-							break;
-						case AnimationState.First:
-							timelineBlend = MixBlend.Setup;
-							alpha = alphaMix;
-							break;
-						case AnimationState.Hold:
-							timelineBlend = MixBlend.Setup;
-							alpha = alphaHold;
-							break;
-						default:
-							timelineBlend = MixBlend.Setup;
-							TrackEntry holdMix = timelineHoldMix[i];
-							alpha = alphaHold * Math.Max(0, 1 - holdMix.mixTime / holdMix.mixDuration);
-							break;
-					}
-					from.totalAlpha += alpha;
-
-					var rotateTimeline = timeline as RotateTimeline;
-					if (rotateTimeline != null) {
-						ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, alpha, timelineBlend, timelinesRotation, i << 1, firstFrame);
-					} else {
-						if (timelineBlend == MixBlend.Setup) {
-							if (timeline is AttachmentTimeline) {
-								if (attachments) direction = MixDirection.In;
-							} else if (timeline is DrawOrderTimeline) {
-								if (drawOrder) {
-									direction = MixDirection.In;
-								}
-							}
-						}
-
-						timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, alpha, timelineBlend, direction);
-					}
-				}
-			}
-
-			if (to.mixDuration > 0) QueueEvents(from, animationTime);
-			this.events.Clear(false);
-			from.nextAnimationLast = animationTime;
-			from.nextTrackLast = from.trackTime;
-
-			return mix;
-		}
-
-		static private void ApplyRotateTimeline (RotateTimeline rotateTimeline, Skeleton skeleton, float time, float alpha, MixBlend pose,
-			float[] timelinesRotation, int i, bool firstFrame) {
-
-			if (firstFrame) timelinesRotation[i] = 0;
-
-			if (alpha == 1) {
-				rotateTimeline.Apply(skeleton, 0, time, null, 1, pose, MixDirection.In);
-				return;
-			}
-
-			Bone bone = skeleton.bones.Items[rotateTimeline.boneIndex];
-			float[] frames = rotateTimeline.frames;
-			if (time < frames[0]) {
-				if (pose == MixBlend.Setup) bone.rotation = bone.data.rotation;
-				return;
-			}
-
-			float r2;
-			if (time >= frames[frames.Length - RotateTimeline.ENTRIES]) // Time is after last frame.
-				r2 = bone.data.rotation + frames[frames.Length + RotateTimeline.PREV_ROTATION];
-			else {
-				// Interpolate between the previous frame and the current frame.
-				int frame = Animation.BinarySearch(frames, time, RotateTimeline.ENTRIES);
-				float prevRotation = frames[frame + RotateTimeline.PREV_ROTATION];
-				float frameTime = frames[frame];
-				float percent = rotateTimeline.GetCurvePercent((frame >> 1) - 1,
-					1 - (time - frameTime) / (frames[frame + RotateTimeline.PREV_TIME] - frameTime));
-
-				r2 = frames[frame + RotateTimeline.ROTATION] - prevRotation;
-				r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360;
-				r2 = prevRotation + r2 * percent + bone.data.rotation;
-				r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360;
-			}
-
-			// Mix between rotations using the direction of the shortest route on the first frame while detecting crosses.
-			float r1 = pose == MixBlend.Setup ? bone.data.rotation : bone.rotation;
-			float total, diff = r2 - r1;
-			diff -= (16384 - (int)(16384.499999999996 - diff / 360)) * 360;
-			if (diff == 0) {
-				total = timelinesRotation[i];
-			} else {
-				float lastTotal, lastDiff;
-				if (firstFrame) {
-					lastTotal = 0;
-					lastDiff = diff;
-				} else {
-					lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops.
-					lastDiff = timelinesRotation[i + 1]; // Difference between bones.
-				}
-				bool current = diff > 0, dir = lastTotal >= 0;
-				// Detect cross at 0 (not 180).
-				if (Math.Sign(lastDiff) != Math.Sign(diff) && Math.Abs(lastDiff) <= 90) {
-					// A cross after a 360 rotation is a loop.
-					if (Math.Abs(lastTotal) > 180) lastTotal += 360 * Math.Sign(lastTotal);
-					dir = current;
-				}
-				total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal.
-				if (dir != current) total += 360 * Math.Sign(lastTotal);
-				timelinesRotation[i] = total;
-			}
-			timelinesRotation[i + 1] = diff;
-			r1 += total * alpha;
-			bone.rotation = r1 - (16384 - (int)(16384.499999999996 - r1 / 360)) * 360;
-		}
-
-		private void QueueEvents (TrackEntry entry, float animationTime) {
-			float animationStart = entry.animationStart, animationEnd = entry.animationEnd;
-			float duration = animationEnd - animationStart;
-			float trackLastWrapped = entry.trackLast % duration;
-
-			// Queue events before complete.
-			var events = this.events;
-			var eventsItems = events.Items;
-			int i = 0, n = events.Count;
-			for (; i < n; i++) {
-				var e = eventsItems[i];
-				if (e.time < trackLastWrapped) break;
-				if (e.time > animationEnd) continue; // Discard events outside animation start/end.
-				queue.Event(entry, e);
-			}
-
-			// Queue complete if completed a loop iteration or the animation.
-			bool complete = false;
-			if (entry.loop)
-				complete = duration == 0 || (trackLastWrapped > entry.trackTime % duration);
-			else
-				complete = animationTime >= animationEnd && entry.animationLast < animationEnd;
-			if (complete) queue.Complete(entry);
-
-			// Queue events after complete.
-			for (; i < n; i++) {
-				Event e = eventsItems[i];
-				if (e.time < animationStart) continue; // Discard events outside animation start/end.
-				queue.Event(entry, eventsItems[i]);
-			}
-		}
-
-		/// <summary>
-		/// Removes all animations from all tracks, leaving skeletons in their previous pose.
-		/// It may be desired to use <see cref="AnimationState.SetEmptyAnimations(float)"/> to mix the skeletons back to the setup pose,
-		/// rather than leaving them in their previous pose.</summary>
-		public void ClearTracks () {
-			bool oldDrainDisabled = queue.drainDisabled;
-			queue.drainDisabled = true;
-			for (int i = 0, n = tracks.Count; i < n; i++) {
-				ClearTrack(i);
-			}
-			tracks.Clear();
-			queue.drainDisabled = oldDrainDisabled;
-			queue.Drain();
-		}
-
-		/// <summary>
-		/// Removes all animations from the tracks, leaving skeletons in their previous pose.
-		/// It may be desired to use <see cref="AnimationState.SetEmptyAnimations(float)"/> to mix the skeletons back to the setup pose,
-		/// rather than leaving them in their previous pose.</summary>
-		public void ClearTrack (int trackIndex) {
-			if (trackIndex >= tracks.Count) return;
-			TrackEntry current = tracks.Items[trackIndex];
-			if (current == null) return;
-
-			queue.End(current);
-
-			DisposeNext(current);
-
-			TrackEntry entry = current;
-			while (true) {
-				TrackEntry from = entry.mixingFrom;
-				if (from == null) break;
-				queue.End(from);
-				entry.mixingFrom = null;
-				entry.mixingTo = null;
-				entry = from;
-			}
-
-			tracks.Items[current.trackIndex] = null;
-
-			queue.Drain();
-		}
-
-		/// <summary>Sets the active TrackEntry for a given track number.</summary>
-		private void SetCurrent (int index, TrackEntry current, bool interrupt) {
-			TrackEntry from = ExpandToIndex(index);
-			tracks.Items[index] = current;
-
-			if (from != null) {
-				if (interrupt) queue.Interrupt(from);
-				current.mixingFrom = from;
-				from.mixingTo = current;
-				current.mixTime = 0;
-
-				// Store interrupted mix percentage.
-				if (from.mixingFrom != null && from.mixDuration > 0)
-					current.interruptAlpha *= Math.Min(1, from.mixTime / from.mixDuration);
-
-				from.timelinesRotation.Clear(); // Reset rotation for mixing out, in case entry was mixed in.
-			}
-
-			queue.Start(current); // triggers AnimationsChanged
-		}
-
-
-		/// <summary>Sets an animation by name. <seealso cref="SetAnimation(int, Animation, bool)" /></summary>
-		public TrackEntry SetAnimation (int trackIndex, string animationName, bool loop) {
-			Animation animation = data.skeletonData.FindAnimation(animationName);
-			if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName");
-			return SetAnimation(trackIndex, animation, loop);
-		}
-
-		/// <summary>Sets the current animation for a track, discarding any queued animations.</summary>
-		/// <param name="loop">If true, the animation will repeat.
-		/// If false, it will not, instead its last frame is applied if played beyond its duration.
-		/// In either case <see cref="TrackEntry.TrackEnd"/> determines when the track is cleared. </param>
-		/// <returns>
-		/// A track entry to allow further customization of animation playback. References to the track entry must not be kept
-		/// after <see cref="AnimationState.Dispose"/>.</returns>
-		public TrackEntry SetAnimation (int trackIndex, Animation animation, bool loop) {
-			if (animation == null) throw new ArgumentNullException("animation", "animation cannot be null.");
-			bool interrupt = true;
-			TrackEntry current = ExpandToIndex(trackIndex);
-			if (current != null) {
-				if (current.nextTrackLast == -1) {
-					// Don't mix from an entry that was never applied.
-					tracks.Items[trackIndex] = current.mixingFrom;
-					queue.Interrupt(current);
-					queue.End(current);
-					DisposeNext(current);
-					current = current.mixingFrom;
-					interrupt = false;
-				} else {
-					DisposeNext(current);
-				}
-			}
-			TrackEntry entry = NewTrackEntry(trackIndex, animation, loop, current);
-			SetCurrent(trackIndex, entry, interrupt);
-			queue.Drain();
-			return entry;
-		}
-
-		/// <summary>Queues an animation by name.</summary>
-		/// <seealso cref="AddAnimation(int, Animation, bool, float)" />
-		public TrackEntry AddAnimation (int trackIndex, string animationName, bool loop, float delay) {
-			Animation animation = data.skeletonData.FindAnimation(animationName);
-			if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName");
-			return AddAnimation(trackIndex, animation, loop, delay);
-		}
-
-		/// <summary>Adds an animation to be played delay seconds after the current or last queued animation
-		/// for a track. If the track is empty, it is equivalent to calling <see cref="SetAnimation"/>.</summary>
-		/// <param name="delay">
-		/// delay Seconds to begin this animation after the start of the previous animation. If  &lt;= 0, uses the duration of the
-		/// previous track entry minus any mix duration (from the <see cref="AnimationStateData"/>) plus the
-		/// specified<code>delay</code> (ie the mix ends at (<code>delay</code> = 0) or before (<code>delay</code> < 0) the previous
-		/// track entry duration). If the previous entry is looping, its next loop completion is used
-		/// instead of the duration.
-		/// </param>
-		/// <returns>A track entry to allow further customization of animation playback. References to the track entry must not be kept
-		/// after <see cref="AnimationState.Dispose"/></returns>
-		public TrackEntry AddAnimation (int trackIndex, Animation animation, bool loop, float delay) {
-			if (animation == null) throw new ArgumentNullException("animation", "animation cannot be null.");
-
-			TrackEntry last = ExpandToIndex(trackIndex);
-			if (last != null) {
-				while (last.next != null)
-					last = last.next;
-			}
-
-			TrackEntry entry = NewTrackEntry(trackIndex, animation, loop, last);
-
-			if (last == null) {
-				SetCurrent(trackIndex, entry, true);
-				queue.Drain();
-			} else {
-				last.next = entry;
-				if (delay <= 0) {
-					float duration = last.animationEnd - last.animationStart;
-					if (duration != 0) {
-						if (last.loop) {
-							delay += duration * (1 + (int)(last.trackTime / duration)); // Completion of next loop.
-						} else {
-							delay += Math.Max(duration, last.trackTime); // After duration, else next update.
-						}
-						delay -= data.GetMix(last.animation, animation);
-					} else
-						delay = last.trackTime; // Next update.
-				}
-			}
-
-			entry.delay = delay;
-			return entry;
-		}
-
-		/// <summary>
-		/// Sets an empty animation for a track, discarding any queued animations, and mixes to it over the specified mix duration.</summary>
-		public TrackEntry SetEmptyAnimation (int trackIndex, float mixDuration) {
-			TrackEntry entry = SetAnimation(trackIndex, AnimationState.EmptyAnimation, false);
-			entry.mixDuration = mixDuration;
-			entry.trackEnd = mixDuration;
-			return entry;
-		}
-
-		/// <summary>
-		/// Adds an empty animation to be played after the current or last queued animation for a track, and mixes to it over the
-		/// specified mix duration.</summary>
-		/// <returns>
-		/// A track entry to allow further customization of animation playback. References to the track entry must not be kept after <see cref="AnimationState.Dispose"/>.
-		/// </returns>
-		/// <param name="trackIndex">Track number.</param>
-		/// <param name="mixDuration">Mix duration.</param>
-		/// <param name="delay">Seconds to begin this animation after the start of the previous animation. May be &lt;= 0 to use the animation
-		/// duration of the previous track minus any mix duration plus the negative delay.</param>
-		public TrackEntry AddEmptyAnimation (int trackIndex, float mixDuration, float delay) {
-			if (delay <= 0) delay -= mixDuration;
-			TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay);
-			entry.mixDuration = mixDuration;
-			entry.trackEnd = mixDuration;
-			return entry;
-		}
-
-		/// <summary>
-		/// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix duration.</summary>
-		public void SetEmptyAnimations (float mixDuration) {
-			bool oldDrainDisabled = queue.drainDisabled;
-			queue.drainDisabled = true;
-			for (int i = 0, n = tracks.Count; i < n; i++) {
-				TrackEntry current = tracks.Items[i];
-				if (current != null) SetEmptyAnimation(i, mixDuration);
-			}
-			queue.drainDisabled = oldDrainDisabled;
-			queue.Drain();
-		}
-
-		private TrackEntry ExpandToIndex (int index) {
-			if (index < tracks.Count) return tracks.Items[index];
-			while (index >= tracks.Count)
-				tracks.Add(null);
-			return null;
-		}
-
-		/// <summary>Object-pooling version of new TrackEntry. Obtain an unused TrackEntry from the pool and clear/initialize its values.</summary>
-		/// <param name="last">May be null.</param>
-		private TrackEntry NewTrackEntry (int trackIndex, Animation animation, bool loop, TrackEntry last) {
-			TrackEntry entry = trackEntryPool.Obtain(); // Pooling
-			entry.trackIndex = trackIndex;
-			entry.animation = animation;
-			entry.loop = loop;
-			entry.holdPrevious = false;
-
-			entry.eventThreshold = 0;
-			entry.attachmentThreshold = 0;
-			entry.drawOrderThreshold = 0;
-
-			entry.animationStart = 0;
-			entry.animationEnd = animation.Duration;
-			entry.animationLast = -1;
-			entry.nextAnimationLast = -1;
-
-			entry.delay = 0;
-			entry.trackTime = 0;
-			entry.trackLast = -1;
-			entry.nextTrackLast = -1; // nextTrackLast == -1 signifies a TrackEntry that wasn't applied yet.
-			entry.trackEnd = float.MaxValue; // loop ? float.MaxValue : animation.Duration;
-			entry.timeScale = 1;
-
-			entry.alpha = 1;
-			entry.interruptAlpha = 1;
-			entry.mixTime = 0;
-			entry.mixDuration = (last == null) ? 0 : data.GetMix(last.animation, animation);
-			return entry;
-		}
-
-		/// <summary>Dispose all track entries queued after the given TrackEntry.</summary>
-		private void DisposeNext (TrackEntry entry) {
-			TrackEntry next = entry.next;
-			while (next != null) {
-				queue.Dispose(next);
-				next = next.next;
-			}
-			entry.next = null;
-		}
-
-		private void AnimationsChanged () {
-			animationsChanged = false;
-
-			propertyIDs.Clear();
-
-			var tracksItems = tracks.Items;
-			for (int i = 0, n = tracks.Count; i < n; i++) {
-				var entry = tracksItems[i];
-				if (entry == null) continue;
-				// Move to last entry, then iterate in reverse (the order animations are applied).
-				while (entry.mixingFrom != null)
-					entry = entry.mixingFrom;
-
-				do {
-					if (entry.mixingTo == null || entry.mixBlend != MixBlend.Add) SetTimelineModes(entry);
-					entry = entry.mixingTo;
-				} while (entry != null);
-
-			}
-		}
-
-		private void SetTimelineModes (TrackEntry entry) {
-			TrackEntry to = entry.mixingTo;
-			var timelines = entry.animation.timelines.Items;
-			int timelinesCount = entry.animation.timelines.Count;
-			var timelineMode = entry.timelineMode.Resize(timelinesCount).Items; //timelineMode.setSize(timelinesCount);
-			entry.timelineHoldMix.Clear();
-			var timelineHoldMix = entry.timelineHoldMix.Resize(timelinesCount).Items; //timelineHoldMix.setSize(timelinesCount);
-			var propertyIDs = this.propertyIDs;
-
-			if (to != null && to.holdPrevious) {
-				for (int i = 0; i < timelinesCount; i++) {
-					propertyIDs.Add(timelines[i].PropertyId);
-					timelineMode[i] = AnimationState.Hold;
-				}
-				return;
-			}
-
-			// outer:
-			for (int i = 0; i < timelinesCount; i++) {
-				int id = timelines[i].PropertyId;
-				if (!propertyIDs.Add(id))
-					timelineMode[i] = AnimationState.Subsequent;
-				else if (to == null || !HasTimeline(to, id))
-					timelineMode[i] = AnimationState.First;
-				else {
-					for (TrackEntry next = to.mixingTo; next != null; next = next.mixingTo) {
-						if (HasTimeline(next, id)) continue;
-						if (next.mixDuration > 0) {
-							timelineMode[i] = AnimationState.HoldMix;
-							timelineHoldMix[i] = next;
-							goto continue_outer; // continue outer;
-						}
-						break;
-					}
-					timelineMode[i] = AnimationState.Hold;
-				}
-				continue_outer: {}
-			}
-		}
-
-		static bool HasTimeline (TrackEntry entry, int id) {
-			var timelines = entry.animation.timelines.Items;
-			for (int i = 0, n = entry.animation.timelines.Count; i < n; i++)
-				if (timelines[i].PropertyId == id) return true;
-			return false;
-		}
-
-		/// <returns>The track entry for the animation currently playing on the track, or null if no animation is currently playing.</returns>
-		public TrackEntry GetCurrent (int trackIndex) {
-			return (trackIndex >= tracks.Count) ? null : tracks.Items[trackIndex];
-		}
-
-		override public string ToString () {
-			var buffer = new System.Text.StringBuilder();
-			for (int i = 0, n = tracks.Count; i < n; i++) {
-				TrackEntry entry = tracks.Items[i];
-				if (entry == null) continue;
-				if (buffer.Length > 0) buffer.Append(", ");
-				buffer.Append(entry.ToString());
-			}
-			return buffer.Length == 0 ? "<none>" : buffer.ToString();
-		}
-
-		internal void OnStart (TrackEntry entry) { if (Start != null) Start(entry); }
-		internal void OnInterrupt (TrackEntry entry) { if (Interrupt != null) Interrupt(entry); }
-		internal void OnEnd (TrackEntry entry) { if (End != null) End(entry); }
-		internal void OnDispose (TrackEntry entry) { if (Dispose != null) Dispose(entry); }
-		internal void OnComplete (TrackEntry entry) { if (Complete != null) Complete(entry); }
-		internal void OnEvent (TrackEntry entry, Event e) { if (Event != null) Event(entry, e); }
-	}
-
-	/// <summary>State for the playback of an animation.</summary>
-	public class TrackEntry : Pool<TrackEntry>.IPoolable {
-		internal Animation animation;
-
-		internal TrackEntry next, mixingFrom, mixingTo;
-		internal int trackIndex;
-
-		internal bool loop, holdPrevious;
-		internal float eventThreshold, attachmentThreshold, drawOrderThreshold;
-		internal float animationStart, animationEnd, animationLast, nextAnimationLast;
-		internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f;
-		internal float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha;
-		internal MixBlend mixBlend = MixBlend.Replace;
-		internal readonly ExposedList<int> timelineMode = new ExposedList<int>();
-		internal readonly ExposedList<TrackEntry> timelineHoldMix = new ExposedList<TrackEntry>();
-		internal readonly ExposedList<float> timelinesRotation = new ExposedList<float>();
-
-		// IPoolable.Reset()
-		public void Reset () {
-			next = null;
-			mixingFrom = null;
-			mixingTo = null;
-			animation = null;
-			timelineMode.Clear();
-			timelineHoldMix.Clear();
-			timelinesRotation.Clear();
-
-			Start = null;
-			Interrupt = null;
-			End = null;
-			Dispose = null;
-			Complete = null;
-			Event = null;
-		}
-
-		/// <summary>The index of the track where this entry is either current or queued.</summary>
-		public int TrackIndex { get { return trackIndex; } }
-
-		/// <summary>The animation to apply for this track entry.</summary>
-		public Animation Animation { get { return animation; } }
-
-		/// <summary>
-		/// If true, the animation will repeat. If false, it will not, instead its last frame is applied if played beyond its duration.</summary>
-		public bool Loop { get { return loop; } set { loop = value; } }
-
-		///<summary>
-		/// Seconds to postpone playing the animation. When a track entry is the current track entry, delay postpones incrementing
-		/// the track time. When a track entry is queued, delay is the time from the start of the previous animation to when the
-		/// track entry will become the current track entry. <see cref="TrackEntry.TimeScale"/> affects the delay.</summary>
-		public float Delay { get { return delay; } set { delay = value; } }
-
-		/// <summary>
-		/// Current time in seconds this track entry has been the current track entry. The track time determines
-		/// <see cref="TrackEntry.AnimationTime"/>. The track time can be set to start the animation at a time other than 0, without affecting looping.</summary>
-		public float TrackTime { get { return trackTime; } set { trackTime = value; } }
-
-		/// <summary>
-		/// The track time in seconds when this animation will be removed from the track. Defaults to the animation duration for
-		/// non-looping animations and to <see cref="int.MaxValue"/> for looping animations. If the track end time is reached and no
-		/// other animations are queued for playback, and mixing from any previous animations is complete, properties keyed by the animation,
-		/// are set to the setup pose and the track is cleared.
-		///
-		/// It may be desired to use <see cref="AnimationState.AddEmptyAnimation(int, float, float)"/> to mix the properties back to the
-		/// setup pose over time, rather than have it happen instantly.
-		/// </summary>
-		public float TrackEnd { get { return trackEnd; } set { trackEnd = value; } }
-
-		/// <summary>
-		/// Seconds when this animation starts, both initially and after looping. Defaults to 0.
-		///
-		/// When changing the animation start time, it often makes sense to set <see cref="TrackEntry.AnimationLast"/> to the same value to
-		/// prevent timeline keys before the start time from triggering.
-		/// </summary>
-		public float AnimationStart { get { return animationStart; } set { animationStart = value; } }
-
-		/// <summary>
-		/// Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will
-		/// loop back to <see cref="TrackEntry.AnimationStart"/> at this time. Defaults to the animation duration.</summary>
-		public float AnimationEnd { get { return animationEnd; } set { animationEnd = value; } }
-
-		/// <summary>
-		/// The time in seconds this animation was last applied. Some timelines use this for one-time triggers. Eg, when this
-		/// animation is applied, event timelines will fire all events between the animation last time (exclusive) and animation time
-		/// (inclusive). Defaults to -1 to ensure triggers on frame 0 happen the first time this animation is applied.</summary>
-		public float AnimationLast {
-			get { return animationLast; }
-			set {
-				animationLast = value;
-				nextAnimationLast = value;
-			}
-		}
-
-		/// <summary>
-		/// Uses <see cref="TrackEntry.TrackTime"/> to compute the animation time between <see cref="TrackEntry.AnimationStart"/>. and
-		/// <see cref="TrackEntry.AnimationEnd"/>. When the track time is 0, the animation time is equal to the animation start time.
-		/// </summary>
-		public float AnimationTime {
-			get {
-				if (loop) {
-					float duration = animationEnd - animationStart;
-					if (duration == 0) return animationStart;
-					return (trackTime % duration) + animationStart;
-				}
-				return Math.Min(trackTime + animationStart, animationEnd);
-			}
-		}
-
-		/// <summary>
-		/// Multiplier for the delta time when the animation state is updated, causing time for all animations and mixes to play slower or
-		/// faster. Defaults to 1.
-		///
-		/// <see cref="TrackEntry.MixTime"/> is not affected by track entry time scale, so <see cref="TrackEntry.MixDuration"/> may need to
-		// be adjusted to match the animation speed.
-		///
-		/// When using <see cref="AnimationState.AddAnimation(int, Animation, boolean, float)"> with a <code>delay</code> <= 0, note the
-		/// {<see cref="TrackEntry.Delay"/> is set using the mix duration from the <see cref="AnimationStateData"/>, assuming time scale to be 1. If
-		/// the time scale is not 1, the delay may need to be adjusted.
-		/// </summary>
-		public float TimeScale { get { return timeScale; } set { timeScale = value; } }
-
-		/// <summary>
-		/// Values less than 1 mix this animation with the last skeleton pose. Defaults to 1, which overwrites the last skeleton pose with
-		/// this animation.
-		///
-		/// Typically track 0 is used to completely pose the skeleton, then alpha can be used on higher tracks. It doesn't make sense
-		/// to use alpha on track 0 if the skeleton pose is from the last frame render.
-		/// </summary>
-		public float Alpha { get { return alpha; } set { alpha = value; } }
-
-		/// <summary>
-		/// When the mix percentage (mix time / mix duration) is less than the event threshold, event timelines for the animation
-		/// being mixed out will be applied. Defaults to 0, so event timelines are not applied for an animation being mixed out.</summary>
-		public float EventThreshold { get { return eventThreshold; } set { eventThreshold = value; } }
-
-		/// <summary>
-		/// When the mix percentage (mix time / mix duration) is less than the attachment threshold, attachment timelines for the
-		/// animation being mixed out will be applied. Defaults to 0, so attachment timelines are not applied for an animation being
-		/// mixed out.</summary>
-		public float AttachmentThreshold { get { return attachmentThreshold; } set { attachmentThreshold = value; } }
-
-		/// <summary>
-		/// When the mix percentage (mix time / mix duration) is less than the draw order threshold, draw order timelines for the
-		/// animation being mixed out will be applied. Defaults to 0, so draw order timelines are not applied for an animation being
-		/// mixed out.
-		/// </summary>
-		public float DrawOrderThreshold { get { return drawOrderThreshold; } set { drawOrderThreshold = value; } }
-
-		/// <summary>
-		/// The animation queued to start after this animation, or null.</summary>
-		public TrackEntry Next { get { return next; } }
-
-		/// <summary>
-		/// Returns true if at least one loop has been completed.</summary>
-		public bool IsComplete {
-			get { return trackTime >= animationEnd - animationStart; }
-		}
-
-		/// <summary>
-		/// Seconds from 0 to the mix duration when mixing from the previous animation to this animation. May be slightly more than
-		/// <see cref="TrackEntry.MixDuration"/> when the mix is complete.</summary>
-		public float MixTime { get { return mixTime; } set { mixTime = value; } }
-
-		/// <summary>
-		/// Seconds for mixing from the previous animation to this animation. Defaults to the value provided by
-		/// <see cref="AnimationStateData"/> based on the animation before this animation (if any).
-		///
-		/// The mix duration can be set manually rather than use the value from AnimationStateData.GetMix.
-		/// In that case, the mixDuration must be set before <see cref="AnimationState.Update(float)"/> is next called.
-		/// <para>
-		/// When using <seealso cref="AnimationState.AddAnimation(int, Animation, bool, float)"/> with a
-		/// <code>delay</code> less than or equal to 0, note the <seealso cref="Delay"/> is set using the mix duration from the <see cref=" AnimationStateData"/>,
-		/// not a mix duration set afterward.
-		/// </para>
-		///
-		/// </summary>
-		public float MixDuration { get { return mixDuration; } set { mixDuration = value; } }
-
-		/// <summary>
-		/// Controls how properties keyed in the animation are mixed with lower tracks. Defaults to {@link MixBlend#replace}, which
-		/// replaces the values from the lower tracks with the animation values. {@link MixBlend#add} adds the animation values to
-		/// the values from the lower tracks.
-		/// <para>
-		/// The<code> mixBlend</code> can be set for a new track entry only before
-		/// <code>AnimationState.Apply(Skeleton)</code> is first called.
-		/// </para>
-		/// </summary>
-		public MixBlend MixBlend { get { return mixBlend; } set { mixBlend = value; } }
-
-		/// <summary>
-		/// The track entry for the previous animation when mixing from the previous animation to this animation, or null if no
-		/// mixing is currently occuring. When mixing from multiple animations, MixingFrom makes up a linked list.</summary>
-		public TrackEntry MixingFrom { get { return mixingFrom; } }
-
-		/// <summary>
-		/// If true, when mixing from the previous animation to this animation, the previous animation is applied as normal instead of being mixed out.
-		///
-		/// When mixing between animations that key the same property, if a lower track also keys that property then the value will briefly dip toward the lower track value during the mix. This happens because the first animation mixes from 100% to 0% while the second animation mixes from 0% to 100%. Setting HoldPrevious to true applies the first animation at 100% during the mix so the lower track value is overwritten. Such dipping does not occur on the lowest track which keys the property, only when a higher track also keys the property.
-		///
-		/// Snapping will occur if HoldPrevious is true and this animation does not key all the same properties as the previous animation.
-		/// </summary>
-		public bool HoldPrevious { get { return holdPrevious; } set { holdPrevious = value; } }
-
-		public event AnimationState.TrackEntryDelegate Start, Interrupt, End, Dispose, Complete;
-		public event AnimationState.TrackEntryEventDelegate Event;
-		internal void OnStart () { if (Start != null) Start(this); }
-		internal void OnInterrupt () { if (Interrupt != null) Interrupt(this); }
-		internal void OnEnd () { if (End != null) End(this); }
-		internal void OnDispose () { if (Dispose != null) Dispose(this); }
-		internal void OnComplete () { if (Complete != null) Complete(this); }
-		internal void OnEvent (Event e) { if (Event != null) Event(this, e); }
-
-		/// <summary>
-		/// <para>
-		/// Resets the rotation directions for mixing this entry's rotate timelines. This can be useful to avoid bones rotating the
-		/// long way around when using <see cref="alpha"/> and starting animations on other tracks.
-		/// </para>
-		/// <para>
-		/// Mixing with <see cref="MixBlend.Replace"/> involves finding a rotation between two others, which has two possible solutions:
-		/// the short way or the long way around.The two rotations likely change over time, so which direction is the short or long
-		/// way also changes.If the short way was always chosen, bones would flip to the other side when that direction became the
-		/// long way. TrackEntry chooses the short way the first time it is applied and remembers that direction.
-		/// </para>
-		/// </summary>
-		public void ResetRotationDirections () {
-			timelinesRotation.Clear();
-		}
-
-		override public string ToString () {
-			return animation == null ? "<none>" : animation.name;
-		}
-	}
-
-	class EventQueue {
-		private readonly List<EventQueueEntry> eventQueueEntries = new List<EventQueueEntry>();
-		internal bool drainDisabled;
-
-		private readonly AnimationState state;
-		private readonly Pool<TrackEntry> trackEntryPool;
-		internal event Action AnimationsChanged;
-
-		internal EventQueue (AnimationState state, Action HandleAnimationsChanged, Pool<TrackEntry> trackEntryPool) {
-			this.state = state;
-			this.AnimationsChanged += HandleAnimationsChanged;
-			this.trackEntryPool = trackEntryPool;
-		}
-
-		struct EventQueueEntry {
-			public EventType type;
-			public TrackEntry entry;
-			public Event e;
-
-			public EventQueueEntry (EventType eventType, TrackEntry trackEntry, Event e = null) {
-				this.type = eventType;
-				this.entry = trackEntry;
-				this.e = e;
-			}
-		}
-
-		enum EventType {
-			Start, Interrupt, End, Dispose, Complete, Event
-		}
-
-		internal void Start (TrackEntry entry) {
-			eventQueueEntries.Add(new EventQueueEntry(EventType.Start, entry));
-			if (AnimationsChanged != null) AnimationsChanged();
-		}
-
-		internal void Interrupt (TrackEntry entry) {
-			eventQueueEntries.Add(new EventQueueEntry(EventType.Interrupt, entry));
-		}
-
-		internal void End (TrackEntry entry) {
-			eventQueueEntries.Add(new EventQueueEntry(EventType.End, entry));
-			if (AnimationsChanged != null) AnimationsChanged();
-		}
-
-		internal void Dispose (TrackEntry entry) {
-			eventQueueEntries.Add(new EventQueueEntry(EventType.Dispose, entry));
-		}
-
-		internal void Complete (TrackEntry entry) {
-			eventQueueEntries.Add(new EventQueueEntry(EventType.Complete, entry));
-		}
-
-		internal void Event (TrackEntry entry, Event e) {
-			eventQueueEntries.Add(new EventQueueEntry(EventType.Event, entry, e));
-		}
-
-		/// <summary>Raises all events in the queue and drains the queue.</summary>
-		internal void Drain () {
-			if (drainDisabled) return;
-			drainDisabled = true;
-
-			var entries = this.eventQueueEntries;
-			AnimationState state = this.state;
-
-			// Don't cache entries.Count so callbacks can queue their own events (eg, call SetAnimation in AnimationState_Complete).
-			for (int i = 0; i < entries.Count; i++) {
-				var queueEntry = entries[i];
-				TrackEntry trackEntry = queueEntry.entry;
-
-				switch (queueEntry.type) {
-				case EventType.Start:
-					trackEntry.OnStart();
-					state.OnStart(trackEntry);
-					break;
-				case EventType.Interrupt:
-					trackEntry.OnInterrupt();
-					state.OnInterrupt(trackEntry);
-					break;
-				case EventType.End:
-					trackEntry.OnEnd();
-					state.OnEnd(trackEntry);
-					goto case EventType.Dispose; // Fall through. (C#)
-				case EventType.Dispose:
-					trackEntry.OnDispose();
-					state.OnDispose(trackEntry);
-					trackEntryPool.Free(trackEntry); // Pooling
-					break;
-				case EventType.Complete:
-					trackEntry.OnComplete();
-					state.OnComplete(trackEntry);
-					break;
-				case EventType.Event:
-					trackEntry.OnEvent(queueEntry.e);
-					state.OnEvent(trackEntry, queueEntry.e);
-					break;
-				}
-			}
-			eventQueueEntries.Clear();
-
-			drainDisabled = false;
-		}
-
-		internal void Clear () {
-			eventQueueEntries.Clear();
-		}
-	}
-
-	public class Pool<T> where T : class, new() {
-		public readonly int max;
-		readonly Stack<T> freeObjects;
-
-		public int Count { get { return freeObjects.Count; } }
-		public int Peak { get; private set; }
-
-		public Pool (int initialCapacity = 16, int max = int.MaxValue) {
-			freeObjects = new Stack<T>(initialCapacity);
-			this.max = max;
-		}
-
-		public T Obtain () {
-			return freeObjects.Count == 0 ? new T() : freeObjects.Pop();
-		}
-
-		public void Free (T obj) {
-			if (obj == null) throw new ArgumentNullException("obj", "obj cannot be null");
-			if (freeObjects.Count < max) {
-				freeObjects.Push(obj);
-				Peak = Math.Max(Peak, freeObjects.Count);
-			}
-			Reset(obj);
-		}
-
-//		protected void FreeAll (List<T> objects) {
-//			if (objects == null) throw new ArgumentNullException("objects", "objects cannot be null.");
-//			var freeObjects = this.freeObjects;
-//			int max = this.max;
-//			for (int i = 0; i < objects.Count; i++) {
-//				T obj = objects[i];
-//				if (obj == null) continue;
-//				if (freeObjects.Count < max) freeObjects.Push(obj);
-//				Reset(obj);
-//			}
-//			Peak = Math.Max(Peak, freeObjects.Count);
-//		}
-
-		public void Clear () {
-			freeObjects.Clear();
-		}
-
-		protected void Reset (T obj) {
-			var poolable = obj as IPoolable;
-			if (poolable != null) poolable.Reset();
-		}
-
-		public interface IPoolable {
-			void Reset ();
-		}
-	}
-
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+using System.Collections.Generic;
+
+namespace Spine {
+	public class AnimationState {
+		static readonly Animation EmptyAnimation = new Animation("<empty>", new ExposedList<Timeline>(), 0);
+		internal const int Subsequent = 0, First = 1, Hold = 2, HoldMix = 3;
+
+		protected AnimationStateData data;
+
+		private readonly Pool<TrackEntry> trackEntryPool = new Pool<TrackEntry>();
+		private readonly ExposedList<TrackEntry> tracks = new ExposedList<TrackEntry>();
+		private readonly ExposedList<Event> events = new ExposedList<Event>();
+		private readonly EventQueue queue; // Initialized by constructor.
+
+		private readonly HashSet<int> propertyIDs = new HashSet<int>();
+		private bool animationsChanged;
+
+		private float timeScale = 1;
+
+		public AnimationStateData Data { get { return data; } }
+
+		/// <summary>A list of tracks that have animations, which may contain nulls.</summary>
+		public ExposedList<TrackEntry> Tracks { get { return tracks; } }
+		public float TimeScale { get { return timeScale; } set { timeScale = value; } }
+
+		public delegate void TrackEntryDelegate(TrackEntry trackEntry);
+		public event TrackEntryDelegate Start, Interrupt, End, Dispose, Complete;
+
+		public delegate void TrackEntryEventDelegate(TrackEntry trackEntry, Event e);
+		public event TrackEntryEventDelegate Event;
+
+		public AnimationState(AnimationStateData data) {
+			if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
+			this.data = data;
+			this.queue = new EventQueue(
+				this,
+				delegate { this.animationsChanged = true; },
+				trackEntryPool
+			);
+		}
+
+		/// <summary>
+		/// Increments the track entry trackTimes, setting queued animations as current if needed</summary>
+		/// <param name="delta">delta time</param>
+		public void Update (float delta) {
+			delta *= timeScale;
+			var tracksItems = tracks.Items;
+			for (int i = 0, n = tracks.Count; i < n; i++) {
+				TrackEntry current = tracksItems[i];
+				if (current == null) continue;
+
+				current.animationLast = current.nextAnimationLast;
+				current.trackLast = current.nextTrackLast;
+
+				float currentDelta = delta * current.timeScale;
+
+				if (current.delay > 0) {
+					current.delay -= currentDelta;
+					if (current.delay > 0) continue;
+					currentDelta = -current.delay;
+					current.delay = 0;
+				}
+
+				TrackEntry next = current.next;
+				if (next != null) {
+					// When the next entry's delay is passed, change to the next entry, preserving leftover time.
+					float nextTime = current.trackLast - next.delay;
+					if (nextTime >= 0) {
+						next.delay = 0;
+						next.trackTime = (nextTime / current.timeScale + delta) * next.timeScale;
+						current.trackTime += currentDelta;
+						SetCurrent(i, next, true);
+						while (next.mixingFrom != null) {
+							next.mixTime += delta;
+							next = next.mixingFrom;
+						}
+						continue;
+					}
+				} else if (current.trackLast >= current.trackEnd && current.mixingFrom == null) {
+					// Clear the track when there is no next entry, the track end time is reached, and there is no mixingFrom.
+					tracksItems[i] = null;
+					queue.End(current);
+					DisposeNext(current);
+					continue;
+				}
+				if (current.mixingFrom != null && UpdateMixingFrom(current, delta)) {
+					// End mixing from entries once all have completed.
+					var from = current.mixingFrom;
+					current.mixingFrom = null;
+					if (from != null) from.mixingTo = null;
+					while (from != null) {
+						queue.End(from);
+						from = from.mixingFrom;
+					}
+				}
+
+				current.trackTime += currentDelta;
+			}
+
+			queue.Drain();
+		}
+
+		/// <summary>Returns true when all mixing from entries are complete.</summary>
+		private bool UpdateMixingFrom (TrackEntry to, float delta) {
+			TrackEntry from = to.mixingFrom;
+			if (from == null) return true;
+
+			bool finished = UpdateMixingFrom(from, delta);
+
+			from.animationLast = from.nextAnimationLast;
+			from.trackLast = from.nextTrackLast;
+
+			// Require mixTime > 0 to ensure the mixing from entry was applied at least once.
+			if (to.mixTime > 0 && to.mixTime >= to.mixDuration) {
+				// Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame).
+				if (from.totalAlpha == 0 || to.mixDuration == 0) {
+					to.mixingFrom = from.mixingFrom;
+					if (from.mixingFrom != null) from.mixingFrom.mixingTo = to;
+					to.interruptAlpha = from.interruptAlpha;
+					queue.End(from);
+				}
+				return finished;
+			}
+
+			from.trackTime += delta * from.timeScale;
+			to.mixTime += delta;
+			return false;
+		}
+
+		/// <summary>
+		/// Poses the skeleton using the track entry animations. There are no side effects other than invoking listeners, so the
+		/// animation state can be applied to multiple skeletons to pose them identically.</summary>
+		public bool Apply (Skeleton skeleton) {
+			if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
+			if (animationsChanged) AnimationsChanged();
+
+			var events = this.events;
+			bool applied = false;
+			var tracksItems = tracks.Items;
+			for (int i = 0, m = tracks.Count; i < m; i++) {
+				TrackEntry current = tracksItems[i];
+				if (current == null || current.delay > 0) continue;
+				applied = true;
+
+				// Track 0 animations aren't for layering, so do not show the previously applied animations before the first key.
+				MixBlend blend = i == 0 ? MixBlend.First : current.mixBlend;
+
+				// Apply mixing from entries first.
+				float mix = current.alpha;
+				if (current.mixingFrom != null)
+					mix *= ApplyMixingFrom(current, skeleton, blend);
+				else if (current.trackTime >= current.trackEnd && current.next == null) //
+					mix = 0; // Set to setup pose the last time the entry will be applied.
+
+				// Apply current entry.
+				float animationLast = current.animationLast, animationTime = current.AnimationTime;
+				int timelineCount = current.animation.timelines.Count;
+				var timelines = current.animation.timelines;
+				var timelinesItems = timelines.Items;
+				if (i == 0 && (mix == 1 || blend == MixBlend.Add)) {
+					for (int ii = 0; ii < timelineCount; ii++)
+						timelinesItems[ii].Apply(skeleton, animationLast, animationTime, events, mix, blend, MixDirection.In);
+				} else {
+					var timelineMode = current.timelineMode.Items;
+
+					bool firstFrame = current.timelinesRotation.Count == 0;
+					if (firstFrame) current.timelinesRotation.EnsureCapacity(timelines.Count << 1);
+					var timelinesRotation = current.timelinesRotation.Items;
+
+					for (int ii = 0; ii < timelineCount; ii++) {
+						Timeline timeline = timelinesItems[ii];
+						MixBlend timelineBlend = timelineMode[ii] >= AnimationState.Subsequent ? blend : MixBlend.Setup;
+						var rotateTimeline = timeline as RotateTimeline;
+						if (rotateTimeline != null)
+							ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, ii << 1, firstFrame);
+						else
+							timeline.Apply(skeleton, animationLast, animationTime, events, mix, timelineBlend, MixDirection.In);
+					}
+				}
+				QueueEvents(current, animationTime);
+				events.Clear(false);
+				current.nextAnimationLast = animationTime;
+				current.nextTrackLast = current.trackTime;
+			}
+
+			queue.Drain();
+			return applied;
+		}
+
+		private float ApplyMixingFrom (TrackEntry to, Skeleton skeleton, MixBlend blend) {
+			TrackEntry from = to.mixingFrom;
+			if (from.mixingFrom != null) ApplyMixingFrom(from, skeleton, blend);
+
+			float mix;
+			if (to.mixDuration == 0) { // Single frame mix to undo mixingFrom changes.
+				mix = 1;
+				if (blend == MixBlend.First) blend = MixBlend.Setup; // Tracks > 0 are transparent and can't reset to setup pose.
+			} else {
+				mix = to.mixTime / to.mixDuration;
+				if (mix > 1) mix = 1;
+				if (blend != MixBlend.First) blend = from.mixBlend; // Track 0 ignores frack mix blend.
+			}
+
+			var eventBuffer = mix < from.eventThreshold ? this.events : null;
+			bool attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold;
+			float animationLast = from.animationLast, animationTime = from.AnimationTime;
+			var timelines = from.animation.timelines;
+			int timelineCount = timelines.Count;
+			var timelinesItems = timelines.Items;
+			float alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix);
+
+			if (blend == MixBlend.Add) {
+				for (int i = 0; i < timelineCount; i++)
+					timelinesItems[i].Apply(skeleton, animationLast, animationTime, eventBuffer, alphaMix, blend, MixDirection.Out);
+			} else {
+				var timelineMode = from.timelineMode.Items;
+				var timelineHoldMix = from.timelineHoldMix.Items;
+
+				bool firstFrame = from.timelinesRotation.Count == 0;
+				if (firstFrame)	from.timelinesRotation.Resize(timelines.Count << 1); // from.timelinesRotation.setSize
+				var timelinesRotation = from.timelinesRotation.Items;
+
+				from.totalAlpha = 0;
+				for (int i = 0; i < timelineCount; i++) {
+					Timeline timeline = timelinesItems[i];
+					MixDirection direction = MixDirection.Out;
+					MixBlend timelineBlend;
+					float alpha;
+					switch (timelineMode[i]) {
+						case AnimationState.Subsequent:
+							if (!attachments && timeline is AttachmentTimeline) continue;
+							if (!drawOrder && timeline is DrawOrderTimeline) continue;
+							timelineBlend = blend;
+							alpha = alphaMix;
+							break;
+						case AnimationState.First:
+							timelineBlend = MixBlend.Setup;
+							alpha = alphaMix;
+							break;
+						case AnimationState.Hold:
+							timelineBlend = MixBlend.Setup;
+							alpha = alphaHold;
+							break;
+						default:
+							timelineBlend = MixBlend.Setup;
+							TrackEntry holdMix = timelineHoldMix[i];
+							alpha = alphaHold * Math.Max(0, 1 - holdMix.mixTime / holdMix.mixDuration);
+							break;
+					}
+					from.totalAlpha += alpha;
+
+					var rotateTimeline = timeline as RotateTimeline;
+					if (rotateTimeline != null) {
+						ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, alpha, timelineBlend, timelinesRotation, i << 1, firstFrame);
+					} else {
+						if (timelineBlend == MixBlend.Setup) {
+							if (timeline is AttachmentTimeline) {
+								if (attachments) direction = MixDirection.In;
+							} else if (timeline is DrawOrderTimeline) {
+								if (drawOrder) {
+									direction = MixDirection.In;
+								}
+							}
+						}
+
+						timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, alpha, timelineBlend, direction);
+					}
+				}
+			}
+
+			if (to.mixDuration > 0) QueueEvents(from, animationTime);
+			this.events.Clear(false);
+			from.nextAnimationLast = animationTime;
+			from.nextTrackLast = from.trackTime;
+
+			return mix;
+		}
+
+		static private void ApplyRotateTimeline (RotateTimeline rotateTimeline, Skeleton skeleton, float time, float alpha, MixBlend pose,
+			float[] timelinesRotation, int i, bool firstFrame) {
+
+			if (firstFrame) timelinesRotation[i] = 0;
+
+			if (alpha == 1) {
+				rotateTimeline.Apply(skeleton, 0, time, null, 1, pose, MixDirection.In);
+				return;
+			}
+
+			Bone bone = skeleton.bones.Items[rotateTimeline.boneIndex];
+			float[] frames = rotateTimeline.frames;
+			if (time < frames[0]) {
+				if (pose == MixBlend.Setup) bone.rotation = bone.data.rotation;
+				return;
+			}
+
+			float r2;
+			if (time >= frames[frames.Length - RotateTimeline.ENTRIES]) // Time is after last frame.
+				r2 = bone.data.rotation + frames[frames.Length + RotateTimeline.PREV_ROTATION];
+			else {
+				// Interpolate between the previous frame and the current frame.
+				int frame = Animation.BinarySearch(frames, time, RotateTimeline.ENTRIES);
+				float prevRotation = frames[frame + RotateTimeline.PREV_ROTATION];
+				float frameTime = frames[frame];
+				float percent = rotateTimeline.GetCurvePercent((frame >> 1) - 1,
+					1 - (time - frameTime) / (frames[frame + RotateTimeline.PREV_TIME] - frameTime));
+
+				r2 = frames[frame + RotateTimeline.ROTATION] - prevRotation;
+				r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360;
+				r2 = prevRotation + r2 * percent + bone.data.rotation;
+				r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360;
+			}
+
+			// Mix between rotations using the direction of the shortest route on the first frame while detecting crosses.
+			float r1 = pose == MixBlend.Setup ? bone.data.rotation : bone.rotation;
+			float total, diff = r2 - r1;
+			diff -= (16384 - (int)(16384.499999999996 - diff / 360)) * 360;
+			if (diff == 0) {
+				total = timelinesRotation[i];
+			} else {
+				float lastTotal, lastDiff;
+				if (firstFrame) {
+					lastTotal = 0;
+					lastDiff = diff;
+				} else {
+					lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops.
+					lastDiff = timelinesRotation[i + 1]; // Difference between bones.
+				}
+				bool current = diff > 0, dir = lastTotal >= 0;
+				// Detect cross at 0 (not 180).
+				if (Math.Sign(lastDiff) != Math.Sign(diff) && Math.Abs(lastDiff) <= 90) {
+					// A cross after a 360 rotation is a loop.
+					if (Math.Abs(lastTotal) > 180) lastTotal += 360 * Math.Sign(lastTotal);
+					dir = current;
+				}
+				total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal.
+				if (dir != current) total += 360 * Math.Sign(lastTotal);
+				timelinesRotation[i] = total;
+			}
+			timelinesRotation[i + 1] = diff;
+			r1 += total * alpha;
+			bone.rotation = r1 - (16384 - (int)(16384.499999999996 - r1 / 360)) * 360;
+		}
+
+		private void QueueEvents (TrackEntry entry, float animationTime) {
+			float animationStart = entry.animationStart, animationEnd = entry.animationEnd;
+			float duration = animationEnd - animationStart;
+			float trackLastWrapped = entry.trackLast % duration;
+
+			// Queue events before complete.
+			var events = this.events;
+			var eventsItems = events.Items;
+			int i = 0, n = events.Count;
+			for (; i < n; i++) {
+				var e = eventsItems[i];
+				if (e.time < trackLastWrapped) break;
+				if (e.time > animationEnd) continue; // Discard events outside animation start/end.
+				queue.Event(entry, e);
+			}
+
+			// Queue complete if completed a loop iteration or the animation.
+			bool complete = false;
+			if (entry.loop)
+				complete = duration == 0 || (trackLastWrapped > entry.trackTime % duration);
+			else
+				complete = animationTime >= animationEnd && entry.animationLast < animationEnd;
+			if (complete) queue.Complete(entry);
+
+			// Queue events after complete.
+			for (; i < n; i++) {
+				Event e = eventsItems[i];
+				if (e.time < animationStart) continue; // Discard events outside animation start/end.
+				queue.Event(entry, eventsItems[i]);
+			}
+		}
+
+		/// <summary>
+		/// Removes all animations from all tracks, leaving skeletons in their previous pose.
+		/// It may be desired to use <see cref="AnimationState.SetEmptyAnimations(float)"/> to mix the skeletons back to the setup pose,
+		/// rather than leaving them in their previous pose.</summary>
+		public void ClearTracks () {
+			bool oldDrainDisabled = queue.drainDisabled;
+			queue.drainDisabled = true;
+			for (int i = 0, n = tracks.Count; i < n; i++) {
+				ClearTrack(i);
+			}
+			tracks.Clear();
+			queue.drainDisabled = oldDrainDisabled;
+			queue.Drain();
+		}
+
+		/// <summary>
+		/// Removes all animations from the tracks, leaving skeletons in their previous pose.
+		/// It may be desired to use <see cref="AnimationState.SetEmptyAnimations(float)"/> to mix the skeletons back to the setup pose,
+		/// rather than leaving them in their previous pose.</summary>
+		public void ClearTrack (int trackIndex) {
+			if (trackIndex >= tracks.Count) return;
+			TrackEntry current = tracks.Items[trackIndex];
+			if (current == null) return;
+
+			queue.End(current);
+
+			DisposeNext(current);
+
+			TrackEntry entry = current;
+			while (true) {
+				TrackEntry from = entry.mixingFrom;
+				if (from == null) break;
+				queue.End(from);
+				entry.mixingFrom = null;
+				entry.mixingTo = null;
+				entry = from;
+			}
+
+			tracks.Items[current.trackIndex] = null;
+
+			queue.Drain();
+		}
+
+		/// <summary>Sets the active TrackEntry for a given track number.</summary>
+		private void SetCurrent (int index, TrackEntry current, bool interrupt) {
+			TrackEntry from = ExpandToIndex(index);
+			tracks.Items[index] = current;
+
+			if (from != null) {
+				if (interrupt) queue.Interrupt(from);
+				current.mixingFrom = from;
+				from.mixingTo = current;
+				current.mixTime = 0;
+
+				// Store interrupted mix percentage.
+				if (from.mixingFrom != null && from.mixDuration > 0)
+					current.interruptAlpha *= Math.Min(1, from.mixTime / from.mixDuration);
+
+				from.timelinesRotation.Clear(); // Reset rotation for mixing out, in case entry was mixed in.
+			}
+
+			queue.Start(current); // triggers AnimationsChanged
+		}
+
+
+		/// <summary>Sets an animation by name. <seealso cref="SetAnimation(int, Animation, bool)" /></summary>
+		public TrackEntry SetAnimation (int trackIndex, string animationName, bool loop) {
+			Animation animation = data.skeletonData.FindAnimation(animationName);
+			if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName");
+			return SetAnimation(trackIndex, animation, loop);
+		}
+
+		/// <summary>Sets the current animation for a track, discarding any queued animations.</summary>
+		/// <param name="loop">If true, the animation will repeat.
+		/// If false, it will not, instead its last frame is applied if played beyond its duration.
+		/// In either case <see cref="TrackEntry.TrackEnd"/> determines when the track is cleared. </param>
+		/// <returns>
+		/// A track entry to allow further customization of animation playback. References to the track entry must not be kept
+		/// after <see cref="AnimationState.Dispose"/>.</returns>
+		public TrackEntry SetAnimation (int trackIndex, Animation animation, bool loop) {
+			if (animation == null) throw new ArgumentNullException("animation", "animation cannot be null.");
+			bool interrupt = true;
+			TrackEntry current = ExpandToIndex(trackIndex);
+			if (current != null) {
+				if (current.nextTrackLast == -1) {
+					// Don't mix from an entry that was never applied.
+					tracks.Items[trackIndex] = current.mixingFrom;
+					queue.Interrupt(current);
+					queue.End(current);
+					DisposeNext(current);
+					current = current.mixingFrom;
+					interrupt = false;
+				} else {
+					DisposeNext(current);
+				}
+			}
+			TrackEntry entry = NewTrackEntry(trackIndex, animation, loop, current);
+			SetCurrent(trackIndex, entry, interrupt);
+			queue.Drain();
+			return entry;
+		}
+
+		/// <summary>Queues an animation by name.</summary>
+		/// <seealso cref="AddAnimation(int, Animation, bool, float)" />
+		public TrackEntry AddAnimation (int trackIndex, string animationName, bool loop, float delay) {
+			Animation animation = data.skeletonData.FindAnimation(animationName);
+			if (animation == null) throw new ArgumentException("Animation not found: " + animationName, "animationName");
+			return AddAnimation(trackIndex, animation, loop, delay);
+		}
+
+		/// <summary>Adds an animation to be played delay seconds after the current or last queued animation
+		/// for a track. If the track is empty, it is equivalent to calling <see cref="SetAnimation"/>.</summary>
+		/// <param name="delay">
+		/// delay Seconds to begin this animation after the start of the previous animation. If  &lt;= 0, uses the duration of the
+		/// previous track entry minus any mix duration (from the <see cref="AnimationStateData"/>) plus the
+		/// specified<code>delay</code> (ie the mix ends at (<code>delay</code> = 0) or before (<code>delay</code> < 0) the previous
+		/// track entry duration). If the previous entry is looping, its next loop completion is used
+		/// instead of the duration.
+		/// </param>
+		/// <returns>A track entry to allow further customization of animation playback. References to the track entry must not be kept
+		/// after <see cref="AnimationState.Dispose"/></returns>
+		public TrackEntry AddAnimation (int trackIndex, Animation animation, bool loop, float delay) {
+			if (animation == null) throw new ArgumentNullException("animation", "animation cannot be null.");
+
+			TrackEntry last = ExpandToIndex(trackIndex);
+			if (last != null) {
+				while (last.next != null)
+					last = last.next;
+			}
+
+			TrackEntry entry = NewTrackEntry(trackIndex, animation, loop, last);
+
+			if (last == null) {
+				SetCurrent(trackIndex, entry, true);
+				queue.Drain();
+			} else {
+				last.next = entry;
+				if (delay <= 0) {
+					float duration = last.animationEnd - last.animationStart;
+					if (duration != 0) {
+						if (last.loop) {
+							delay += duration * (1 + (int)(last.trackTime / duration)); // Completion of next loop.
+						} else {
+							delay += Math.Max(duration, last.trackTime); // After duration, else next update.
+						}
+						delay -= data.GetMix(last.animation, animation);
+					} else
+						delay = last.trackTime; // Next update.
+				}
+			}
+
+			entry.delay = delay;
+			return entry;
+		}
+
+		/// <summary>
+		/// Sets an empty animation for a track, discarding any queued animations, and mixes to it over the specified mix duration.</summary>
+		public TrackEntry SetEmptyAnimation (int trackIndex, float mixDuration) {
+			TrackEntry entry = SetAnimation(trackIndex, AnimationState.EmptyAnimation, false);
+			entry.mixDuration = mixDuration;
+			entry.trackEnd = mixDuration;
+			return entry;
+		}
+
+		/// <summary>
+		/// Adds an empty animation to be played after the current or last queued animation for a track, and mixes to it over the
+		/// specified mix duration.</summary>
+		/// <returns>
+		/// A track entry to allow further customization of animation playback. References to the track entry must not be kept after <see cref="AnimationState.Dispose"/>.
+		/// </returns>
+		/// <param name="trackIndex">Track number.</param>
+		/// <param name="mixDuration">Mix duration.</param>
+		/// <param name="delay">Seconds to begin this animation after the start of the previous animation. May be &lt;= 0 to use the animation
+		/// duration of the previous track minus any mix duration plus the negative delay.</param>
+		public TrackEntry AddEmptyAnimation (int trackIndex, float mixDuration, float delay) {
+			if (delay <= 0) delay -= mixDuration;
+			TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay);
+			entry.mixDuration = mixDuration;
+			entry.trackEnd = mixDuration;
+			return entry;
+		}
+
+		/// <summary>
+		/// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix duration.</summary>
+		public void SetEmptyAnimations (float mixDuration) {
+			bool oldDrainDisabled = queue.drainDisabled;
+			queue.drainDisabled = true;
+			for (int i = 0, n = tracks.Count; i < n; i++) {
+				TrackEntry current = tracks.Items[i];
+				if (current != null) SetEmptyAnimation(i, mixDuration);
+			}
+			queue.drainDisabled = oldDrainDisabled;
+			queue.Drain();
+		}
+
+		private TrackEntry ExpandToIndex (int index) {
+			if (index < tracks.Count) return tracks.Items[index];
+			while (index >= tracks.Count)
+				tracks.Add(null);
+			return null;
+		}
+
+		/// <summary>Object-pooling version of new TrackEntry. Obtain an unused TrackEntry from the pool and clear/initialize its values.</summary>
+		/// <param name="last">May be null.</param>
+		private TrackEntry NewTrackEntry (int trackIndex, Animation animation, bool loop, TrackEntry last) {
+			TrackEntry entry = trackEntryPool.Obtain(); // Pooling
+			entry.trackIndex = trackIndex;
+			entry.animation = animation;
+			entry.loop = loop;
+			entry.holdPrevious = false;
+
+			entry.eventThreshold = 0;
+			entry.attachmentThreshold = 0;
+			entry.drawOrderThreshold = 0;
+
+			entry.animationStart = 0;
+			entry.animationEnd = animation.Duration;
+			entry.animationLast = -1;
+			entry.nextAnimationLast = -1;
+
+			entry.delay = 0;
+			entry.trackTime = 0;
+			entry.trackLast = -1;
+			entry.nextTrackLast = -1; // nextTrackLast == -1 signifies a TrackEntry that wasn't applied yet.
+			entry.trackEnd = float.MaxValue; // loop ? float.MaxValue : animation.Duration;
+			entry.timeScale = 1;
+
+			entry.alpha = 1;
+			entry.interruptAlpha = 1;
+			entry.mixTime = 0;
+			entry.mixDuration = (last == null) ? 0 : data.GetMix(last.animation, animation);
+			return entry;
+		}
+
+		/// <summary>Dispose all track entries queued after the given TrackEntry.</summary>
+		private void DisposeNext (TrackEntry entry) {
+			TrackEntry next = entry.next;
+			while (next != null) {
+				queue.Dispose(next);
+				next = next.next;
+			}
+			entry.next = null;
+		}
+
+		private void AnimationsChanged () {
+			animationsChanged = false;
+
+			propertyIDs.Clear();
+
+			var tracksItems = tracks.Items;
+			for (int i = 0, n = tracks.Count; i < n; i++) {
+				var entry = tracksItems[i];
+				if (entry == null) continue;
+				// Move to last entry, then iterate in reverse (the order animations are applied).
+				while (entry.mixingFrom != null)
+					entry = entry.mixingFrom;
+
+				do {
+					if (entry.mixingTo == null || entry.mixBlend != MixBlend.Add) SetTimelineModes(entry);
+					entry = entry.mixingTo;
+				} while (entry != null);
+
+			}
+		}
+
+		private void SetTimelineModes (TrackEntry entry) {
+			TrackEntry to = entry.mixingTo;
+			var timelines = entry.animation.timelines.Items;
+			int timelinesCount = entry.animation.timelines.Count;
+			var timelineMode = entry.timelineMode.Resize(timelinesCount).Items; //timelineMode.setSize(timelinesCount);
+			entry.timelineHoldMix.Clear();
+			var timelineHoldMix = entry.timelineHoldMix.Resize(timelinesCount).Items; //timelineHoldMix.setSize(timelinesCount);
+			var propertyIDs = this.propertyIDs;
+
+			if (to != null && to.holdPrevious) {
+				for (int i = 0; i < timelinesCount; i++) {
+					propertyIDs.Add(timelines[i].PropertyId);
+					timelineMode[i] = AnimationState.Hold;
+				}
+				return;
+			}
+
+			// outer:
+			for (int i = 0; i < timelinesCount; i++) {
+				int id = timelines[i].PropertyId;
+				if (!propertyIDs.Add(id))
+					timelineMode[i] = AnimationState.Subsequent;
+				else if (to == null || !HasTimeline(to, id))
+					timelineMode[i] = AnimationState.First;
+				else {
+					for (TrackEntry next = to.mixingTo; next != null; next = next.mixingTo) {
+						if (HasTimeline(next, id)) continue;
+						if (next.mixDuration > 0) {
+							timelineMode[i] = AnimationState.HoldMix;
+							timelineHoldMix[i] = next;
+							goto continue_outer; // continue outer;
+						}
+						break;
+					}
+					timelineMode[i] = AnimationState.Hold;
+				}
+				continue_outer: {}
+			}
+		}
+
+		static bool HasTimeline (TrackEntry entry, int id) {
+			var timelines = entry.animation.timelines.Items;
+			for (int i = 0, n = entry.animation.timelines.Count; i < n; i++)
+				if (timelines[i].PropertyId == id) return true;
+			return false;
+		}
+
+		/// <returns>The track entry for the animation currently playing on the track, or null if no animation is currently playing.</returns>
+		public TrackEntry GetCurrent (int trackIndex) {
+			return (trackIndex >= tracks.Count) ? null : tracks.Items[trackIndex];
+		}
+
+		override public string ToString () {
+			var buffer = new System.Text.StringBuilder();
+			for (int i = 0, n = tracks.Count; i < n; i++) {
+				TrackEntry entry = tracks.Items[i];
+				if (entry == null) continue;
+				if (buffer.Length > 0) buffer.Append(", ");
+				buffer.Append(entry.ToString());
+			}
+			return buffer.Length == 0 ? "<none>" : buffer.ToString();
+		}
+
+		internal void OnStart (TrackEntry entry) { if (Start != null) Start(entry); }
+		internal void OnInterrupt (TrackEntry entry) { if (Interrupt != null) Interrupt(entry); }
+		internal void OnEnd (TrackEntry entry) { if (End != null) End(entry); }
+		internal void OnDispose (TrackEntry entry) { if (Dispose != null) Dispose(entry); }
+		internal void OnComplete (TrackEntry entry) { if (Complete != null) Complete(entry); }
+		internal void OnEvent (TrackEntry entry, Event e) { if (Event != null) Event(entry, e); }
+	}
+
+	/// <summary>State for the playback of an animation.</summary>
+	public class TrackEntry : Pool<TrackEntry>.IPoolable {
+		internal Animation animation;
+
+		internal TrackEntry next, mixingFrom, mixingTo;
+		internal int trackIndex;
+
+		internal bool loop, holdPrevious;
+		internal float eventThreshold, attachmentThreshold, drawOrderThreshold;
+		internal float animationStart, animationEnd, animationLast, nextAnimationLast;
+		internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f;
+		internal float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha;
+		internal MixBlend mixBlend = MixBlend.Replace;
+		internal readonly ExposedList<int> timelineMode = new ExposedList<int>();
+		internal readonly ExposedList<TrackEntry> timelineHoldMix = new ExposedList<TrackEntry>();
+		internal readonly ExposedList<float> timelinesRotation = new ExposedList<float>();
+
+		// IPoolable.Reset()
+		public void Reset () {
+			next = null;
+			mixingFrom = null;
+			mixingTo = null;
+			animation = null;
+			timelineMode.Clear();
+			timelineHoldMix.Clear();
+			timelinesRotation.Clear();
+
+			Start = null;
+			Interrupt = null;
+			End = null;
+			Dispose = null;
+			Complete = null;
+			Event = null;
+		}
+
+		/// <summary>The index of the track where this entry is either current or queued.</summary>
+		public int TrackIndex { get { return trackIndex; } }
+
+		/// <summary>The animation to apply for this track entry.</summary>
+		public Animation Animation { get { return animation; } }
+
+		/// <summary>
+		/// If true, the animation will repeat. If false, it will not, instead its last frame is applied if played beyond its duration.</summary>
+		public bool Loop { get { return loop; } set { loop = value; } }
+
+		///<summary>
+		/// Seconds to postpone playing the animation. When a track entry is the current track entry, delay postpones incrementing
+		/// the track time. When a track entry is queued, delay is the time from the start of the previous animation to when the
+		/// track entry will become the current track entry. <see cref="TrackEntry.TimeScale"/> affects the delay.</summary>
+		public float Delay { get { return delay; } set { delay = value; } }
+
+		/// <summary>
+		/// Current time in seconds this track entry has been the current track entry. The track time determines
+		/// <see cref="TrackEntry.AnimationTime"/>. The track time can be set to start the animation at a time other than 0, without affecting looping.</summary>
+		public float TrackTime { get { return trackTime; } set { trackTime = value; } }
+
+		/// <summary>
+		/// The track time in seconds when this animation will be removed from the track. Defaults to the animation duration for
+		/// non-looping animations and to <see cref="int.MaxValue"/> for looping animations. If the track end time is reached and no
+		/// other animations are queued for playback, and mixing from any previous animations is complete, properties keyed by the animation,
+		/// are set to the setup pose and the track is cleared.
+		///
+		/// It may be desired to use <see cref="AnimationState.AddEmptyAnimation(int, float, float)"/> to mix the properties back to the
+		/// setup pose over time, rather than have it happen instantly.
+		/// </summary>
+		public float TrackEnd { get { return trackEnd; } set { trackEnd = value; } }
+
+		/// <summary>
+		/// Seconds when this animation starts, both initially and after looping. Defaults to 0.
+		///
+		/// When changing the animation start time, it often makes sense to set <see cref="TrackEntry.AnimationLast"/> to the same value to
+		/// prevent timeline keys before the start time from triggering.
+		/// </summary>
+		public float AnimationStart { get { return animationStart; } set { animationStart = value; } }
+
+		/// <summary>
+		/// Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will
+		/// loop back to <see cref="TrackEntry.AnimationStart"/> at this time. Defaults to the animation duration.</summary>
+		public float AnimationEnd { get { return animationEnd; } set { animationEnd = value; } }
+
+		/// <summary>
+		/// The time in seconds this animation was last applied. Some timelines use this for one-time triggers. Eg, when this
+		/// animation is applied, event timelines will fire all events between the animation last time (exclusive) and animation time
+		/// (inclusive). Defaults to -1 to ensure triggers on frame 0 happen the first time this animation is applied.</summary>
+		public float AnimationLast {
+			get { return animationLast; }
+			set {
+				animationLast = value;
+				nextAnimationLast = value;
+			}
+		}
+
+		/// <summary>
+		/// Uses <see cref="TrackEntry.TrackTime"/> to compute the animation time between <see cref="TrackEntry.AnimationStart"/>. and
+		/// <see cref="TrackEntry.AnimationEnd"/>. When the track time is 0, the animation time is equal to the animation start time.
+		/// </summary>
+		public float AnimationTime {
+			get {
+				if (loop) {
+					float duration = animationEnd - animationStart;
+					if (duration == 0) return animationStart;
+					return (trackTime % duration) + animationStart;
+				}
+				return Math.Min(trackTime + animationStart, animationEnd);
+			}
+		}
+
+		/// <summary>
+		/// Multiplier for the delta time when the animation state is updated, causing time for all animations and mixes to play slower or
+		/// faster. Defaults to 1.
+		///
+		/// <see cref="TrackEntry.MixTime"/> is not affected by track entry time scale, so <see cref="TrackEntry.MixDuration"/> may need to
+		// be adjusted to match the animation speed.
+		///
+		/// When using <see cref="AnimationState.AddAnimation(int, Animation, boolean, float)"> with a <code>delay</code> <= 0, note the
+		/// {<see cref="TrackEntry.Delay"/> is set using the mix duration from the <see cref="AnimationStateData"/>, assuming time scale to be 1. If
+		/// the time scale is not 1, the delay may need to be adjusted.
+		/// </summary>
+		public float TimeScale { get { return timeScale; } set { timeScale = value; } }
+
+		/// <summary>
+		/// Values less than 1 mix this animation with the last skeleton pose. Defaults to 1, which overwrites the last skeleton pose with
+		/// this animation.
+		///
+		/// Typically track 0 is used to completely pose the skeleton, then alpha can be used on higher tracks. It doesn't make sense
+		/// to use alpha on track 0 if the skeleton pose is from the last frame render.
+		/// </summary>
+		public float Alpha { get { return alpha; } set { alpha = value; } }
+
+		/// <summary>
+		/// When the mix percentage (mix time / mix duration) is less than the event threshold, event timelines for the animation
+		/// being mixed out will be applied. Defaults to 0, so event timelines are not applied for an animation being mixed out.</summary>
+		public float EventThreshold { get { return eventThreshold; } set { eventThreshold = value; } }
+
+		/// <summary>
+		/// When the mix percentage (mix time / mix duration) is less than the attachment threshold, attachment timelines for the
+		/// animation being mixed out will be applied. Defaults to 0, so attachment timelines are not applied for an animation being
+		/// mixed out.</summary>
+		public float AttachmentThreshold { get { return attachmentThreshold; } set { attachmentThreshold = value; } }
+
+		/// <summary>
+		/// When the mix percentage (mix time / mix duration) is less than the draw order threshold, draw order timelines for the
+		/// animation being mixed out will be applied. Defaults to 0, so draw order timelines are not applied for an animation being
+		/// mixed out.
+		/// </summary>
+		public float DrawOrderThreshold { get { return drawOrderThreshold; } set { drawOrderThreshold = value; } }
+
+		/// <summary>
+		/// The animation queued to start after this animation, or null.</summary>
+		public TrackEntry Next { get { return next; } }
+
+		/// <summary>
+		/// Returns true if at least one loop has been completed.</summary>
+		public bool IsComplete {
+			get { return trackTime >= animationEnd - animationStart; }
+		}
+
+		/// <summary>
+		/// Seconds from 0 to the mix duration when mixing from the previous animation to this animation. May be slightly more than
+		/// <see cref="TrackEntry.MixDuration"/> when the mix is complete.</summary>
+		public float MixTime { get { return mixTime; } set { mixTime = value; } }
+
+		/// <summary>
+		/// Seconds for mixing from the previous animation to this animation. Defaults to the value provided by
+		/// <see cref="AnimationStateData"/> based on the animation before this animation (if any).
+		///
+		/// The mix duration can be set manually rather than use the value from AnimationStateData.GetMix.
+		/// In that case, the mixDuration must be set before <see cref="AnimationState.Update(float)"/> is next called.
+		/// <para>
+		/// When using <seealso cref="AnimationState.AddAnimation(int, Animation, bool, float)"/> with a
+		/// <code>delay</code> less than or equal to 0, note the <seealso cref="Delay"/> is set using the mix duration from the <see cref=" AnimationStateData"/>,
+		/// not a mix duration set afterward.
+		/// </para>
+		///
+		/// </summary>
+		public float MixDuration { get { return mixDuration; } set { mixDuration = value; } }
+
+		/// <summary>
+		/// Controls how properties keyed in the animation are mixed with lower tracks. Defaults to {@link MixBlend#replace}, which
+		/// replaces the values from the lower tracks with the animation values. {@link MixBlend#add} adds the animation values to
+		/// the values from the lower tracks.
+		/// <para>
+		/// The<code> mixBlend</code> can be set for a new track entry only before
+		/// <code>AnimationState.Apply(Skeleton)</code> is first called.
+		/// </para>
+		/// </summary>
+		public MixBlend MixBlend { get { return mixBlend; } set { mixBlend = value; } }
+
+		/// <summary>
+		/// The track entry for the previous animation when mixing from the previous animation to this animation, or null if no
+		/// mixing is currently occuring. When mixing from multiple animations, MixingFrom makes up a linked list.</summary>
+		public TrackEntry MixingFrom { get { return mixingFrom; } }
+
+		/// <summary>
+		/// If true, when mixing from the previous animation to this animation, the previous animation is applied as normal instead of being mixed out.
+		///
+		/// When mixing between animations that key the same property, if a lower track also keys that property then the value will briefly dip toward the lower track value during the mix. This happens because the first animation mixes from 100% to 0% while the second animation mixes from 0% to 100%. Setting HoldPrevious to true applies the first animation at 100% during the mix so the lower track value is overwritten. Such dipping does not occur on the lowest track which keys the property, only when a higher track also keys the property.
+		///
+		/// Snapping will occur if HoldPrevious is true and this animation does not key all the same properties as the previous animation.
+		/// </summary>
+		public bool HoldPrevious { get { return holdPrevious; } set { holdPrevious = value; } }
+
+		public event AnimationState.TrackEntryDelegate Start, Interrupt, End, Dispose, Complete;
+		public event AnimationState.TrackEntryEventDelegate Event;
+		internal void OnStart () { if (Start != null) Start(this); }
+		internal void OnInterrupt () { if (Interrupt != null) Interrupt(this); }
+		internal void OnEnd () { if (End != null) End(this); }
+		internal void OnDispose () { if (Dispose != null) Dispose(this); }
+		internal void OnComplete () { if (Complete != null) Complete(this); }
+		internal void OnEvent (Event e) { if (Event != null) Event(this, e); }
+
+		/// <summary>
+		/// <para>
+		/// Resets the rotation directions for mixing this entry's rotate timelines. This can be useful to avoid bones rotating the
+		/// long way around when using <see cref="alpha"/> and starting animations on other tracks.
+		/// </para>
+		/// <para>
+		/// Mixing with <see cref="MixBlend.Replace"/> involves finding a rotation between two others, which has two possible solutions:
+		/// the short way or the long way around.The two rotations likely change over time, so which direction is the short or long
+		/// way also changes.If the short way was always chosen, bones would flip to the other side when that direction became the
+		/// long way. TrackEntry chooses the short way the first time it is applied and remembers that direction.
+		/// </para>
+		/// </summary>
+		public void ResetRotationDirections () {
+			timelinesRotation.Clear();
+		}
+
+		override public string ToString () {
+			return animation == null ? "<none>" : animation.name;
+		}
+	}
+
+	class EventQueue {
+		private readonly List<EventQueueEntry> eventQueueEntries = new List<EventQueueEntry>();
+		internal bool drainDisabled;
+
+		private readonly AnimationState state;
+		private readonly Pool<TrackEntry> trackEntryPool;
+		internal event Action AnimationsChanged;
+
+		internal EventQueue (AnimationState state, Action HandleAnimationsChanged, Pool<TrackEntry> trackEntryPool) {
+			this.state = state;
+			this.AnimationsChanged += HandleAnimationsChanged;
+			this.trackEntryPool = trackEntryPool;
+		}
+
+		struct EventQueueEntry {
+			public EventType type;
+			public TrackEntry entry;
+			public Event e;
+
+			public EventQueueEntry (EventType eventType, TrackEntry trackEntry, Event e = null) {
+				this.type = eventType;
+				this.entry = trackEntry;
+				this.e = e;
+			}
+		}
+
+		enum EventType {
+			Start, Interrupt, End, Dispose, Complete, Event
+		}
+
+		internal void Start (TrackEntry entry) {
+			eventQueueEntries.Add(new EventQueueEntry(EventType.Start, entry));
+			if (AnimationsChanged != null) AnimationsChanged();
+		}
+
+		internal void Interrupt (TrackEntry entry) {
+			eventQueueEntries.Add(new EventQueueEntry(EventType.Interrupt, entry));
+		}
+
+		internal void End (TrackEntry entry) {
+			eventQueueEntries.Add(new EventQueueEntry(EventType.End, entry));
+			if (AnimationsChanged != null) AnimationsChanged();
+		}
+
+		internal void Dispose (TrackEntry entry) {
+			eventQueueEntries.Add(new EventQueueEntry(EventType.Dispose, entry));
+		}
+
+		internal void Complete (TrackEntry entry) {
+			eventQueueEntries.Add(new EventQueueEntry(EventType.Complete, entry));
+		}
+
+		internal void Event (TrackEntry entry, Event e) {
+			eventQueueEntries.Add(new EventQueueEntry(EventType.Event, entry, e));
+		}
+
+		/// <summary>Raises all events in the queue and drains the queue.</summary>
+		internal void Drain () {
+			if (drainDisabled) return;
+			drainDisabled = true;
+
+			var entries = this.eventQueueEntries;
+			AnimationState state = this.state;
+
+			// Don't cache entries.Count so callbacks can queue their own events (eg, call SetAnimation in AnimationState_Complete).
+			for (int i = 0; i < entries.Count; i++) {
+				var queueEntry = entries[i];
+				TrackEntry trackEntry = queueEntry.entry;
+
+				switch (queueEntry.type) {
+				case EventType.Start:
+					trackEntry.OnStart();
+					state.OnStart(trackEntry);
+					break;
+				case EventType.Interrupt:
+					trackEntry.OnInterrupt();
+					state.OnInterrupt(trackEntry);
+					break;
+				case EventType.End:
+					trackEntry.OnEnd();
+					state.OnEnd(trackEntry);
+					goto case EventType.Dispose; // Fall through. (C#)
+				case EventType.Dispose:
+					trackEntry.OnDispose();
+					state.OnDispose(trackEntry);
+					trackEntryPool.Free(trackEntry); // Pooling
+					break;
+				case EventType.Complete:
+					trackEntry.OnComplete();
+					state.OnComplete(trackEntry);
+					break;
+				case EventType.Event:
+					trackEntry.OnEvent(queueEntry.e);
+					state.OnEvent(trackEntry, queueEntry.e);
+					break;
+				}
+			}
+			eventQueueEntries.Clear();
+
+			drainDisabled = false;
+		}
+
+		internal void Clear () {
+			eventQueueEntries.Clear();
+		}
+	}
+
+	public class Pool<T> where T : class, new() {
+		public readonly int max;
+		readonly Stack<T> freeObjects;
+
+		public int Count { get { return freeObjects.Count; } }
+		public int Peak { get; private set; }
+
+		public Pool (int initialCapacity = 16, int max = int.MaxValue) {
+			freeObjects = new Stack<T>(initialCapacity);
+			this.max = max;
+		}
+
+		public T Obtain () {
+			return freeObjects.Count == 0 ? new T() : freeObjects.Pop();
+		}
+
+		public void Free (T obj) {
+			if (obj == null) throw new ArgumentNullException("obj", "obj cannot be null");
+			if (freeObjects.Count < max) {
+				freeObjects.Push(obj);
+				Peak = Math.Max(Peak, freeObjects.Count);
+			}
+			Reset(obj);
+		}
+
+//		protected void FreeAll (List<T> objects) {
+//			if (objects == null) throw new ArgumentNullException("objects", "objects cannot be null.");
+//			var freeObjects = this.freeObjects;
+//			int max = this.max;
+//			for (int i = 0; i < objects.Count; i++) {
+//				T obj = objects[i];
+//				if (obj == null) continue;
+//				if (freeObjects.Count < max) freeObjects.Push(obj);
+//				Reset(obj);
+//			}
+//			Peak = Math.Max(Peak, freeObjects.Count);
+//		}
+
+		public void Clear () {
+			freeObjects.Clear();
+		}
+
+		protected void Reset (T obj) {
+			var poolable = obj as IPoolable;
+			if (poolable != null) poolable.Reset();
+		}
+
+		public interface IPoolable {
+			void Reset ();
+		}
+	}
+
+}

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

@@ -1,115 +1,115 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-using System;
-using System.Collections.Generic;
-
-namespace Spine {
-
-	/// <summary>Stores mix (crossfade) durations to be applied when AnimationState animations are changed.</summary>
-	public class AnimationStateData {
-		internal SkeletonData skeletonData;
-		readonly Dictionary<AnimationPair, float> animationToMixTime = new Dictionary<AnimationPair, float>(AnimationPairComparer.Instance);
-		internal float defaultMix;
-
-		/// <summary>The SkeletonData to look up animations when they are specified by name.</summary>
-		public SkeletonData SkeletonData { get { return skeletonData; } }
-
-		/// <summary>
-		/// The mix duration to use when no mix duration has been specifically defined between two animations.</summary>
-		public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } }
-
-		public AnimationStateData (SkeletonData skeletonData) {
-			if (skeletonData == null) throw new ArgumentException("skeletonData cannot be null.", "skeletonData");
-			this.skeletonData = skeletonData;
-		}
-
-		/// <summary>Sets a mix duration by animation names.</summary>
-		public void SetMix (string fromName, string toName, float duration) {
-			Animation from = skeletonData.FindAnimation(fromName);
-			if (from == null) throw new ArgumentException("Animation not found: " + fromName, "fromName");
-			Animation to = skeletonData.FindAnimation(toName);
-			if (to == null) throw new ArgumentException("Animation not found: " + toName, "toName");
-			SetMix(from, to, duration);
-		}
-
-		/// <summary>Sets a mix duration when changing from the specified animation to the other. 
-		/// See TrackEntry.MixDuration.</summary>
-		public void SetMix (Animation from, Animation to, float duration) {
-			if (from == null) throw new ArgumentNullException("from", "from cannot be null.");
-			if (to == null) throw new ArgumentNullException("to", "to cannot be null.");
-			AnimationPair key = new AnimationPair(from, to);
-			animationToMixTime.Remove(key);
-			animationToMixTime.Add(key, duration);
-		}
-
-		/// <summary>
-		/// The mix duration to use when changing from the specified animation to the other, 
-		/// or the DefaultMix if no mix duration has been set.
-		/// </summary>
-		public float GetMix (Animation from, Animation to) {
-			if (from == null) throw new ArgumentNullException("from", "from cannot be null.");
-			if (to == null) throw new ArgumentNullException("to", "to cannot be null.");
-			AnimationPair key = new AnimationPair(from, to);
-			float duration;
-			if (animationToMixTime.TryGetValue(key, out duration)) return duration;
-			return defaultMix;
-		}
-
-		public struct AnimationPair {
-			public readonly Animation a1;
-			public readonly Animation a2;
-
-			public AnimationPair (Animation a1, Animation a2) {
-				this.a1 = a1;
-				this.a2 = a2;
-			}
-
-			public override string ToString () {
-				return a1.name + "->" + a2.name;
-			}
-		}
-
-		// Avoids boxing in the dictionary.
-		public class AnimationPairComparer : IEqualityComparer<AnimationPair> {
-			public static readonly AnimationPairComparer Instance = new AnimationPairComparer();
-
-			bool IEqualityComparer<AnimationPair>.Equals (AnimationPair x, AnimationPair y) {
-				return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2);
-			}
-
-			int IEqualityComparer<AnimationPair>.GetHashCode (AnimationPair obj) {
-				// from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2);
-				int h1 = obj.a1.GetHashCode();
-				return (((h1 << 5) + h1) ^ obj.a2.GetHashCode());
-			}
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+using System.Collections.Generic;
+
+namespace Spine {
+
+	/// <summary>Stores mix (crossfade) durations to be applied when AnimationState animations are changed.</summary>
+	public class AnimationStateData {
+		internal SkeletonData skeletonData;
+		readonly Dictionary<AnimationPair, float> animationToMixTime = new Dictionary<AnimationPair, float>(AnimationPairComparer.Instance);
+		internal float defaultMix;
+
+		/// <summary>The SkeletonData to look up animations when they are specified by name.</summary>
+		public SkeletonData SkeletonData { get { return skeletonData; } }
+
+		/// <summary>
+		/// The mix duration to use when no mix duration has been specifically defined between two animations.</summary>
+		public float DefaultMix { get { return defaultMix; } set { defaultMix = value; } }
+
+		public AnimationStateData (SkeletonData skeletonData) {
+			if (skeletonData == null) throw new ArgumentException("skeletonData cannot be null.", "skeletonData");
+			this.skeletonData = skeletonData;
+		}
+
+		/// <summary>Sets a mix duration by animation names.</summary>
+		public void SetMix (string fromName, string toName, float duration) {
+			Animation from = skeletonData.FindAnimation(fromName);
+			if (from == null) throw new ArgumentException("Animation not found: " + fromName, "fromName");
+			Animation to = skeletonData.FindAnimation(toName);
+			if (to == null) throw new ArgumentException("Animation not found: " + toName, "toName");
+			SetMix(from, to, duration);
+		}
+
+		/// <summary>Sets a mix duration when changing from the specified animation to the other. 
+		/// See TrackEntry.MixDuration.</summary>
+		public void SetMix (Animation from, Animation to, float duration) {
+			if (from == null) throw new ArgumentNullException("from", "from cannot be null.");
+			if (to == null) throw new ArgumentNullException("to", "to cannot be null.");
+			AnimationPair key = new AnimationPair(from, to);
+			animationToMixTime.Remove(key);
+			animationToMixTime.Add(key, duration);
+		}
+
+		/// <summary>
+		/// The mix duration to use when changing from the specified animation to the other, 
+		/// or the DefaultMix if no mix duration has been set.
+		/// </summary>
+		public float GetMix (Animation from, Animation to) {
+			if (from == null) throw new ArgumentNullException("from", "from cannot be null.");
+			if (to == null) throw new ArgumentNullException("to", "to cannot be null.");
+			AnimationPair key = new AnimationPair(from, to);
+			float duration;
+			if (animationToMixTime.TryGetValue(key, out duration)) return duration;
+			return defaultMix;
+		}
+
+		public struct AnimationPair {
+			public readonly Animation a1;
+			public readonly Animation a2;
+
+			public AnimationPair (Animation a1, Animation a2) {
+				this.a1 = a1;
+				this.a2 = a2;
+			}
+
+			public override string ToString () {
+				return a1.name + "->" + a2.name;
+			}
+		}
+
+		// Avoids boxing in the dictionary.
+		public class AnimationPairComparer : IEqualityComparer<AnimationPair> {
+			public static readonly AnimationPairComparer Instance = new AnimationPairComparer();
+
+			bool IEqualityComparer<AnimationPair>.Equals (AnimationPair x, AnimationPair y) {
+				return ReferenceEquals(x.a1, y.a1) && ReferenceEquals(x.a2, y.a2);
+			}
+
+			int IEqualityComparer<AnimationPair>.GetHashCode (AnimationPair obj) {
+				// from Tuple.CombineHashCodes // return (((h1 << 5) + h1) ^ h2);
+				int h1 = obj.a1.GetHashCode();
+				return (((h1 << 5) + h1) ^ obj.a2.GetHashCode());
+			}
+		}
+	}
+}

+ 315 - 315
spine-csharp/src/Atlas.cs

@@ -1,315 +1,315 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-#if (UNITY_5 || UNITY_5_3_OR_NEWER || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1)
-#define IS_UNITY
-#endif
-
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Reflection;
-
-#if WINDOWS_STOREAPP
-using System.Threading.Tasks;
-using Windows.Storage;
-#endif
-
-namespace Spine {
-	public class Atlas : IEnumerable<AtlasRegion> {
-		readonly List<AtlasPage> pages = new List<AtlasPage>();
-		List<AtlasRegion> regions = new List<AtlasRegion>();
-		TextureLoader textureLoader;
-
-		#region IEnumerable implementation
-		public IEnumerator<AtlasRegion> GetEnumerator () {
-			return regions.GetEnumerator();
-		}
-
-		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () {
-			return regions.GetEnumerator();
-		}
-		#endregion
-
-		#if !(IS_UNITY)
-		#if WINDOWS_STOREAPP
-		private async Task ReadFile(string path, TextureLoader textureLoader) {
-			var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
-			var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false);
-			using (var reader = new StreamReader(await file.OpenStreamForReadAsync().ConfigureAwait(false))) {
-				try {
-					Load(reader, Path.GetDirectoryName(path), textureLoader);
-				} catch (Exception ex) {
-					throw new Exception("Error reading atlas file: " + path, ex);
-				}
-			}
-		}
-
-		public Atlas(string path, TextureLoader textureLoader) {
-			this.ReadFile(path, textureLoader).Wait();
-		}
-		#else
-
-		public Atlas (string path, TextureLoader textureLoader) {
-
-			#if WINDOWS_PHONE
-			Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path);
-			using (StreamReader reader = new StreamReader(stream)) {
-			#else
-			using (StreamReader reader = new StreamReader(path)) {
-			#endif // WINDOWS_PHONE
-
-				try {
-					Load(reader, Path.GetDirectoryName(path), textureLoader);
-				} catch (Exception ex) {
-					throw new Exception("Error reading atlas file: " + path, ex);
-				}
-
-			}
-		}
-		#endif // WINDOWS_STOREAPP
-
-		#endif
-
-		public Atlas (TextReader reader, string dir, TextureLoader textureLoader) {
-			Load(reader, dir, textureLoader);
-		}
-
-		public Atlas (List<AtlasPage> pages, List<AtlasRegion> regions) {
-			this.pages = pages;
-			this.regions = regions;
-			this.textureLoader = null;
-		}
-
-		private void Load (TextReader reader, string imagesDir, TextureLoader textureLoader) {
-			if (textureLoader == null) throw new ArgumentNullException("textureLoader", "textureLoader cannot be null.");
-			this.textureLoader = textureLoader;
-
-			string[] tuple = new string[4];
-			AtlasPage page = null;
-			while (true) {
-				string line = reader.ReadLine();
-				if (line == null) break;
-				if (line.Trim().Length == 0)
-					page = null;
-				else if (page == null) {
-					page = new AtlasPage();
-					page.name = line;
-
-					if (ReadTuple(reader, tuple) == 2) { // size is only optional for an atlas packed with an old TexturePacker.
-						page.width = int.Parse(tuple[0]);
-						page.height = int.Parse(tuple[1]);
-						ReadTuple(reader, tuple);
-					}
-					page.format = (Format)Enum.Parse(typeof(Format), tuple[0], false);
-
-					ReadTuple(reader, tuple);
-					page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0], false);
-					page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1], false);
-
-					string direction = ReadValue(reader);
-					page.uWrap = TextureWrap.ClampToEdge;
-					page.vWrap = TextureWrap.ClampToEdge;
-					if (direction == "x")
-						page.uWrap = TextureWrap.Repeat;
-					else if (direction == "y")
-						page.vWrap = TextureWrap.Repeat;
-					else if (direction == "xy")
-						page.uWrap = page.vWrap = TextureWrap.Repeat;
-
-					textureLoader.Load(page, Path.Combine(imagesDir, line));
-
-					pages.Add(page);
-
-				} else {
-					AtlasRegion region = new AtlasRegion();
-					region.name = line;
-					region.page = page;
-
-					region.rotate = Boolean.Parse(ReadValue(reader));
-
-					ReadTuple(reader, tuple);
-					int x = int.Parse(tuple[0]);
-					int y = int.Parse(tuple[1]);
-
-					ReadTuple(reader, tuple);
-					int width = int.Parse(tuple[0]);
-					int height = int.Parse(tuple[1]);
-
-					region.u = x / (float)page.width;
-					region.v = y / (float)page.height;
-					if (region.rotate) {
-						region.u2 = (x + height) / (float)page.width;
-						region.v2 = (y + width) / (float)page.height;
-					} else {
-						region.u2 = (x + width) / (float)page.width;
-						region.v2 = (y + height) / (float)page.height;
-					}
-					region.x = x;
-					region.y = y;
-					region.width = Math.Abs(width);
-					region.height = Math.Abs(height);
-
-					if (ReadTuple(reader, tuple) == 4) { // split is optional
-						region.splits = new [] {int.Parse(tuple[0]), int.Parse(tuple[1]),
-								int.Parse(tuple[2]), int.Parse(tuple[3])};
-
-						if (ReadTuple(reader, tuple) == 4) { // pad is optional, but only present with splits
-							region.pads = new [] {int.Parse(tuple[0]), int.Parse(tuple[1]),
-									int.Parse(tuple[2]), int.Parse(tuple[3])};
-
-							ReadTuple(reader, tuple);
-						}
-					}
-
-					region.originalWidth = int.Parse(tuple[0]);
-					region.originalHeight = int.Parse(tuple[1]);
-
-					ReadTuple(reader, tuple);
-					region.offsetX = int.Parse(tuple[0]);
-					region.offsetY = int.Parse(tuple[1]);
-
-					region.index = int.Parse(ReadValue(reader));
-
-					regions.Add(region);
-				}
-			}
-		}
-
-		static string ReadValue (TextReader reader) {
-			string line = reader.ReadLine();
-			int colon = line.IndexOf(':');
-			if (colon == -1) throw new Exception("Invalid line: " + line);
-			return line.Substring(colon + 1).Trim();
-		}
-
-		/// <summary>Returns the number of tuple values read (1, 2 or 4).</summary>
-		static int ReadTuple (TextReader reader, string[] tuple) {
-			string line = reader.ReadLine();
-			int colon = line.IndexOf(':');
-			if (colon == -1) throw new Exception("Invalid line: " + line);
-			int i = 0, lastMatch = colon + 1;
-			for (; i < 3; i++) {
-				int comma = line.IndexOf(',', lastMatch);
-				if (comma == -1) break;
-				tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim();
-				lastMatch = comma + 1;
-			}
-			tuple[i] = line.Substring(lastMatch).Trim();
-			return i + 1;
-		}
-
-		public void FlipV () {
-			for (int i = 0, n = regions.Count; i < n; i++) {
-				AtlasRegion region = regions[i];
-				region.v = 1 - region.v;
-				region.v2 = 1 - region.v2;
-			}
-		}
-
-		/// <summary>Returns the first region found with the specified name. This method uses string comparison to find the region, so the result
-		/// should be cached rather than calling this method multiple times.</summary>
-		/// <returns>The region, or null.</returns>
-		public AtlasRegion FindRegion (string name) {
-			for (int i = 0, n = regions.Count; i < n; i++)
-				if (regions[i].name == name) return regions[i];
-			return null;
-		}
-
-		public void Dispose () {
-			if (textureLoader == null) return;
-			for (int i = 0, n = pages.Count; i < n; i++)
-				textureLoader.Unload(pages[i].rendererObject);
-		}
-	}
-
-	public enum Format {
-		Alpha,
-		Intensity,
-		LuminanceAlpha,
-		RGB565,
-		RGBA4444,
-		RGB888,
-		RGBA8888
-	}
-
-	public enum TextureFilter {
-		Nearest,
-		Linear,
-		MipMap,
-		MipMapNearestNearest,
-		MipMapLinearNearest,
-		MipMapNearestLinear,
-		MipMapLinearLinear
-	}
-
-	public enum TextureWrap {
-		MirroredRepeat,
-		ClampToEdge,
-		Repeat
-	}
-
-	public class AtlasPage {
-		public string name;
-		public Format format;
-		public TextureFilter minFilter;
-		public TextureFilter magFilter;
-		public TextureWrap uWrap;
-		public TextureWrap vWrap;
-		public object rendererObject;
-		public int width, height;
-
-		public AtlasPage Clone () {
-			return MemberwiseClone() as AtlasPage;
-		}
-	}
-
-	public class AtlasRegion {
-		public AtlasPage page;
-		public string name;
-		public int x, y, width, height;
-		public float u, v, u2, v2;
-		public float offsetX, offsetY;
-		public int originalWidth, originalHeight;
-		public int index;
-		public bool rotate;
-		public int[] splits;
-		public int[] pads;
-
-		public AtlasRegion Clone () {
-			return MemberwiseClone() as AtlasRegion;
-		}
-	}
-
-	public interface TextureLoader {
-		void Load (AtlasPage page, string path);
-		void Unload (Object texture);
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+#if (UNITY_5 || UNITY_5_3_OR_NEWER || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1)
+#define IS_UNITY
+#endif
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+
+#if WINDOWS_STOREAPP
+using System.Threading.Tasks;
+using Windows.Storage;
+#endif
+
+namespace Spine {
+	public class Atlas : IEnumerable<AtlasRegion> {
+		readonly List<AtlasPage> pages = new List<AtlasPage>();
+		List<AtlasRegion> regions = new List<AtlasRegion>();
+		TextureLoader textureLoader;
+
+		#region IEnumerable implementation
+		public IEnumerator<AtlasRegion> GetEnumerator () {
+			return regions.GetEnumerator();
+		}
+
+		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () {
+			return regions.GetEnumerator();
+		}
+		#endregion
+
+		#if !(IS_UNITY)
+		#if WINDOWS_STOREAPP
+		private async Task ReadFile(string path, TextureLoader textureLoader) {
+			var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
+			var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false);
+			using (var reader = new StreamReader(await file.OpenStreamForReadAsync().ConfigureAwait(false))) {
+				try {
+					Load(reader, Path.GetDirectoryName(path), textureLoader);
+				} catch (Exception ex) {
+					throw new Exception("Error reading atlas file: " + path, ex);
+				}
+			}
+		}
+
+		public Atlas(string path, TextureLoader textureLoader) {
+			this.ReadFile(path, textureLoader).Wait();
+		}
+		#else
+
+		public Atlas (string path, TextureLoader textureLoader) {
+
+			#if WINDOWS_PHONE
+			Stream stream = Microsoft.Xna.Framework.TitleContainer.OpenStream(path);
+			using (StreamReader reader = new StreamReader(stream)) {
+			#else
+			using (StreamReader reader = new StreamReader(path)) {
+			#endif // WINDOWS_PHONE
+
+				try {
+					Load(reader, Path.GetDirectoryName(path), textureLoader);
+				} catch (Exception ex) {
+					throw new Exception("Error reading atlas file: " + path, ex);
+				}
+
+			}
+		}
+		#endif // WINDOWS_STOREAPP
+
+		#endif
+
+		public Atlas (TextReader reader, string dir, TextureLoader textureLoader) {
+			Load(reader, dir, textureLoader);
+		}
+
+		public Atlas (List<AtlasPage> pages, List<AtlasRegion> regions) {
+			this.pages = pages;
+			this.regions = regions;
+			this.textureLoader = null;
+		}
+
+		private void Load (TextReader reader, string imagesDir, TextureLoader textureLoader) {
+			if (textureLoader == null) throw new ArgumentNullException("textureLoader", "textureLoader cannot be null.");
+			this.textureLoader = textureLoader;
+
+			string[] tuple = new string[4];
+			AtlasPage page = null;
+			while (true) {
+				string line = reader.ReadLine();
+				if (line == null) break;
+				if (line.Trim().Length == 0)
+					page = null;
+				else if (page == null) {
+					page = new AtlasPage();
+					page.name = line;
+
+					if (ReadTuple(reader, tuple) == 2) { // size is only optional for an atlas packed with an old TexturePacker.
+						page.width = int.Parse(tuple[0]);
+						page.height = int.Parse(tuple[1]);
+						ReadTuple(reader, tuple);
+					}
+					page.format = (Format)Enum.Parse(typeof(Format), tuple[0], false);
+
+					ReadTuple(reader, tuple);
+					page.minFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[0], false);
+					page.magFilter = (TextureFilter)Enum.Parse(typeof(TextureFilter), tuple[1], false);
+
+					string direction = ReadValue(reader);
+					page.uWrap = TextureWrap.ClampToEdge;
+					page.vWrap = TextureWrap.ClampToEdge;
+					if (direction == "x")
+						page.uWrap = TextureWrap.Repeat;
+					else if (direction == "y")
+						page.vWrap = TextureWrap.Repeat;
+					else if (direction == "xy")
+						page.uWrap = page.vWrap = TextureWrap.Repeat;
+
+					textureLoader.Load(page, Path.Combine(imagesDir, line));
+
+					pages.Add(page);
+
+				} else {
+					AtlasRegion region = new AtlasRegion();
+					region.name = line;
+					region.page = page;
+
+					region.rotate = Boolean.Parse(ReadValue(reader));
+
+					ReadTuple(reader, tuple);
+					int x = int.Parse(tuple[0]);
+					int y = int.Parse(tuple[1]);
+
+					ReadTuple(reader, tuple);
+					int width = int.Parse(tuple[0]);
+					int height = int.Parse(tuple[1]);
+
+					region.u = x / (float)page.width;
+					region.v = y / (float)page.height;
+					if (region.rotate) {
+						region.u2 = (x + height) / (float)page.width;
+						region.v2 = (y + width) / (float)page.height;
+					} else {
+						region.u2 = (x + width) / (float)page.width;
+						region.v2 = (y + height) / (float)page.height;
+					}
+					region.x = x;
+					region.y = y;
+					region.width = Math.Abs(width);
+					region.height = Math.Abs(height);
+
+					if (ReadTuple(reader, tuple) == 4) { // split is optional
+						region.splits = new [] {int.Parse(tuple[0]), int.Parse(tuple[1]),
+								int.Parse(tuple[2]), int.Parse(tuple[3])};
+
+						if (ReadTuple(reader, tuple) == 4) { // pad is optional, but only present with splits
+							region.pads = new [] {int.Parse(tuple[0]), int.Parse(tuple[1]),
+									int.Parse(tuple[2]), int.Parse(tuple[3])};
+
+							ReadTuple(reader, tuple);
+						}
+					}
+
+					region.originalWidth = int.Parse(tuple[0]);
+					region.originalHeight = int.Parse(tuple[1]);
+
+					ReadTuple(reader, tuple);
+					region.offsetX = int.Parse(tuple[0]);
+					region.offsetY = int.Parse(tuple[1]);
+
+					region.index = int.Parse(ReadValue(reader));
+
+					regions.Add(region);
+				}
+			}
+		}
+
+		static string ReadValue (TextReader reader) {
+			string line = reader.ReadLine();
+			int colon = line.IndexOf(':');
+			if (colon == -1) throw new Exception("Invalid line: " + line);
+			return line.Substring(colon + 1).Trim();
+		}
+
+		/// <summary>Returns the number of tuple values read (1, 2 or 4).</summary>
+		static int ReadTuple (TextReader reader, string[] tuple) {
+			string line = reader.ReadLine();
+			int colon = line.IndexOf(':');
+			if (colon == -1) throw new Exception("Invalid line: " + line);
+			int i = 0, lastMatch = colon + 1;
+			for (; i < 3; i++) {
+				int comma = line.IndexOf(',', lastMatch);
+				if (comma == -1) break;
+				tuple[i] = line.Substring(lastMatch, comma - lastMatch).Trim();
+				lastMatch = comma + 1;
+			}
+			tuple[i] = line.Substring(lastMatch).Trim();
+			return i + 1;
+		}
+
+		public void FlipV () {
+			for (int i = 0, n = regions.Count; i < n; i++) {
+				AtlasRegion region = regions[i];
+				region.v = 1 - region.v;
+				region.v2 = 1 - region.v2;
+			}
+		}
+
+		/// <summary>Returns the first region found with the specified name. This method uses string comparison to find the region, so the result
+		/// should be cached rather than calling this method multiple times.</summary>
+		/// <returns>The region, or null.</returns>
+		public AtlasRegion FindRegion (string name) {
+			for (int i = 0, n = regions.Count; i < n; i++)
+				if (regions[i].name == name) return regions[i];
+			return null;
+		}
+
+		public void Dispose () {
+			if (textureLoader == null) return;
+			for (int i = 0, n = pages.Count; i < n; i++)
+				textureLoader.Unload(pages[i].rendererObject);
+		}
+	}
+
+	public enum Format {
+		Alpha,
+		Intensity,
+		LuminanceAlpha,
+		RGB565,
+		RGBA4444,
+		RGB888,
+		RGBA8888
+	}
+
+	public enum TextureFilter {
+		Nearest,
+		Linear,
+		MipMap,
+		MipMapNearestNearest,
+		MipMapLinearNearest,
+		MipMapNearestLinear,
+		MipMapLinearLinear
+	}
+
+	public enum TextureWrap {
+		MirroredRepeat,
+		ClampToEdge,
+		Repeat
+	}
+
+	public class AtlasPage {
+		public string name;
+		public Format format;
+		public TextureFilter minFilter;
+		public TextureFilter magFilter;
+		public TextureWrap uWrap;
+		public TextureWrap vWrap;
+		public object rendererObject;
+		public int width, height;
+
+		public AtlasPage Clone () {
+			return MemberwiseClone() as AtlasPage;
+		}
+	}
+
+	public class AtlasRegion {
+		public AtlasPage page;
+		public string name;
+		public int x, y, width, height;
+		public float u, v, u2, v2;
+		public float offsetX, offsetY;
+		public int originalWidth, originalHeight;
+		public int index;
+		public bool rotate;
+		public int[] splits;
+		public int[] pads;
+
+		public AtlasRegion Clone () {
+			return MemberwiseClone() as AtlasRegion;
+		}
+	}
+
+	public interface TextureLoader {
+		void Load (AtlasPage page, string path);
+		void Unload (Object texture);
+	}
+}

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

@@ -1,109 +1,109 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-using System;
-
-namespace Spine {
-
-	/// <summary>
-	/// An AttachmentLoader that configures attachments using texture regions from an Atlas.
-	/// See <a href='http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data'>Loading Skeleton Data</a> in the Spine Runtimes Guide.
-	/// </summary>
-	public class AtlasAttachmentLoader : AttachmentLoader {
-		private Atlas[] atlasArray;
-
-		public AtlasAttachmentLoader (params Atlas[] atlasArray) {
-			if (atlasArray == null) throw new ArgumentNullException("atlas array cannot be null.");
-			this.atlasArray = atlasArray;
-		}
-
-		public RegionAttachment NewRegionAttachment (Skin skin, string name, string path) {
-			AtlasRegion region = FindRegion(path);
-			if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name));
-			RegionAttachment attachment = new RegionAttachment(name);
-			attachment.RendererObject = region;
-			attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate);
-			attachment.regionOffsetX = region.offsetX;
-			attachment.regionOffsetY = region.offsetY;
-			attachment.regionWidth = region.width;
-			attachment.regionHeight = region.height;
-			attachment.regionOriginalWidth = region.originalWidth;
-			attachment.regionOriginalHeight = region.originalHeight;
-			return attachment;
-		}
-
-		public MeshAttachment NewMeshAttachment (Skin skin, string name, string path) {
-			AtlasRegion region = FindRegion(path);
-			if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name));
-			MeshAttachment attachment = new MeshAttachment(name);
-			attachment.RendererObject = region;
-			attachment.RegionU = region.u;
-			attachment.RegionV = region.v;
-			attachment.RegionU2 = region.u2;
-			attachment.RegionV2 = region.v2;
-			attachment.RegionRotate = region.rotate;
-			attachment.regionOffsetX = region.offsetX;
-			attachment.regionOffsetY = region.offsetY;
-			attachment.regionWidth = region.width;
-			attachment.regionHeight = region.height;
-			attachment.regionOriginalWidth = region.originalWidth;
-			attachment.regionOriginalHeight = region.originalHeight;
-			return attachment;
-		}			
-
-		public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name) {
-			return new BoundingBoxAttachment(name);
-		}
-
-		public PathAttachment NewPathAttachment (Skin skin, string name) {
-			return new PathAttachment(name);
-		}
-
-		public PointAttachment NewPointAttachment (Skin skin, string name) {
-			return new PointAttachment(name);
-		}
-
-		public ClippingAttachment NewClippingAttachment(Skin skin, string name) {
-			return new ClippingAttachment(name);
-		}
-
-		public AtlasRegion FindRegion (string name) {
-			AtlasRegion region;
-
-			for (int i = 0; i < atlasArray.Length; i++) {
-				region = atlasArray[i].FindRegion(name);
-				if (region != null)
-					return region;
-			}
-
-			return null;
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+
+	/// <summary>
+	/// An AttachmentLoader that configures attachments using texture regions from an Atlas.
+	/// See <a href='http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data'>Loading Skeleton Data</a> in the Spine Runtimes Guide.
+	/// </summary>
+	public class AtlasAttachmentLoader : AttachmentLoader {
+		private Atlas[] atlasArray;
+
+		public AtlasAttachmentLoader (params Atlas[] atlasArray) {
+			if (atlasArray == null) throw new ArgumentNullException("atlas array cannot be null.");
+			this.atlasArray = atlasArray;
+		}
+
+		public RegionAttachment NewRegionAttachment (Skin skin, string name, string path) {
+			AtlasRegion region = FindRegion(path);
+			if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name));
+			RegionAttachment attachment = new RegionAttachment(name);
+			attachment.RendererObject = region;
+			attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate);
+			attachment.regionOffsetX = region.offsetX;
+			attachment.regionOffsetY = region.offsetY;
+			attachment.regionWidth = region.width;
+			attachment.regionHeight = region.height;
+			attachment.regionOriginalWidth = region.originalWidth;
+			attachment.regionOriginalHeight = region.originalHeight;
+			return attachment;
+		}
+
+		public MeshAttachment NewMeshAttachment (Skin skin, string name, string path) {
+			AtlasRegion region = FindRegion(path);
+			if (region == null) throw new ArgumentException(string.Format("Region not found in atlas: {0} (region attachment: {1})", path, name));
+			MeshAttachment attachment = new MeshAttachment(name);
+			attachment.RendererObject = region;
+			attachment.RegionU = region.u;
+			attachment.RegionV = region.v;
+			attachment.RegionU2 = region.u2;
+			attachment.RegionV2 = region.v2;
+			attachment.RegionRotate = region.rotate;
+			attachment.regionOffsetX = region.offsetX;
+			attachment.regionOffsetY = region.offsetY;
+			attachment.regionWidth = region.width;
+			attachment.regionHeight = region.height;
+			attachment.regionOriginalWidth = region.originalWidth;
+			attachment.regionOriginalHeight = region.originalHeight;
+			return attachment;
+		}			
+
+		public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name) {
+			return new BoundingBoxAttachment(name);
+		}
+
+		public PathAttachment NewPathAttachment (Skin skin, string name) {
+			return new PathAttachment(name);
+		}
+
+		public PointAttachment NewPointAttachment (Skin skin, string name) {
+			return new PointAttachment(name);
+		}
+
+		public ClippingAttachment NewClippingAttachment(Skin skin, string name) {
+			return new ClippingAttachment(name);
+		}
+
+		public AtlasRegion FindRegion (string name) {
+			AtlasRegion region;
+
+			for (int i = 0; i < atlasArray.Length; i++) {
+				region = atlasArray[i].FindRegion(name);
+				if (region != null)
+					return region;
+			}
+
+			return null;
+		}
+	}
+}

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

@@ -1,50 +1,50 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-using System;
-
-namespace Spine {
-	abstract public class Attachment {
-		public string Name { get; private set; }
-
-		protected Attachment (string name) {
-			if (name == null) throw new ArgumentNullException("name", "name cannot be null");
-			Name = name;
-		}
-
-		override public string ToString () {
-			return Name;
-		}
-	}
-
-	public interface IHasRendererObject {
-		object RendererObject { get; set; }
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+	abstract public class Attachment {
+		public string Name { get; private set; }
+
+		protected Attachment (string name) {
+			if (name == null) throw new ArgumentNullException("name", "name cannot be null");
+			Name = name;
+		}
+
+		override public string ToString () {
+			return Name;
+		}
+	}
+
+	public interface IHasRendererObject {
+		object RendererObject { get; set; }
+	}
+}

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

@@ -1,49 +1,49 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-namespace Spine {
-	public interface AttachmentLoader {
-		/// <return>May be null to not load any attachment.</return>
-		RegionAttachment NewRegionAttachment (Skin skin, string name, string path);
-
-		/// <return>May be null to not load any attachment.</return>
-		MeshAttachment NewMeshAttachment (Skin skin, string name, string path);
-
-		/// <return>May be null to not load any attachment.</return>
-		BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name);
-
-		/// <returns>May be null to not load any attachment</returns>
-		PathAttachment NewPathAttachment (Skin skin, string name);
-
-		PointAttachment NewPointAttachment (Skin skin, string name);
-
-		ClippingAttachment NewClippingAttachment (Skin skin, string name);
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+namespace Spine {
+	public interface AttachmentLoader {
+		/// <return>May be null to not load any attachment.</return>
+		RegionAttachment NewRegionAttachment (Skin skin, string name, string path);
+
+		/// <return>May be null to not load any attachment.</return>
+		MeshAttachment NewMeshAttachment (Skin skin, string name, string path);
+
+		/// <return>May be null to not load any attachment.</return>
+		BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name);
+
+		/// <returns>May be null to not load any attachment</returns>
+		PathAttachment NewPathAttachment (Skin skin, string name);
+
+		PointAttachment NewPointAttachment (Skin skin, string name);
+
+		ClippingAttachment NewClippingAttachment (Skin skin, string name);
+	}
+}

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

@@ -1,35 +1,35 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-namespace Spine {
-	public enum AttachmentType {
-		Region, Boundingbox, Mesh, Linkedmesh, Path, Point, Clipping
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+namespace Spine {
+	public enum AttachmentType {
+		Region, Boundingbox, Mesh, Linkedmesh, Path, Point, Clipping
+	}
+}

+ 40 - 40
spine-csharp/src/Attachments/BoundingBoxAttachment.cs

@@ -1,40 +1,40 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-using System;
-
-namespace Spine {
-	/// <summary>Attachment that has a polygon for bounds checking.</summary>
-	public class BoundingBoxAttachment : VertexAttachment {
-		public BoundingBoxAttachment (string name)
-			: base(name) {
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+	/// <summary>Attachment that has a polygon for bounds checking.</summary>
+	public class BoundingBoxAttachment : VertexAttachment {
+		public BoundingBoxAttachment (string name)
+			: base(name) {
+		}
+	}
+}

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

@@ -1,134 +1,134 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-using System;
-
-namespace Spine {
-	/// <summary>Attachment that displays a texture region using a mesh.</summary>
-	public class MeshAttachment : VertexAttachment, IHasRendererObject {
-		internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight;
-		private MeshAttachment parentMesh;
-		internal float[] uvs, regionUVs;
-		internal int[] triangles;
-		internal float r = 1, g = 1, b = 1, a = 1;
-		internal int hulllength;
-		internal bool inheritDeform;
-
-		public int HullLength { get { return hulllength; } set { hulllength = value; } }
-		public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } }
-		/// <summary>The UV pair for each vertex, normalized within the entire texture. <seealso cref="MeshAttachment.UpdateUVs"/></summary>
-		public float[] UVs { get { return uvs; } set { uvs = value; } }
-		public int[] Triangles { get { return triangles; } set { triangles = value; } }
-
-		public float R { get { return r; } set { r = value; } }
-		public float G { get { return g; } set { g = value; } }
-		public float B { get { return b; } set { b = value; } }
-		public float A { get { return a; } set { a = value; } }
-
-		public string Path { get; set; }
-		public object RendererObject { get; set; }
-		public float RegionU { get; set; }
-		public float RegionV { get; set; }
-		public float RegionU2 { get; set; }
-		public float RegionV2 { get; set; }
-		public bool RegionRotate { get; set; }
-		public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } }
-		public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated.
-		public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } }
-		public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size.
-		public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } }
-		public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size.
-
-		public bool InheritDeform { get { return inheritDeform; } set { inheritDeform = value; } }
-
-		public MeshAttachment ParentMesh {
-			get { return parentMesh; }
-			set {
-				parentMesh = value;
-				if (value != null) {
-					bones = value.bones;
-					vertices = value.vertices;
-					worldVerticesLength = value.worldVerticesLength;
-					regionUVs = value.regionUVs;
-					triangles = value.triangles;
-					HullLength = value.HullLength;
-					Edges = value.Edges;
-					Width = value.Width;
-					Height = value.Height;
-				}
-			}
-		}
-
-		// Nonessential.
-		public int[] Edges { get; set; }
-		public float Width { get; set; }
-		public float Height { get; set; }
-
-		public MeshAttachment (string name)
-			: base(name) {
-		}
-
-		public void UpdateUVs () {
-			float[] regionUVs = this.regionUVs;
-			if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length];
-			float[] uvs = this.uvs;
-
-			if (RegionRotate) {
-				float textureHeight = this.regionWidth / (RegionV2 - RegionV);
-				float textureWidth = this.regionHeight / (RegionU2 - RegionU);
-				float u = RegionU - (RegionOriginalHeight - RegionOffsetY - RegionHeight) / textureWidth;
-				float v = RegionV - (RegionOriginalWidth - RegionOffsetX - RegionWidth) / textureHeight;
-				float width = RegionOriginalHeight / textureWidth;
-				float height = RegionOriginalWidth / textureHeight;
-
-				for (int i = 0, n = uvs.Length; i < n; i += 2) {
-					uvs[i] = u + regionUVs[i + 1] * width;
-					uvs[i + 1] = v + height - regionUVs[i] * height;
-				}
-			} else {
-				float textureWidth = this.regionWidth / (RegionU2 - RegionU);
-				float textureHeight = this.regionHeight / (RegionV2 - RegionV);
-				float u = RegionU - RegionOffsetX / textureWidth;
-				float v = RegionV - (RegionOriginalHeight - RegionOffsetY - RegionHeight) / textureHeight;
-				float width = RegionOriginalWidth / textureWidth;
-				float height = RegionOriginalHeight / textureHeight;
-
-				for (int i = 0, n = uvs.Length; i < n; i += 2) {
-					uvs[i] = u + regionUVs[i] * width;
-					uvs[i + 1] = v + regionUVs[i + 1] * height;
-				}
-			}
-		}
-
-		override public bool ApplyDeform (VertexAttachment sourceAttachment) {
-			return this == sourceAttachment || (inheritDeform && parentMesh == sourceAttachment);
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+	/// <summary>Attachment that displays a texture region using a mesh.</summary>
+	public class MeshAttachment : VertexAttachment, IHasRendererObject {
+		internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight;
+		private MeshAttachment parentMesh;
+		internal float[] uvs, regionUVs;
+		internal int[] triangles;
+		internal float r = 1, g = 1, b = 1, a = 1;
+		internal int hulllength;
+		internal bool inheritDeform;
+
+		public int HullLength { get { return hulllength; } set { hulllength = value; } }
+		public float[] RegionUVs { get { return regionUVs; } set { regionUVs = value; } }
+		/// <summary>The UV pair for each vertex, normalized within the entire texture. <seealso cref="MeshAttachment.UpdateUVs"/></summary>
+		public float[] UVs { get { return uvs; } set { uvs = value; } }
+		public int[] Triangles { get { return triangles; } set { triangles = value; } }
+
+		public float R { get { return r; } set { r = value; } }
+		public float G { get { return g; } set { g = value; } }
+		public float B { get { return b; } set { b = value; } }
+		public float A { get { return a; } set { a = value; } }
+
+		public string Path { get; set; }
+		public object RendererObject { get; set; }
+		public float RegionU { get; set; }
+		public float RegionV { get; set; }
+		public float RegionU2 { get; set; }
+		public float RegionV2 { get; set; }
+		public bool RegionRotate { get; set; }
+		public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } }
+		public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated.
+		public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } }
+		public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size.
+		public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } }
+		public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size.
+
+		public bool InheritDeform { get { return inheritDeform; } set { inheritDeform = value; } }
+
+		public MeshAttachment ParentMesh {
+			get { return parentMesh; }
+			set {
+				parentMesh = value;
+				if (value != null) {
+					bones = value.bones;
+					vertices = value.vertices;
+					worldVerticesLength = value.worldVerticesLength;
+					regionUVs = value.regionUVs;
+					triangles = value.triangles;
+					HullLength = value.HullLength;
+					Edges = value.Edges;
+					Width = value.Width;
+					Height = value.Height;
+				}
+			}
+		}
+
+		// Nonessential.
+		public int[] Edges { get; set; }
+		public float Width { get; set; }
+		public float Height { get; set; }
+
+		public MeshAttachment (string name)
+			: base(name) {
+		}
+
+		public void UpdateUVs () {
+			float[] regionUVs = this.regionUVs;
+			if (this.uvs == null || this.uvs.Length != regionUVs.Length) this.uvs = new float[regionUVs.Length];
+			float[] uvs = this.uvs;
+
+			if (RegionRotate) {
+				float textureHeight = this.regionWidth / (RegionV2 - RegionV);
+				float textureWidth = this.regionHeight / (RegionU2 - RegionU);
+				float u = RegionU - (RegionOriginalHeight - RegionOffsetY - RegionHeight) / textureWidth;
+				float v = RegionV - (RegionOriginalWidth - RegionOffsetX - RegionWidth) / textureHeight;
+				float width = RegionOriginalHeight / textureWidth;
+				float height = RegionOriginalWidth / textureHeight;
+
+				for (int i = 0, n = uvs.Length; i < n; i += 2) {
+					uvs[i] = u + regionUVs[i + 1] * width;
+					uvs[i + 1] = v + height - regionUVs[i] * height;
+				}
+			} else {
+				float textureWidth = this.regionWidth / (RegionU2 - RegionU);
+				float textureHeight = this.regionHeight / (RegionV2 - RegionV);
+				float u = RegionU - RegionOffsetX / textureWidth;
+				float v = RegionV - (RegionOriginalHeight - RegionOffsetY - RegionHeight) / textureHeight;
+				float width = RegionOriginalWidth / textureWidth;
+				float height = RegionOriginalHeight / textureHeight;
+
+				for (int i = 0, n = uvs.Length; i < n; i += 2) {
+					uvs[i] = u + regionUVs[i] * width;
+					uvs[i + 1] = v + regionUVs[i + 1] * height;
+				}
+			}
+		}
+
+		override public bool ApplyDeform (VertexAttachment sourceAttachment) {
+			return this == sourceAttachment || (inheritDeform && parentMesh == sourceAttachment);
+		}
+	}
+}

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

@@ -1,48 +1,48 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-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 v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+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) {
+		}			
+	}
+}

+ 183 - 183
spine-csharp/src/Attachments/RegionAttachment.cs

@@ -1,183 +1,183 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-using System;
-
-namespace Spine {
-	/// <summary>Attachment that displays a texture region.</summary>
-	public class RegionAttachment : Attachment, IHasRendererObject {
-		public const int BLX = 0;
-		public const int BLY = 1;
-		public const int ULX = 2;
-		public const int ULY = 3;
-		public const int URX = 4;
-		public const int URY = 5;
-		public const int BRX = 6;
-		public const int BRY = 7;
-
-		internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height;
-		internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight;
-		internal float[] offset = new float[8], uvs = new float[8];
-		internal float r = 1, g = 1, b = 1, a = 1;
-
-		public float X { get { return x; } set { x = value; } }
-		public float Y { get { return y; } set { y = value; } }
-		public float Rotation { get { return rotation; } set { rotation = value; } }
-		public float ScaleX { get { return scaleX; } set { scaleX = value; } }
-		public float ScaleY { get { return scaleY; } set { scaleY = value; } }
-		public float Width { get { return width; } set { width = value; } }
-		public float Height { get { return height; } set { height = value; } }
-
-		public float R { get { return r; } set { r = value; } }
-		public float G { get { return g; } set { g = value; } }
-		public float B { get { return b; } set { b = value; } }
-		public float A { get { return a; } set { a = value; } }
-
-		public string Path { get; set; }
-		public object RendererObject { get; set; }
-		public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } }
-		public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated.
-		public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } }
-		public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size.
-		public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } }
-		public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size.
-
-		public float[] Offset { get { return offset; } }
-		public float[] UVs { get { return uvs; } }
-
-		public RegionAttachment (string name)
-			: base(name) {
-		}
-
-		public void UpdateOffset () {
-			float width = this.width;
-			float height = this.height;
-			float localX2 = width * 0.5f;
-			float localY2 = height * 0.5f;
-			float localX = -localX2;
-			float localY = -localY2;
-			if (regionOriginalWidth != 0) { // if (region != null)
-				localX += regionOffsetX / regionOriginalWidth * width;
-				localY += regionOffsetY / regionOriginalHeight * height;
-				localX2 -= (regionOriginalWidth - regionOffsetX - regionWidth) / regionOriginalWidth * width;
-				localY2 -= (regionOriginalHeight - regionOffsetY - regionHeight) / regionOriginalHeight * height;
-			}
-			float scaleX = this.scaleX;
-			float scaleY = this.scaleY;
-			localX *= scaleX;
-			localY *= scaleY;
-			localX2 *= scaleX;
-			localY2 *= scaleY;
-			float rotation = this.rotation;
-			float cos = MathUtils.CosDeg(rotation);
-			float sin = MathUtils.SinDeg(rotation);
-			float x = this.x;
-			float y = this.y;
-			float localXCos = localX * cos + x;
-			float localXSin = localX * sin;
-			float localYCos = localY * cos + y;
-			float localYSin = localY * sin;
-			float localX2Cos = localX2 * cos + x;
-			float localX2Sin = localX2 * sin;
-			float localY2Cos = localY2 * cos + y;
-			float localY2Sin = localY2 * sin;
-			float[] offset = this.offset;
-			offset[BLX] = localXCos - localYSin;
-			offset[BLY] = localYCos + localXSin;
-			offset[ULX] = localXCos - localY2Sin;
-			offset[ULY] = localY2Cos + localXSin;
-			offset[URX] = localX2Cos - localY2Sin;
-			offset[URY] = localY2Cos + localX2Sin;
-			offset[BRX] = localX2Cos - localYSin;
-			offset[BRY] = localYCos + localX2Sin;
-		}
-
-		public void SetUVs (float u, float v, float u2, float v2, bool rotate) {
-			float[] uvs = this.uvs;
-			// UV values differ from RegionAttachment.java
-			if (rotate) {
-				uvs[URX] = u;
-				uvs[URY] = v2;
-				uvs[BRX] = u;
-				uvs[BRY] = v;
-				uvs[BLX] = u2;
-				uvs[BLY] = v;
-				uvs[ULX] = u2;
-				uvs[ULY] = v2;
-			} else {
-				uvs[ULX] = u;
-				uvs[ULY] = v2;
-				uvs[URX] = u;
-				uvs[URY] = v;
-				uvs[BRX] = u2;
-				uvs[BRY] = v;
-				uvs[BLX] = u2;
-				uvs[BLY] = v2;
-			}
-		}
-
-		/// <summary>Transforms the attachment's four vertices to world coordinates.</summary>
-		/// <param name="bone">The parent bone.</param>
-		/// <param name="worldVertices">The output world vertices. Must have a length greater than or equal to offset + 8.</param>
-		/// <param name="offset">The worldVertices index to begin writing values.</param>
-		/// <param name="stride">The number of worldVertices entries between the value pairs written.</param>
-		public void ComputeWorldVertices (Bone bone, float[] worldVertices, int offset, int stride = 2) {
-			float[] vertexOffset = this.offset;
-			float bwx = bone.worldX, bwy = bone.worldY;
-			float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
-			float offsetX, offsetY;
-
-			// Vertex order is different from RegionAttachment.java
-			offsetX = vertexOffset[BRX]; // 0
-			offsetY = vertexOffset[BRY]; // 1
-			worldVertices[offset] = offsetX * a + offsetY * b + bwx; // bl
-			worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy;
-			offset += stride;
-
-			offsetX = vertexOffset[BLX]; // 2
-			offsetY = vertexOffset[BLY]; // 3
-			worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ul
-			worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy;
-			offset += stride;
-
-			offsetX = vertexOffset[ULX]; // 4
-			offsetY = vertexOffset[ULY]; // 5
-			worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ur
-			worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy;
-			offset += stride;
-
-			offsetX = vertexOffset[URX]; // 6
-			offsetY = vertexOffset[URY]; // 7
-			worldVertices[offset] = offsetX * a + offsetY * b + bwx; // br
-			worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy;
-			//offset += stride;
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+	/// <summary>Attachment that displays a texture region.</summary>
+	public class RegionAttachment : Attachment, IHasRendererObject {
+		public const int BLX = 0;
+		public const int BLY = 1;
+		public const int ULX = 2;
+		public const int ULY = 3;
+		public const int URX = 4;
+		public const int URY = 5;
+		public const int BRX = 6;
+		public const int BRY = 7;
+
+		internal float x, y, rotation, scaleX = 1, scaleY = 1, width, height;
+		internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight;
+		internal float[] offset = new float[8], uvs = new float[8];
+		internal float r = 1, g = 1, b = 1, a = 1;
+
+		public float X { get { return x; } set { x = value; } }
+		public float Y { get { return y; } set { y = value; } }
+		public float Rotation { get { return rotation; } set { rotation = value; } }
+		public float ScaleX { get { return scaleX; } set { scaleX = value; } }
+		public float ScaleY { get { return scaleY; } set { scaleY = value; } }
+		public float Width { get { return width; } set { width = value; } }
+		public float Height { get { return height; } set { height = value; } }
+
+		public float R { get { return r; } set { r = value; } }
+		public float G { get { return g; } set { g = value; } }
+		public float B { get { return b; } set { b = value; } }
+		public float A { get { return a; } set { a = value; } }
+
+		public string Path { get; set; }
+		public object RendererObject { get; set; }
+		public float RegionOffsetX { get { return regionOffsetX; } set { regionOffsetX = value; } }
+		public float RegionOffsetY { get { return regionOffsetY; } set { regionOffsetY = value; } } // Pixels stripped from the bottom left, unrotated.
+		public float RegionWidth { get { return regionWidth; } set { regionWidth = value; } }
+		public float RegionHeight { get { return regionHeight; } set { regionHeight = value; } } // Unrotated, stripped size.
+		public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } }
+		public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size.
+
+		public float[] Offset { get { return offset; } }
+		public float[] UVs { get { return uvs; } }
+
+		public RegionAttachment (string name)
+			: base(name) {
+		}
+
+		public void UpdateOffset () {
+			float width = this.width;
+			float height = this.height;
+			float localX2 = width * 0.5f;
+			float localY2 = height * 0.5f;
+			float localX = -localX2;
+			float localY = -localY2;
+			if (regionOriginalWidth != 0) { // if (region != null)
+				localX += regionOffsetX / regionOriginalWidth * width;
+				localY += regionOffsetY / regionOriginalHeight * height;
+				localX2 -= (regionOriginalWidth - regionOffsetX - regionWidth) / regionOriginalWidth * width;
+				localY2 -= (regionOriginalHeight - regionOffsetY - regionHeight) / regionOriginalHeight * height;
+			}
+			float scaleX = this.scaleX;
+			float scaleY = this.scaleY;
+			localX *= scaleX;
+			localY *= scaleY;
+			localX2 *= scaleX;
+			localY2 *= scaleY;
+			float rotation = this.rotation;
+			float cos = MathUtils.CosDeg(rotation);
+			float sin = MathUtils.SinDeg(rotation);
+			float x = this.x;
+			float y = this.y;
+			float localXCos = localX * cos + x;
+			float localXSin = localX * sin;
+			float localYCos = localY * cos + y;
+			float localYSin = localY * sin;
+			float localX2Cos = localX2 * cos + x;
+			float localX2Sin = localX2 * sin;
+			float localY2Cos = localY2 * cos + y;
+			float localY2Sin = localY2 * sin;
+			float[] offset = this.offset;
+			offset[BLX] = localXCos - localYSin;
+			offset[BLY] = localYCos + localXSin;
+			offset[ULX] = localXCos - localY2Sin;
+			offset[ULY] = localY2Cos + localXSin;
+			offset[URX] = localX2Cos - localY2Sin;
+			offset[URY] = localY2Cos + localX2Sin;
+			offset[BRX] = localX2Cos - localYSin;
+			offset[BRY] = localYCos + localX2Sin;
+		}
+
+		public void SetUVs (float u, float v, float u2, float v2, bool rotate) {
+			float[] uvs = this.uvs;
+			// UV values differ from RegionAttachment.java
+			if (rotate) {
+				uvs[URX] = u;
+				uvs[URY] = v2;
+				uvs[BRX] = u;
+				uvs[BRY] = v;
+				uvs[BLX] = u2;
+				uvs[BLY] = v;
+				uvs[ULX] = u2;
+				uvs[ULY] = v2;
+			} else {
+				uvs[ULX] = u;
+				uvs[ULY] = v2;
+				uvs[URX] = u;
+				uvs[URY] = v;
+				uvs[BRX] = u2;
+				uvs[BRY] = v;
+				uvs[BLX] = u2;
+				uvs[BLY] = v2;
+			}
+		}
+
+		/// <summary>Transforms the attachment's four vertices to world coordinates.</summary>
+		/// <param name="bone">The parent bone.</param>
+		/// <param name="worldVertices">The output world vertices. Must have a length greater than or equal to offset + 8.</param>
+		/// <param name="offset">The worldVertices index to begin writing values.</param>
+		/// <param name="stride">The number of worldVertices entries between the value pairs written.</param>
+		public void ComputeWorldVertices (Bone bone, float[] worldVertices, int offset, int stride = 2) {
+			float[] vertexOffset = this.offset;
+			float bwx = bone.worldX, bwy = bone.worldY;
+			float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
+			float offsetX, offsetY;
+
+			// Vertex order is different from RegionAttachment.java
+			offsetX = vertexOffset[BRX]; // 0
+			offsetY = vertexOffset[BRY]; // 1
+			worldVertices[offset] = offsetX * a + offsetY * b + bwx; // bl
+			worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy;
+			offset += stride;
+
+			offsetX = vertexOffset[BLX]; // 2
+			offsetY = vertexOffset[BLY]; // 3
+			worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ul
+			worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy;
+			offset += stride;
+
+			offsetX = vertexOffset[ULX]; // 4
+			offsetY = vertexOffset[ULY]; // 5
+			worldVertices[offset] = offsetX * a + offsetY * b + bwx; // ur
+			worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy;
+			offset += stride;
+
+			offsetX = vertexOffset[URX]; // 6
+			offsetY = vertexOffset[URY]; // 7
+			worldVertices[offset] = offsetX * a + offsetY * b + bwx; // br
+			worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy;
+			//offset += stride;
+		}
+	}
+}

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

@@ -1,130 +1,130 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-using System;
-
-namespace Spine {
-	/// <summary>>An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's vertices.</summary> 
-	public class VertexAttachment : Attachment {
-		static int nextID = 0;
-		static readonly Object nextIdLock = new Object();
-
-		internal readonly int id;
-		internal int[] bones;
-		internal float[] vertices;
-		internal int worldVerticesLength;
-
-		/// <summary>Gets a unique ID for this attachment.</summary>
-		public int Id { get { return id; } }
-		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) {
-
-			lock (VertexAttachment.nextIdLock) {
-				id = (VertexAttachment.nextID++ & 65535) << 11;
-			}
-		}
-
-		public void ComputeWorldVertices (Slot slot, float[] worldVertices) {
-			ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0);
-		}
-
-		/// <summary>Transforms local vertices to world coordinates.</summary>
-		/// <param name="start">The index of the first <see cref="Vertices"/> value to transform. Each vertex has 2 values, x and y.</param>
-		/// <param name="count">The number of world vertex values to output. Must be less than or equal to <see cref="WorldVerticesLength"/> - start.</param>
-		/// <param name="worldVertices">The output world vertices. Must have a length greater than or equal to <paramref name="offset"/> + <paramref name="count"/>.</param>
-		/// <param name="offset">The <paramref name="worldVertices"/> index to begin writing values.</param>
-		/// <param name="stride">The number of <paramref name="worldVertices"/> entries between the value pairs written.</param>
-		public void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) {
-			count = offset + (count >> 1) * stride;
-			Skeleton skeleton = slot.bone.skeleton;
-			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;
-				float x = bone.worldX, y = bone.worldY;
-				float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
-				for (int vv = start, w = offset; w < count; vv += 2, w += stride) {
-					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;
-			}
-			var skeletonBones = skeleton.bones.Items;
-			if (deformArray.Count == 0) {
-				for (int w = offset, b = skip * 3; w < count; w += stride) {
-					float wx = 0, wy = 0;
-					int n = bones[v++];
-					n += v;
-					for (; v < n; v++, b += 3) {
-						Bone bone = skeletonBones[bones[v]];
-						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 += stride) {
-					float wx = 0, wy = 0;
-					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 v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+	/// <summary>>An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's vertices.</summary> 
+	public class VertexAttachment : Attachment {
+		static int nextID = 0;
+		static readonly Object nextIdLock = new Object();
+
+		internal readonly int id;
+		internal int[] bones;
+		internal float[] vertices;
+		internal int worldVerticesLength;
+
+		/// <summary>Gets a unique ID for this attachment.</summary>
+		public int Id { get { return id; } }
+		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) {
+
+			lock (VertexAttachment.nextIdLock) {
+				id = (VertexAttachment.nextID++ & 65535) << 11;
+			}
+		}
+
+		public void ComputeWorldVertices (Slot slot, float[] worldVertices) {
+			ComputeWorldVertices(slot, 0, worldVerticesLength, worldVertices, 0);
+		}
+
+		/// <summary>Transforms local vertices to world coordinates.</summary>
+		/// <param name="start">The index of the first <see cref="Vertices"/> value to transform. Each vertex has 2 values, x and y.</param>
+		/// <param name="count">The number of world vertex values to output. Must be less than or equal to <see cref="WorldVerticesLength"/> - start.</param>
+		/// <param name="worldVertices">The output world vertices. Must have a length greater than or equal to <paramref name="offset"/> + <paramref name="count"/>.</param>
+		/// <param name="offset">The <paramref name="worldVertices"/> index to begin writing values.</param>
+		/// <param name="stride">The number of <paramref name="worldVertices"/> entries between the value pairs written.</param>
+		public void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset, int stride = 2) {
+			count = offset + (count >> 1) * stride;
+			Skeleton skeleton = slot.bone.skeleton;
+			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;
+				float x = bone.worldX, y = bone.worldY;
+				float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
+				for (int vv = start, w = offset; w < count; vv += 2, w += stride) {
+					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;
+			}
+			var skeletonBones = skeleton.bones.Items;
+			if (deformArray.Count == 0) {
+				for (int w = offset, b = skip * 3; w < count; w += stride) {
+					float wx = 0, wy = 0;
+					int n = bones[v++];
+					n += v;
+					for (; v < n; v++, b += 3) {
+						Bone bone = skeletonBones[bones[v]];
+						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 += stride) {
+					float wx = 0, wy = 0;
+					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;
+		}			
+	}
+}

+ 35 - 35
spine-csharp/src/BlendMode.cs

@@ -1,35 +1,35 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-namespace Spine {
-	public enum BlendMode {
-		Normal, Additive, Multiply, Screen
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+namespace Spine {
+	public enum BlendMode {
+		Normal, Additive, Multiply, Screen
+	}
+}

+ 362 - 362
spine-csharp/src/Bone.cs

@@ -1,362 +1,362 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-using System;
-
-namespace Spine {
-	/// <summary>
-	/// Stores a bone's current pose.
-	/// <para>
-	/// A bone has a local transform which is used to compute its world transform. A bone also has an applied transform, which is a
-	/// local transform that can be applied to compute the world transform. The local transform and applied transform may differ if a
-	/// constraint or application code modifies the world transform after it was computed from the local transform.
-	/// </para>
-	/// </summary>
-	public class Bone : IUpdatable {
-		static public bool yDown;
-
-		internal BoneData data;
-		internal Skeleton skeleton;
-		internal Bone parent;
-		internal ExposedList<Bone> children = new ExposedList<Bone>();
-		internal float x, y, rotation, scaleX, scaleY, shearX, shearY;
-		internal float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY;
-		internal bool appliedValid;
-
-		internal float a, b, worldX;
-		internal float c, d, worldY;
-
-		internal bool sorted;
-
-		public BoneData Data { get { return data; } }
-		public Skeleton Skeleton { get { return skeleton; } }
-		public Bone Parent { get { return parent; } }
-		public ExposedList<Bone> Children { get { return children; } }
-		/// <summary>The local X translation.</summary>
-		public float X { get { return x; } set { x = value; } }
-		/// <summary>The local Y translation.</summary>
-		public float Y { get { return y; } set { y = value; } }
-		/// <summary>The local rotation.</summary>
-		public float Rotation { get { return rotation; } set { rotation = value; } }
-
-		/// <summary>The local scaleX.</summary>
-		public float ScaleX { get { return scaleX; } set { scaleX = value; } }
-
-		/// <summary>The local scaleY.</summary>
-		public float ScaleY { get { return scaleY; } set { scaleY = value; } }
-
-		/// <summary>The local shearX.</summary>
-		public float ShearX { get { return shearX; } set { shearX = value; } }
-
-		/// <summary>The local shearY.</summary>
-		public float ShearY { get { return shearY; } set { shearY = value; } }
-
-		/// <summary>The rotation, as calculated by any constraints.</summary>
-		public float AppliedRotation { get { return arotation; } set { arotation = value; } }
-
-		/// <summary>The applied local x translation.</summary>
-		public float AX { get { return ax; } set { ax = value; } }
-
-		/// <summary>The applied local y translation.</summary>
-		public float AY { get { return ay; } set { ay = value; } }
-
-		/// <summary>The applied local scaleX.</summary>
-		public float AScaleX { get { return ascaleX; } set { ascaleX = value; } }
-
-		/// <summary>The applied local scaleY.</summary>
-		public float AScaleY { get { return ascaleY; } set { ascaleY = value; } }
-
-		/// <summary>The applied local shearX.</summary>
-		public float AShearX { get { return ashearX; } set { ashearX = value; } }
-
-		/// <summary>The applied local shearY.</summary>
-		public float AShearY { get { return ashearY; } set { ashearY = value; } }
-
-		public float A { get { return a; } }
-		public float B { get { return b; } }
-		public float C { get { return c; } }
-		public float D { get { return d; } }
-
-		public float WorldX { get { return worldX; } }
-		public float WorldY { get { return worldY; } }
-		public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.RadDeg; } }
-		public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.RadDeg; } }
-
-		/// <summary>Returns the magnitide (always positive) of the world scale X.</summary>
-		public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } }
-		/// <summary>Returns the magnitide (always positive) of the world scale Y.</summary>
-		public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } }
-
-		/// <param name="parent">May be null.</param>
-		public Bone (BoneData data, Skeleton skeleton, Bone parent) {
-			if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
-			if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
-			this.data = data;
-			this.skeleton = skeleton;
-			this.parent = parent;
-			SetToSetupPose();
-		}
-
-		/// <summary>Same as <see cref="UpdateWorldTransform"/>. This method exists for Bone to implement <see cref="Spine.IUpdatable"/>.</summary>
-		public void Update () {
-			UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY);
-		}
-
-		/// <summary>Computes the world transform using the parent bone and this bone's local transform.</summary>
-		public void UpdateWorldTransform () {
-			UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY);
-		}
-
-		/// <summary>Computes the world transform using the parent bone and the specified local transform.</summary>
-		public void UpdateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) {
-			ax = x;
-			ay = y;
-			arotation = rotation;
-			ascaleX = scaleX;
-			ascaleY = scaleY;
-			ashearX = shearX;
-			ashearY = shearY;
-			appliedValid = true;
-			Skeleton skeleton = this.skeleton;
-
-			Bone parent = this.parent;
-			if (parent == null) { // Root bone.
-				float rotationY = rotation + 90 + shearY, sx = skeleton.scaleX, sy = skeleton.scaleY;
-				a = MathUtils.CosDeg(rotation + shearX) * scaleX * sx;
-				b = MathUtils.CosDeg(rotationY) * scaleY * sy;
-				c = MathUtils.SinDeg(rotation + shearX) * scaleX * sx;
-				d = MathUtils.SinDeg(rotationY) * scaleY * sy;
-				worldX = x * sx + skeleton.x;
-				worldY = y * sy + skeleton.y;
-				return;
-			}
-
-			float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
-			worldX = pa * x + pb * y + parent.worldX;
-			worldY = pc * x + pd * y + parent.worldY;
-
-			switch (data.transformMode) {
-			case TransformMode.Normal: {
-					float rotationY = rotation + 90 + shearY;
-					float la = MathUtils.CosDeg(rotation + shearX) * scaleX;
-					float lb = MathUtils.CosDeg(rotationY) * scaleY;
-					float lc = MathUtils.SinDeg(rotation + shearX) * scaleX;
-					float ld = MathUtils.SinDeg(rotationY) * scaleY;
-					a = pa * la + pb * lc;
-					b = pa * lb + pb * ld;
-					c = pc * la + pd * lc;
-					d = pc * lb + pd * ld;
-					return;
-				}
-			case TransformMode.OnlyTranslation: {
-					float rotationY = rotation + 90 + shearY;
-					a = MathUtils.CosDeg(rotation + shearX) * scaleX;
-					b = MathUtils.CosDeg(rotationY) * scaleY;
-					c = MathUtils.SinDeg(rotation + shearX) * scaleX;
-					d = MathUtils.SinDeg(rotationY) * scaleY;
-					break;
-				}
-			case TransformMode.NoRotationOrReflection: {
-					float s = pa * pa + pc * pc, prx;
-					if (s > 0.0001f) {
-						s = Math.Abs(pa * pd - pb * pc) / s;
-						pb = pc * s;
-						pd = pa * s;
-						prx = MathUtils.Atan2(pc, pa) * MathUtils.RadDeg;
-					} else {
-						pa = 0;
-						pc = 0;
-						prx = 90 - MathUtils.Atan2(pd, pb) * MathUtils.RadDeg;
-					}
-					float rx = rotation + shearX - prx;
-					float ry = rotation + shearY - prx + 90;
-					float la = MathUtils.CosDeg(rx) * scaleX;
-					float lb = MathUtils.CosDeg(ry) * scaleY;
-					float lc = MathUtils.SinDeg(rx) * scaleX;
-					float ld = MathUtils.SinDeg(ry) * scaleY;
-					a = pa * la - pb * lc;
-					b = pa * lb - pb * ld;
-					c = pc * la + pd * lc;
-					d = pc * lb + pd * ld;
-					break;
-				}
-			case TransformMode.NoScale:
-			case TransformMode.NoScaleOrReflection: {
-					float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation);
-					float za = (pa * cos + pb * sin) / skeleton.scaleX;
-					float zc = (pc * cos + pd * sin) / skeleton.scaleY;
-					float s = (float)Math.Sqrt(za * za + zc * zc);
-					if (s > 0.00001f) s = 1 / s;
-					za *= s;
-					zc *= s;
-					s = (float)Math.Sqrt(za * za + zc * zc);
-					if (data.transformMode == TransformMode.NoScale
-						&& (pa * pd - pb * pc < 0) != (skeleton.scaleX < 0 != skeleton.scaleY < 0)) s = -s;
-
-					float r = MathUtils.PI / 2 + MathUtils.Atan2(zc, za);
-					float zb = MathUtils.Cos(r) * s;
-					float zd = MathUtils.Sin(r) * s;
-					float la = MathUtils.CosDeg(shearX) * scaleX;
-					float lb = MathUtils.CosDeg(90 + shearY) * scaleY;
-					float lc = MathUtils.SinDeg(shearX) * scaleX;
-					float ld = MathUtils.SinDeg(90 + shearY) * scaleY;
-					a = za * la + zb * lc;
-					b = za * lb + zb * ld;
-					c = zc * la + zd * lc;
-					d = zc * lb + zd * ld;
-					break;
-				}
-			}
-
-			a *= skeleton.scaleX;
-			b *= skeleton.scaleX;
-			c *= skeleton.scaleY;
-			d *= skeleton.scaleY;
-		}
-
-		public void SetToSetupPose () {
-			BoneData data = this.data;
-			x = data.x;
-			y = data.y;
-			rotation = data.rotation;
-			scaleX = data.scaleX;
-			scaleY = data.scaleY;
-			shearX = data.shearX;
-			shearY = data.shearY;
-		}
-
-		/// <summary>
-		/// Computes the individual applied transform values from the world transform. This can be useful to perform processing using
-		/// the applied transform after the world transform has been modified directly (eg, by a constraint)..
-		/// 
-		/// Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation.
-		/// </summary>
-		internal void UpdateAppliedTransform () {
-			appliedValid = true;
-			Bone parent = this.parent;
-			if (parent == null) {
-				ax = worldX;
-				ay = worldY;
-				arotation = MathUtils.Atan2(c, a) * MathUtils.RadDeg;
-				ascaleX = (float)Math.Sqrt(a * a + c * c);
-				ascaleY = (float)Math.Sqrt(b * b + d * d);
-				ashearX = 0;
-				ashearY = MathUtils.Atan2(a * b + c * d, a * d - b * c) * MathUtils.RadDeg;
-				return;
-			}
-			float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
-			float pid = 1 / (pa * pd - pb * pc);
-			float dx = worldX - parent.worldX, dy = worldY - parent.worldY;
-			ax = (dx * pd * pid - dy * pb * pid);
-			ay = (dy * pa * pid - dx * pc * pid);
-			float ia = pid * pd;
-			float id = pid * pa;
-			float ib = pid * pb;
-			float ic = pid * pc;
-			float ra = ia * a - ib * c;
-			float rb = ia * b - ib * d;
-			float rc = id * c - ic * a;
-			float rd = id * d - ic * b;
-			ashearX = 0;
-			ascaleX = (float)Math.Sqrt(ra * ra + rc * rc);
-			if (ascaleX > 0.0001f) {
-				float det = ra * rd - rb * rc;
-				ascaleY = det / ascaleX;
-				ashearY = MathUtils.Atan2(ra * rb + rc * rd, det) * MathUtils.RadDeg;
-				arotation = MathUtils.Atan2(rc, ra) * MathUtils.RadDeg;
-			} else {
-				ascaleX = 0;
-				ascaleY = (float)Math.Sqrt(rb * rb + rd * rd);
-				ashearY = 0;
-				arotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.RadDeg;
-			}
-		}
-
-		public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) {			
-			float a = this.a, b = this.b, c = this.c, d = this.d;
-			float invDet = 1 / (a * d - b * c);
-			float x = worldX - this.worldX, y = worldY - this.worldY;
-			localX = (x * d * invDet - y * b * invDet);
-			localY = (y * a * invDet - x * c * invDet);
-		}
-
-		public void LocalToWorld (float localX, float localY, out float worldX, out float worldY) {
-			worldX = localX * a + localY * b + this.worldX;
-			worldY = localX * c + localY * d + this.worldY;
-		}
-
-		public float WorldToLocalRotationX {
-			get {
-				Bone parent = this.parent;
-				if (parent == null) return arotation;
-				float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c;
-				return MathUtils.Atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.RadDeg;
-			}
-		}
-
-		public float WorldToLocalRotationY {
-			get {
-				Bone parent = this.parent;
-				if (parent == null) return arotation;
-				float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d;
-				return MathUtils.Atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.RadDeg;
-			}
-		}
-
-		public float WorldToLocalRotation (float worldRotation) {
-			float sin = MathUtils.SinDeg(worldRotation), cos = MathUtils.CosDeg(worldRotation);
-			return MathUtils.Atan2(a * sin - c * cos, d * cos - b * sin) * MathUtils.RadDeg + rotation - shearX;
-		}
-
-		public float LocalToWorldRotation (float localRotation) {
-			localRotation -= rotation - shearX;
-			float sin = MathUtils.SinDeg(localRotation), cos = MathUtils.CosDeg(localRotation);
-			return MathUtils.Atan2(cos * c + sin * d, cos * a + sin * b) * MathUtils.RadDeg;
-		}
-
-		/// <summary>
-		/// Rotates the world transform the specified amount and sets isAppliedValid to false.
-		/// </summary>
-		/// <param name="degrees">Degrees.</param>
-		public void RotateWorld (float degrees) {
-			float a = this.a, b = this.b, c = this.c, d = this.d;
-			float cos = MathUtils.CosDeg(degrees), sin = MathUtils.SinDeg(degrees);
-			this.a = cos * a - sin * c;
-			this.b = cos * b - sin * d;
-			this.c = sin * a + cos * c;
-			this.d = sin * b + cos * d;
-			appliedValid = false;
-		}
-
-		override public string ToString () {
-			return data.name;
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+	/// <summary>
+	/// Stores a bone's current pose.
+	/// <para>
+	/// A bone has a local transform which is used to compute its world transform. A bone also has an applied transform, which is a
+	/// local transform that can be applied to compute the world transform. The local transform and applied transform may differ if a
+	/// constraint or application code modifies the world transform after it was computed from the local transform.
+	/// </para>
+	/// </summary>
+	public class Bone : IUpdatable {
+		static public bool yDown;
+
+		internal BoneData data;
+		internal Skeleton skeleton;
+		internal Bone parent;
+		internal ExposedList<Bone> children = new ExposedList<Bone>();
+		internal float x, y, rotation, scaleX, scaleY, shearX, shearY;
+		internal float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY;
+		internal bool appliedValid;
+
+		internal float a, b, worldX;
+		internal float c, d, worldY;
+
+		internal bool sorted;
+
+		public BoneData Data { get { return data; } }
+		public Skeleton Skeleton { get { return skeleton; } }
+		public Bone Parent { get { return parent; } }
+		public ExposedList<Bone> Children { get { return children; } }
+		/// <summary>The local X translation.</summary>
+		public float X { get { return x; } set { x = value; } }
+		/// <summary>The local Y translation.</summary>
+		public float Y { get { return y; } set { y = value; } }
+		/// <summary>The local rotation.</summary>
+		public float Rotation { get { return rotation; } set { rotation = value; } }
+
+		/// <summary>The local scaleX.</summary>
+		public float ScaleX { get { return scaleX; } set { scaleX = value; } }
+
+		/// <summary>The local scaleY.</summary>
+		public float ScaleY { get { return scaleY; } set { scaleY = value; } }
+
+		/// <summary>The local shearX.</summary>
+		public float ShearX { get { return shearX; } set { shearX = value; } }
+
+		/// <summary>The local shearY.</summary>
+		public float ShearY { get { return shearY; } set { shearY = value; } }
+
+		/// <summary>The rotation, as calculated by any constraints.</summary>
+		public float AppliedRotation { get { return arotation; } set { arotation = value; } }
+
+		/// <summary>The applied local x translation.</summary>
+		public float AX { get { return ax; } set { ax = value; } }
+
+		/// <summary>The applied local y translation.</summary>
+		public float AY { get { return ay; } set { ay = value; } }
+
+		/// <summary>The applied local scaleX.</summary>
+		public float AScaleX { get { return ascaleX; } set { ascaleX = value; } }
+
+		/// <summary>The applied local scaleY.</summary>
+		public float AScaleY { get { return ascaleY; } set { ascaleY = value; } }
+
+		/// <summary>The applied local shearX.</summary>
+		public float AShearX { get { return ashearX; } set { ashearX = value; } }
+
+		/// <summary>The applied local shearY.</summary>
+		public float AShearY { get { return ashearY; } set { ashearY = value; } }
+
+		public float A { get { return a; } }
+		public float B { get { return b; } }
+		public float C { get { return c; } }
+		public float D { get { return d; } }
+
+		public float WorldX { get { return worldX; } }
+		public float WorldY { get { return worldY; } }
+		public float WorldRotationX { get { return MathUtils.Atan2(c, a) * MathUtils.RadDeg; } }
+		public float WorldRotationY { get { return MathUtils.Atan2(d, b) * MathUtils.RadDeg; } }
+
+		/// <summary>Returns the magnitide (always positive) of the world scale X.</summary>
+		public float WorldScaleX { get { return (float)Math.Sqrt(a * a + c * c); } }
+		/// <summary>Returns the magnitide (always positive) of the world scale Y.</summary>
+		public float WorldScaleY { get { return (float)Math.Sqrt(b * b + d * d); } }
+
+		/// <param name="parent">May be null.</param>
+		public Bone (BoneData data, Skeleton skeleton, Bone parent) {
+			if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
+			if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
+			this.data = data;
+			this.skeleton = skeleton;
+			this.parent = parent;
+			SetToSetupPose();
+		}
+
+		/// <summary>Same as <see cref="UpdateWorldTransform"/>. This method exists for Bone to implement <see cref="Spine.IUpdatable"/>.</summary>
+		public void Update () {
+			UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY);
+		}
+
+		/// <summary>Computes the world transform using the parent bone and this bone's local transform.</summary>
+		public void UpdateWorldTransform () {
+			UpdateWorldTransform(x, y, rotation, scaleX, scaleY, shearX, shearY);
+		}
+
+		/// <summary>Computes the world transform using the parent bone and the specified local transform.</summary>
+		public void UpdateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) {
+			ax = x;
+			ay = y;
+			arotation = rotation;
+			ascaleX = scaleX;
+			ascaleY = scaleY;
+			ashearX = shearX;
+			ashearY = shearY;
+			appliedValid = true;
+			Skeleton skeleton = this.skeleton;
+
+			Bone parent = this.parent;
+			if (parent == null) { // Root bone.
+				float rotationY = rotation + 90 + shearY, sx = skeleton.scaleX, sy = skeleton.scaleY;
+				a = MathUtils.CosDeg(rotation + shearX) * scaleX * sx;
+				b = MathUtils.CosDeg(rotationY) * scaleY * sy;
+				c = MathUtils.SinDeg(rotation + shearX) * scaleX * sx;
+				d = MathUtils.SinDeg(rotationY) * scaleY * sy;
+				worldX = x * sx + skeleton.x;
+				worldY = y * sy + skeleton.y;
+				return;
+			}
+
+			float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
+			worldX = pa * x + pb * y + parent.worldX;
+			worldY = pc * x + pd * y + parent.worldY;
+
+			switch (data.transformMode) {
+			case TransformMode.Normal: {
+					float rotationY = rotation + 90 + shearY;
+					float la = MathUtils.CosDeg(rotation + shearX) * scaleX;
+					float lb = MathUtils.CosDeg(rotationY) * scaleY;
+					float lc = MathUtils.SinDeg(rotation + shearX) * scaleX;
+					float ld = MathUtils.SinDeg(rotationY) * scaleY;
+					a = pa * la + pb * lc;
+					b = pa * lb + pb * ld;
+					c = pc * la + pd * lc;
+					d = pc * lb + pd * ld;
+					return;
+				}
+			case TransformMode.OnlyTranslation: {
+					float rotationY = rotation + 90 + shearY;
+					a = MathUtils.CosDeg(rotation + shearX) * scaleX;
+					b = MathUtils.CosDeg(rotationY) * scaleY;
+					c = MathUtils.SinDeg(rotation + shearX) * scaleX;
+					d = MathUtils.SinDeg(rotationY) * scaleY;
+					break;
+				}
+			case TransformMode.NoRotationOrReflection: {
+					float s = pa * pa + pc * pc, prx;
+					if (s > 0.0001f) {
+						s = Math.Abs(pa * pd - pb * pc) / s;
+						pb = pc * s;
+						pd = pa * s;
+						prx = MathUtils.Atan2(pc, pa) * MathUtils.RadDeg;
+					} else {
+						pa = 0;
+						pc = 0;
+						prx = 90 - MathUtils.Atan2(pd, pb) * MathUtils.RadDeg;
+					}
+					float rx = rotation + shearX - prx;
+					float ry = rotation + shearY - prx + 90;
+					float la = MathUtils.CosDeg(rx) * scaleX;
+					float lb = MathUtils.CosDeg(ry) * scaleY;
+					float lc = MathUtils.SinDeg(rx) * scaleX;
+					float ld = MathUtils.SinDeg(ry) * scaleY;
+					a = pa * la - pb * lc;
+					b = pa * lb - pb * ld;
+					c = pc * la + pd * lc;
+					d = pc * lb + pd * ld;
+					break;
+				}
+			case TransformMode.NoScale:
+			case TransformMode.NoScaleOrReflection: {
+					float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation);
+					float za = (pa * cos + pb * sin) / skeleton.scaleX;
+					float zc = (pc * cos + pd * sin) / skeleton.scaleY;
+					float s = (float)Math.Sqrt(za * za + zc * zc);
+					if (s > 0.00001f) s = 1 / s;
+					za *= s;
+					zc *= s;
+					s = (float)Math.Sqrt(za * za + zc * zc);
+					if (data.transformMode == TransformMode.NoScale
+						&& (pa * pd - pb * pc < 0) != (skeleton.scaleX < 0 != skeleton.scaleY < 0)) s = -s;
+
+					float r = MathUtils.PI / 2 + MathUtils.Atan2(zc, za);
+					float zb = MathUtils.Cos(r) * s;
+					float zd = MathUtils.Sin(r) * s;
+					float la = MathUtils.CosDeg(shearX) * scaleX;
+					float lb = MathUtils.CosDeg(90 + shearY) * scaleY;
+					float lc = MathUtils.SinDeg(shearX) * scaleX;
+					float ld = MathUtils.SinDeg(90 + shearY) * scaleY;
+					a = za * la + zb * lc;
+					b = za * lb + zb * ld;
+					c = zc * la + zd * lc;
+					d = zc * lb + zd * ld;
+					break;
+				}
+			}
+
+			a *= skeleton.scaleX;
+			b *= skeleton.scaleX;
+			c *= skeleton.scaleY;
+			d *= skeleton.scaleY;
+		}
+
+		public void SetToSetupPose () {
+			BoneData data = this.data;
+			x = data.x;
+			y = data.y;
+			rotation = data.rotation;
+			scaleX = data.scaleX;
+			scaleY = data.scaleY;
+			shearX = data.shearX;
+			shearY = data.shearY;
+		}
+
+		/// <summary>
+		/// Computes the individual applied transform values from the world transform. This can be useful to perform processing using
+		/// the applied transform after the world transform has been modified directly (eg, by a constraint)..
+		/// 
+		/// Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation.
+		/// </summary>
+		internal void UpdateAppliedTransform () {
+			appliedValid = true;
+			Bone parent = this.parent;
+			if (parent == null) {
+				ax = worldX;
+				ay = worldY;
+				arotation = MathUtils.Atan2(c, a) * MathUtils.RadDeg;
+				ascaleX = (float)Math.Sqrt(a * a + c * c);
+				ascaleY = (float)Math.Sqrt(b * b + d * d);
+				ashearX = 0;
+				ashearY = MathUtils.Atan2(a * b + c * d, a * d - b * c) * MathUtils.RadDeg;
+				return;
+			}
+			float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
+			float pid = 1 / (pa * pd - pb * pc);
+			float dx = worldX - parent.worldX, dy = worldY - parent.worldY;
+			ax = (dx * pd * pid - dy * pb * pid);
+			ay = (dy * pa * pid - dx * pc * pid);
+			float ia = pid * pd;
+			float id = pid * pa;
+			float ib = pid * pb;
+			float ic = pid * pc;
+			float ra = ia * a - ib * c;
+			float rb = ia * b - ib * d;
+			float rc = id * c - ic * a;
+			float rd = id * d - ic * b;
+			ashearX = 0;
+			ascaleX = (float)Math.Sqrt(ra * ra + rc * rc);
+			if (ascaleX > 0.0001f) {
+				float det = ra * rd - rb * rc;
+				ascaleY = det / ascaleX;
+				ashearY = MathUtils.Atan2(ra * rb + rc * rd, det) * MathUtils.RadDeg;
+				arotation = MathUtils.Atan2(rc, ra) * MathUtils.RadDeg;
+			} else {
+				ascaleX = 0;
+				ascaleY = (float)Math.Sqrt(rb * rb + rd * rd);
+				ashearY = 0;
+				arotation = 90 - MathUtils.Atan2(rd, rb) * MathUtils.RadDeg;
+			}
+		}
+
+		public void WorldToLocal (float worldX, float worldY, out float localX, out float localY) {			
+			float a = this.a, b = this.b, c = this.c, d = this.d;
+			float invDet = 1 / (a * d - b * c);
+			float x = worldX - this.worldX, y = worldY - this.worldY;
+			localX = (x * d * invDet - y * b * invDet);
+			localY = (y * a * invDet - x * c * invDet);
+		}
+
+		public void LocalToWorld (float localX, float localY, out float worldX, out float worldY) {
+			worldX = localX * a + localY * b + this.worldX;
+			worldY = localX * c + localY * d + this.worldY;
+		}
+
+		public float WorldToLocalRotationX {
+			get {
+				Bone parent = this.parent;
+				if (parent == null) return arotation;
+				float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, a = this.a, c = this.c;
+				return MathUtils.Atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.RadDeg;
+			}
+		}
+
+		public float WorldToLocalRotationY {
+			get {
+				Bone parent = this.parent;
+				if (parent == null) return arotation;
+				float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d, b = this.b, d = this.d;
+				return MathUtils.Atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.RadDeg;
+			}
+		}
+
+		public float WorldToLocalRotation (float worldRotation) {
+			float sin = MathUtils.SinDeg(worldRotation), cos = MathUtils.CosDeg(worldRotation);
+			return MathUtils.Atan2(a * sin - c * cos, d * cos - b * sin) * MathUtils.RadDeg + rotation - shearX;
+		}
+
+		public float LocalToWorldRotation (float localRotation) {
+			localRotation -= rotation - shearX;
+			float sin = MathUtils.SinDeg(localRotation), cos = MathUtils.CosDeg(localRotation);
+			return MathUtils.Atan2(cos * c + sin * d, cos * a + sin * b) * MathUtils.RadDeg;
+		}
+
+		/// <summary>
+		/// Rotates the world transform the specified amount and sets isAppliedValid to false.
+		/// </summary>
+		/// <param name="degrees">Degrees.</param>
+		public void RotateWorld (float degrees) {
+			float a = this.a, b = this.b, c = this.c, d = this.d;
+			float cos = MathUtils.CosDeg(degrees), sin = MathUtils.SinDeg(degrees);
+			this.a = cos * a - sin * c;
+			this.b = cos * b - sin * d;
+			this.c = sin * a + cos * c;
+			this.d = sin * b + cos * d;
+			appliedValid = false;
+		}
+
+		override public string ToString () {
+			return data.name;
+		}
+	}
+}

+ 100 - 100
spine-csharp/src/BoneData.cs

@@ -1,100 +1,100 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-using System;
-
-namespace Spine {
-	public class BoneData {
-		internal int index;
-		internal string name;
-		internal BoneData parent;
-		internal float length;
-		internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY;
-		internal TransformMode transformMode = TransformMode.Normal;
-
-		/// <summary>The index of the bone in Skeleton.Bones</summary>
-		public int Index { get { return index; } }
-
-		/// <summary>The name of the bone, which is unique within the skeleton.</summary>
-		public string Name { get { return name; } }
-
-		/// <summary>May be null.</summary>
-		public BoneData Parent { get { return parent; } }
-
-		public float Length { get { return length; } set { length = value; } }
-
-		/// <summary>Local X translation.</summary>
-		public float X { get { return x; } set { x = value; } }
-
-		/// <summary>Local Y translation.</summary>
-		public float Y { get { return y; } set { y = value; } }
-
-		/// <summary>Local rotation.</summary>
-		public float Rotation { get { return rotation; } set { rotation = value; } }
-
-		/// <summary>Local scaleX.</summary>
-		public float ScaleX { get { return scaleX; } set { scaleX = value; } }
-
-		/// <summary>Local scaleY.</summary>
-		public float ScaleY { get { return scaleY; } set { scaleY = value; } }
-
-		/// <summary>Local shearX.</summary>
-		public float ShearX { get { return shearX; } set { shearX = value; } }
-
-		/// <summary>Local shearY.</summary>
-		public float ShearY { get { return shearY; } set { shearY = value; } }
-
-		/// <summary>The transform mode for how parent world transforms affect this bone.</summary>
-		public TransformMode TransformMode { get { return transformMode; } set { transformMode = value; } }
-
-		/// <param name="parent">May be null.</param>
-		public BoneData (int index, string name, BoneData parent) {
-			if (index < 0) throw new ArgumentException("index must be >= 0", "index");
-			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
-			this.index = index;
-			this.name = name;
-			this.parent = parent;
-		}
-
-		override public string ToString () {
-			return name;
-		}
-	}
-
-	[Flags]
-	public enum TransformMode {
-		//0000 0 Flip Scale Rotation
-		Normal = 0, // 0000
-		OnlyTranslation = 7, // 0111
-		NoRotationOrReflection = 1, // 0001
-		NoScale = 2, // 0010
-		NoScaleOrReflection = 6, // 0110
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+	public class BoneData {
+		internal int index;
+		internal string name;
+		internal BoneData parent;
+		internal float length;
+		internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY;
+		internal TransformMode transformMode = TransformMode.Normal;
+
+		/// <summary>The index of the bone in Skeleton.Bones</summary>
+		public int Index { get { return index; } }
+
+		/// <summary>The name of the bone, which is unique within the skeleton.</summary>
+		public string Name { get { return name; } }
+
+		/// <summary>May be null.</summary>
+		public BoneData Parent { get { return parent; } }
+
+		public float Length { get { return length; } set { length = value; } }
+
+		/// <summary>Local X translation.</summary>
+		public float X { get { return x; } set { x = value; } }
+
+		/// <summary>Local Y translation.</summary>
+		public float Y { get { return y; } set { y = value; } }
+
+		/// <summary>Local rotation.</summary>
+		public float Rotation { get { return rotation; } set { rotation = value; } }
+
+		/// <summary>Local scaleX.</summary>
+		public float ScaleX { get { return scaleX; } set { scaleX = value; } }
+
+		/// <summary>Local scaleY.</summary>
+		public float ScaleY { get { return scaleY; } set { scaleY = value; } }
+
+		/// <summary>Local shearX.</summary>
+		public float ShearX { get { return shearX; } set { shearX = value; } }
+
+		/// <summary>Local shearY.</summary>
+		public float ShearY { get { return shearY; } set { shearY = value; } }
+
+		/// <summary>The transform mode for how parent world transforms affect this bone.</summary>
+		public TransformMode TransformMode { get { return transformMode; } set { transformMode = value; } }
+
+		/// <param name="parent">May be null.</param>
+		public BoneData (int index, string name, BoneData parent) {
+			if (index < 0) throw new ArgumentException("index must be >= 0", "index");
+			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
+			this.index = index;
+			this.name = name;
+			this.parent = parent;
+		}
+
+		override public string ToString () {
+			return name;
+		}
+	}
+
+	[Flags]
+	public enum TransformMode {
+		//0000 0 Flip Scale Rotation
+		Normal = 0, // 0000
+		OnlyTranslation = 7, // 0111
+		NoRotationOrReflection = 1, // 0001
+		NoScale = 2, // 0010
+		NoScaleOrReflection = 6, // 0110
+	}
+}

+ 65 - 65
spine-csharp/src/Event.cs

@@ -1,65 +1,65 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-using System;
-
-namespace Spine {
-	/// <summary>Stores the current pose values for an Event.</summary>
-	public class Event {
-		internal readonly EventData data;
-		internal readonly float time;
-		internal int intValue;
-		internal float floatValue;
-		internal string stringValue;
-		internal float volume;
-		internal  float balance;
-
-		public EventData Data { get { return data; } }
-		/// <summary>The animation time this event was keyed.</summary>
-		public float Time { get { return time; } }
-
-		public int Int { get { return intValue; } set { intValue = value; } }
-		public float Float { get { return floatValue; } set { floatValue = value; } }
-		public string String { get { return stringValue; } set { stringValue = value; } }
-
-		public float Volume { get { return volume; } set { volume = value; } }
-		public float Balance { get { return balance; } set { balance = value; } }
-
-		public Event (float time, EventData data) {
-			if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
-			this.time = time;
-			this.data = data;
-		}
-
-		override public string ToString () {
-			return this.data.Name;
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+	/// <summary>Stores the current pose values for an Event.</summary>
+	public class Event {
+		internal readonly EventData data;
+		internal readonly float time;
+		internal int intValue;
+		internal float floatValue;
+		internal string stringValue;
+		internal float volume;
+		internal  float balance;
+
+		public EventData Data { get { return data; } }
+		/// <summary>The animation time this event was keyed.</summary>
+		public float Time { get { return time; } }
+
+		public int Int { get { return intValue; } set { intValue = value; } }
+		public float Float { get { return floatValue; } set { floatValue = value; } }
+		public string String { get { return stringValue; } set { stringValue = value; } }
+
+		public float Volume { get { return volume; } set { volume = value; } }
+		public float Balance { get { return balance; } set { balance = value; } }
+
+		public Event (float time, EventData data) {
+			if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
+			this.time = time;
+			this.data = data;
+		}
+
+		override public string ToString () {
+			return this.data.Name;
+		}
+	}
+}

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

@@ -1,57 +1,57 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-using System;
-
-namespace Spine {
-	/// <summary>Stores the setup pose values for an Event.</summary>
-	public class EventData {
-		internal string name;
-
-		/// <summary>The name of the event, which is unique within the skeleton.</summary>
-		public string Name { get { return name; } }
-		public int Int { get; set; }
-		public float Float { get; set; }
-		public string @String { get; set; }
-
-		public string AudioPath { get; set; }
-		public float Volume { get; set; }
-		public float Balance { get; set; }
-
-		public EventData (string name) {
-			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
-			this.name = name;
-		}
-
-		override public string ToString () {
-			return Name;
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+	/// <summary>Stores the setup pose values for an Event.</summary>
+	public class EventData {
+		internal string name;
+
+		/// <summary>The name of the event, which is unique within the skeleton.</summary>
+		public string Name { get { return name; } }
+		public int Int { get; set; }
+		public float Float { get; set; }
+		public string @String { get; set; }
+
+		public string AudioPath { get; set; }
+		public float Volume { get; set; }
+		public float Balance { get; set; }
+
+		public EventData (string name) {
+			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
+			this.name = name;
+		}
+
+		override public string ToString () {
+			return Name;
+		}
+	}
+}

+ 35 - 35
spine-csharp/src/IUpdatable.cs

@@ -1,35 +1,35 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-namespace Spine {
-	public interface IUpdatable {
-		void Update ();
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+namespace Spine {
+	public interface IUpdatable {
+		void Update ();
+	}
+}

+ 276 - 276
spine-csharp/src/IkConstraint.cs

@@ -1,276 +1,276 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-using System;
-
-namespace Spine {
-	public class IkConstraint : IConstraint {
-		internal IkConstraintData data;
-		internal ExposedList<Bone> bones = new ExposedList<Bone>();
-		internal Bone target;
-		internal int bendDirection;
-		internal bool compress, stretch;
-		internal float mix;
-
-		public IkConstraintData Data { get { return data; } }
-		public int Order { get { return data.order; } }
-
-		/// <summary>The bones that will be modified by this IK constraint.</summary>
-		public ExposedList<Bone> Bones {
-			get { return bones; }
-		}
-
-		/// <summary>The bone that is the IK target.</summary>
-		public Bone Target {
-			get { return target; }
-			set { target = value; }
-		}
-
-		/// <summary>Controls the bend direction of the IK bones, either 1 or -1.</summary>
-		public int BendDirection {
-			get { return bendDirection; }
-			set { bendDirection = value; }
-		}
-
-		/// <summary>
-		/// When true and only a single bone is being constrained, 
-		/// if the target is too close, the bone is scaled to reach it.</summary>
-		public bool Compress {
-			get { return compress; }
-			set { compress = value; }
-		}
-
-		/// <summary>
-		/// When true, if the target is out of range, the parent bone is scaled on the X axis to reach it.
-		/// If the parent bone has nonuniform scale, stretching is not applied.</summary>
-		public bool Stretch {
-			get { return stretch; }
-			set { stretch = value; }
-		}
-
-		/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained rotations.</summary>
-		public float Mix {
-			get { return mix; }
-			set { mix = value; }
-		}
-
-		public IkConstraint (IkConstraintData 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;
-			mix = data.mix;
-			bendDirection = data.bendDirection;
-			compress = data.compress;
-			stretch = data.stretch;
-
-			bones = new ExposedList<Bone>(data.bones.Count);
-			foreach (BoneData boneData in data.bones)
-				bones.Add(skeleton.FindBone(boneData.name));
-			target = skeleton.FindBone(data.target.name);
-		}
-
-		/// <summary>Applies the constraint to the constrained bones.</summary>
-		public void Apply () {
-			Update();
-		}
-
-		public void Update () {
-			Bone target = this.target;
-			ExposedList<Bone> bones = this.bones;
-			switch (bones.Count) {
-			case 1:
-				Apply(bones.Items[0], target.worldX, target.worldY, compress, stretch, data.uniform, mix);
-				break;
-			case 2:
-				Apply(bones.Items[0], bones.Items[1], target.worldX, target.worldY, bendDirection, stretch, mix);
-				break;
-			}
-		}
-
-		override public string ToString () {
-			return data.name;
-		}
-
-		/// <summary>Applies 1 bone IK. The target is specified in the world coordinate system.</summary>
-		static public void Apply (Bone bone, float targetX, float targetY, bool compress, bool stretch, bool uniform, float alpha) {
-			if (!bone.appliedValid) bone.UpdateAppliedTransform();
-			Bone p = bone.parent;
-			float id = 1 / (p.a * p.d - p.b * p.c);
-			float x = targetX - p.worldX, y = targetY - p.worldY;
-			float tx = (x * p.d - y * p.b) * id - bone.ax, ty = (y * p.a - x * p.c) * id - bone.ay;
-			float rotationIK = (float)Math.Atan2(ty, tx) * MathUtils.RadDeg - bone.ashearX - bone.arotation;
-			if (bone.ascaleX < 0) rotationIK += 180;
-			if (rotationIK > 180)
-				rotationIK -= 360;
-			else if (rotationIK < -180) //
-				rotationIK += 360;
-			float sx = bone.ascaleX, sy = bone.ascaleY;
-			if (compress || stretch) {
-				float b = bone.data.length * sx, dd = (float)Math.Sqrt(tx * tx + ty * ty);
-				if ((compress && dd < b) || (stretch && dd > b) && b > 0.0001f) {
-					float s = (dd / b - 1) * alpha + 1;
-					sx *= s;
-					if (uniform) sy *= s;
-				}
-			}
-			bone.UpdateWorldTransform(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, sx, sy, bone.ashearX,
-				bone.ashearY);
-		}
-
-		/// <summary>Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as
-		/// possible. The target is specified in the world coordinate system.</summary>
-		/// <param name="child">A direct descendant of the parent bone.</param>
-		static public void Apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, bool stretch, float alpha) {
-			if (alpha == 0) {
-				child.UpdateWorldTransform ();
-				return;
-			}
-			if (!parent.appliedValid) parent.UpdateAppliedTransform();
-			if (!child.appliedValid) child.UpdateAppliedTransform();
-			float px = parent.ax, py = parent.ay, psx = parent.ascaleX, sx = psx, psy = parent.ascaleY, csx = child.ascaleX;
-			int os1, os2, s2;
-			if (psx < 0) {
-				psx = -psx;
-				os1 = 180;
-				s2 = -1;
-			} else {
-				os1 = 0;
-				s2 = 1;
-			}
-			if (psy < 0) {
-				psy = -psy;
-				s2 = -s2;
-			}
-			if (csx < 0) {
-				csx = -csx;
-				os2 = 180;
-			} else
-				os2 = 0;
-			float cx = child.ax, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d;
-			bool u = Math.Abs(psx - psy) <= 0.0001f;
-			if (!u) {
-				cy = 0;
-				cwx = a * cx + parent.worldX;
-				cwy = c * cx + parent.worldY;
-			} else {
-				cy = child.ay;
-				cwx = a * cx + b * cy + parent.worldX;
-				cwy = c * cx + d * cy + parent.worldY;
-			}
-			Bone pp = parent.parent;
-			a = pp.a;
-			b = pp.b;
-			c = pp.c;
-			d = pp.d;
-			float id = 1 / (a * d - b * c), x = targetX - pp.worldX, y = targetY - pp.worldY;
-			float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py, dd = tx * tx + ty * ty;
-			x = cwx - pp.worldX;
-			y = cwy - pp.worldY;
-			float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py;
-			float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2;
-			if (u) {
-				l2 *= psx;
-				float cos = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2);
-				if (cos < -1)
-					cos = -1;
-				else if (cos > 1) {
-					cos = 1;
-					if (stretch && l1 + l2 > 0.0001f) sx *= ((float)Math.Sqrt(dd) / (l1 + l2) - 1) * alpha + 1;
-				}
-				a2 = (float)Math.Acos(cos) * bendDir;
-				a = l1 + l2 * cos;
-				b = l2 * (float)Math.Sin(a2);
-				a1 = (float)Math.Atan2(ty * a - tx * b, tx * a + ty * b);
-			} else {
-				a = psx * l2;
-				b = psy * l2;
-				float aa = a * a, bb = b * b, ta = (float)Math.Atan2(ty, tx);
-				c = bb * l1 * l1 + aa * dd - aa * bb;
-				float c1 = -2 * bb * l1, c2 = bb - aa;
-				d = c1 * c1 - 4 * c2 * c;
-				if (d >= 0) {
-					float q = (float)Math.Sqrt(d);
-					if (c1 < 0) q = -q;
-					q = -(c1 + q) / 2;
-					float r0 = q / c2, r1 = c / q;
-					float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1;
-					if (r * r <= dd) {
-						y = (float)Math.Sqrt(dd - r * r) * bendDir;
-						a1 = ta - (float)Math.Atan2(y, r);
-						a2 = (float)Math.Atan2(y / psy, (r - l1) / psx);
-						goto break_outer; // break outer;
-					}
-				}
-				float minAngle = MathUtils.PI, minX = l1 - a, minDist = minX * minX, minY = 0;
-				float maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0;
-				c = -a * l1 / (aa - bb);
-				if (c >= -1 && c <= 1) {
-					c = (float)Math.Acos(c);
-					x = a * (float)Math.Cos(c) + l1;
-					y = b * (float)Math.Sin(c);
-					d = x * x + y * y;
-					if (d < minDist) {
-						minAngle = c;
-						minDist = d;
-						minX = x;
-						minY = y;
-					}
-					if (d > maxDist) {
-						maxAngle = c;
-						maxDist = d;
-						maxX = x;
-						maxY = y;
-					}
-				}
-				if (dd <= (minDist + maxDist) / 2) {
-					a1 = ta - (float)Math.Atan2(minY * bendDir, minX);
-					a2 = minAngle * bendDir;
-				} else {
-					a1 = ta - (float)Math.Atan2(maxY * bendDir, maxX);
-					a2 = maxAngle * bendDir;
-				}
-			}
-			break_outer:
-			float os = (float)Math.Atan2(cy, cx) * s2;
-			float rotation = parent.arotation;
-			a1 = (a1 - os) * MathUtils.RadDeg + os1 - rotation;
-			if (a1 > 180)
-				a1 -= 360;
-			else if (a1 < -180) a1 += 360;
-			parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, sx, parent.ascaleY, 0, 0);
-			rotation = child.arotation;
-			a2 = ((a2 + os) * MathUtils.RadDeg - child.ashearX) * s2 + os2 - rotation;
-			if (a2 > 180)
-				a2 -= 360;
-			else if (a2 < -180) a2 += 360;
-			child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY);
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+	public class IkConstraint : IConstraint {
+		internal IkConstraintData data;
+		internal ExposedList<Bone> bones = new ExposedList<Bone>();
+		internal Bone target;
+		internal int bendDirection;
+		internal bool compress, stretch;
+		internal float mix;
+
+		public IkConstraintData Data { get { return data; } }
+		public int Order { get { return data.order; } }
+
+		/// <summary>The bones that will be modified by this IK constraint.</summary>
+		public ExposedList<Bone> Bones {
+			get { return bones; }
+		}
+
+		/// <summary>The bone that is the IK target.</summary>
+		public Bone Target {
+			get { return target; }
+			set { target = value; }
+		}
+
+		/// <summary>Controls the bend direction of the IK bones, either 1 or -1.</summary>
+		public int BendDirection {
+			get { return bendDirection; }
+			set { bendDirection = value; }
+		}
+
+		/// <summary>
+		/// When true and only a single bone is being constrained, 
+		/// if the target is too close, the bone is scaled to reach it.</summary>
+		public bool Compress {
+			get { return compress; }
+			set { compress = value; }
+		}
+
+		/// <summary>
+		/// When true, if the target is out of range, the parent bone is scaled on the X axis to reach it.
+		/// If the parent bone has nonuniform scale, stretching is not applied.</summary>
+		public bool Stretch {
+			get { return stretch; }
+			set { stretch = value; }
+		}
+
+		/// <summary>A percentage (0-1) that controls the mix between the constrained and unconstrained rotations.</summary>
+		public float Mix {
+			get { return mix; }
+			set { mix = value; }
+		}
+
+		public IkConstraint (IkConstraintData 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;
+			mix = data.mix;
+			bendDirection = data.bendDirection;
+			compress = data.compress;
+			stretch = data.stretch;
+
+			bones = new ExposedList<Bone>(data.bones.Count);
+			foreach (BoneData boneData in data.bones)
+				bones.Add(skeleton.FindBone(boneData.name));
+			target = skeleton.FindBone(data.target.name);
+		}
+
+		/// <summary>Applies the constraint to the constrained bones.</summary>
+		public void Apply () {
+			Update();
+		}
+
+		public void Update () {
+			Bone target = this.target;
+			ExposedList<Bone> bones = this.bones;
+			switch (bones.Count) {
+			case 1:
+				Apply(bones.Items[0], target.worldX, target.worldY, compress, stretch, data.uniform, mix);
+				break;
+			case 2:
+				Apply(bones.Items[0], bones.Items[1], target.worldX, target.worldY, bendDirection, stretch, mix);
+				break;
+			}
+		}
+
+		override public string ToString () {
+			return data.name;
+		}
+
+		/// <summary>Applies 1 bone IK. The target is specified in the world coordinate system.</summary>
+		static public void Apply (Bone bone, float targetX, float targetY, bool compress, bool stretch, bool uniform, float alpha) {
+			if (!bone.appliedValid) bone.UpdateAppliedTransform();
+			Bone p = bone.parent;
+			float id = 1 / (p.a * p.d - p.b * p.c);
+			float x = targetX - p.worldX, y = targetY - p.worldY;
+			float tx = (x * p.d - y * p.b) * id - bone.ax, ty = (y * p.a - x * p.c) * id - bone.ay;
+			float rotationIK = (float)Math.Atan2(ty, tx) * MathUtils.RadDeg - bone.ashearX - bone.arotation;
+			if (bone.ascaleX < 0) rotationIK += 180;
+			if (rotationIK > 180)
+				rotationIK -= 360;
+			else if (rotationIK < -180) //
+				rotationIK += 360;
+			float sx = bone.ascaleX, sy = bone.ascaleY;
+			if (compress || stretch) {
+				float b = bone.data.length * sx, dd = (float)Math.Sqrt(tx * tx + ty * ty);
+				if ((compress && dd < b) || (stretch && dd > b) && b > 0.0001f) {
+					float s = (dd / b - 1) * alpha + 1;
+					sx *= s;
+					if (uniform) sy *= s;
+				}
+			}
+			bone.UpdateWorldTransform(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, sx, sy, bone.ashearX,
+				bone.ashearY);
+		}
+
+		/// <summary>Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as
+		/// possible. The target is specified in the world coordinate system.</summary>
+		/// <param name="child">A direct descendant of the parent bone.</param>
+		static public void Apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, bool stretch, float alpha) {
+			if (alpha == 0) {
+				child.UpdateWorldTransform ();
+				return;
+			}
+			if (!parent.appliedValid) parent.UpdateAppliedTransform();
+			if (!child.appliedValid) child.UpdateAppliedTransform();
+			float px = parent.ax, py = parent.ay, psx = parent.ascaleX, sx = psx, psy = parent.ascaleY, csx = child.ascaleX;
+			int os1, os2, s2;
+			if (psx < 0) {
+				psx = -psx;
+				os1 = 180;
+				s2 = -1;
+			} else {
+				os1 = 0;
+				s2 = 1;
+			}
+			if (psy < 0) {
+				psy = -psy;
+				s2 = -s2;
+			}
+			if (csx < 0) {
+				csx = -csx;
+				os2 = 180;
+			} else
+				os2 = 0;
+			float cx = child.ax, cy, cwx, cwy, a = parent.a, b = parent.b, c = parent.c, d = parent.d;
+			bool u = Math.Abs(psx - psy) <= 0.0001f;
+			if (!u) {
+				cy = 0;
+				cwx = a * cx + parent.worldX;
+				cwy = c * cx + parent.worldY;
+			} else {
+				cy = child.ay;
+				cwx = a * cx + b * cy + parent.worldX;
+				cwy = c * cx + d * cy + parent.worldY;
+			}
+			Bone pp = parent.parent;
+			a = pp.a;
+			b = pp.b;
+			c = pp.c;
+			d = pp.d;
+			float id = 1 / (a * d - b * c), x = targetX - pp.worldX, y = targetY - pp.worldY;
+			float tx = (x * d - y * b) * id - px, ty = (y * a - x * c) * id - py, dd = tx * tx + ty * ty;
+			x = cwx - pp.worldX;
+			y = cwy - pp.worldY;
+			float dx = (x * d - y * b) * id - px, dy = (y * a - x * c) * id - py;
+			float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2;
+			if (u) {
+				l2 *= psx;
+				float cos = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2);
+				if (cos < -1)
+					cos = -1;
+				else if (cos > 1) {
+					cos = 1;
+					if (stretch && l1 + l2 > 0.0001f) sx *= ((float)Math.Sqrt(dd) / (l1 + l2) - 1) * alpha + 1;
+				}
+				a2 = (float)Math.Acos(cos) * bendDir;
+				a = l1 + l2 * cos;
+				b = l2 * (float)Math.Sin(a2);
+				a1 = (float)Math.Atan2(ty * a - tx * b, tx * a + ty * b);
+			} else {
+				a = psx * l2;
+				b = psy * l2;
+				float aa = a * a, bb = b * b, ta = (float)Math.Atan2(ty, tx);
+				c = bb * l1 * l1 + aa * dd - aa * bb;
+				float c1 = -2 * bb * l1, c2 = bb - aa;
+				d = c1 * c1 - 4 * c2 * c;
+				if (d >= 0) {
+					float q = (float)Math.Sqrt(d);
+					if (c1 < 0) q = -q;
+					q = -(c1 + q) / 2;
+					float r0 = q / c2, r1 = c / q;
+					float r = Math.Abs(r0) < Math.Abs(r1) ? r0 : r1;
+					if (r * r <= dd) {
+						y = (float)Math.Sqrt(dd - r * r) * bendDir;
+						a1 = ta - (float)Math.Atan2(y, r);
+						a2 = (float)Math.Atan2(y / psy, (r - l1) / psx);
+						goto break_outer; // break outer;
+					}
+				}
+				float minAngle = MathUtils.PI, minX = l1 - a, minDist = minX * minX, minY = 0;
+				float maxAngle = 0, maxX = l1 + a, maxDist = maxX * maxX, maxY = 0;
+				c = -a * l1 / (aa - bb);
+				if (c >= -1 && c <= 1) {
+					c = (float)Math.Acos(c);
+					x = a * (float)Math.Cos(c) + l1;
+					y = b * (float)Math.Sin(c);
+					d = x * x + y * y;
+					if (d < minDist) {
+						minAngle = c;
+						minDist = d;
+						minX = x;
+						minY = y;
+					}
+					if (d > maxDist) {
+						maxAngle = c;
+						maxDist = d;
+						maxX = x;
+						maxY = y;
+					}
+				}
+				if (dd <= (minDist + maxDist) / 2) {
+					a1 = ta - (float)Math.Atan2(minY * bendDir, minX);
+					a2 = minAngle * bendDir;
+				} else {
+					a1 = ta - (float)Math.Atan2(maxY * bendDir, maxX);
+					a2 = maxAngle * bendDir;
+				}
+			}
+			break_outer:
+			float os = (float)Math.Atan2(cy, cx) * s2;
+			float rotation = parent.arotation;
+			a1 = (a1 - os) * MathUtils.RadDeg + os1 - rotation;
+			if (a1 > 180)
+				a1 -= 360;
+			else if (a1 < -180) a1 += 360;
+			parent.UpdateWorldTransform(px, py, rotation + a1 * alpha, sx, parent.ascaleY, 0, 0);
+			rotation = child.arotation;
+			a2 = ((a2 + os) * MathUtils.RadDeg - child.ashearX) * s2 + os2 - rotation;
+			if (a2 > 180)
+				a2 -= 360;
+			else if (a2 < -180) a2 += 360;
+			child.UpdateWorldTransform(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY);
+		}
+	}
+}

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

@@ -1,112 +1,112 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-using System;
-using System.Collections.Generic;
-
-namespace Spine {
-	/// <summary>Stores the setup pose for an IkConstraint.</summary>
-	public class IkConstraintData {
-		internal string name;
-		internal int order;
-		internal List<BoneData> bones = new List<BoneData>();
-		internal BoneData target;
-		internal int bendDirection = 1;
-		internal bool compress, stretch, uniform;
-		internal float mix = 1;
-
-		/// <summary>The IK constraint's name, which is unique within the skeleton.</summary>
-		public string Name {
-			get { return name; }
-		}
-
-		public int Order {
-			get { return order; }
-			set { order = value; }
-		}
-
-		/// <summary>The bones that are constrained by this IK Constraint.</summary>
-		public List<BoneData> Bones {
-			get { return bones; }
-		}
-
-		/// <summary>The bone that is the IK target.</summary>
-		public BoneData Target {
-			get { return target; }
-			set { target = value; }
-		}
-
-		/// <summary>
-		/// A percentage (0-1) that controls the mix between the constraint and unconstrained rotations.</summary>
-		public float Mix {
-			get { return mix; }
-			set { mix = value; }
-		}
-
-		/// <summary>Controls the bend direction of the IK bones, either 1 or -1.</summary>
-		public int BendDirection {
-			get { return bendDirection; }
-			set { bendDirection = value; }
-		}
-
-		/// <summary>
-		/// When true, and only a single bone is being constrained, 
-		/// if the target is too close, the bone is scaled to reach it. </summary>
-		public bool Compress {
-			get { return compress; }
-			set { compress = value; }
-		}
-
-		/// <summary>
-		/// When true, if the target is out of range, the parent bone is scaled on the X axis to reach it. 
-		/// If the bone has local nonuniform scale, stretching is not applied.</summary>
-		public bool Stretch {
-			get { return stretch; }
-			set { stretch = value; }
-		}
-
-		/// <summary>
-		/// When true, only a single bone is being constrained and Compress or Stretch is used, 
-		/// the bone is scaled both on the X and Y axes.</summary>
-		public bool Uniform {
-			get { return uniform; }
-			set { uniform = value; }
-		}
-
-		public IkConstraintData (string name) {
-			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
-			this.name = name;
-		}
-
-		override public string ToString () {
-			return name;
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+using System.Collections.Generic;
+
+namespace Spine {
+	/// <summary>Stores the setup pose for an IkConstraint.</summary>
+	public class IkConstraintData {
+		internal string name;
+		internal int order;
+		internal List<BoneData> bones = new List<BoneData>();
+		internal BoneData target;
+		internal int bendDirection = 1;
+		internal bool compress, stretch, uniform;
+		internal float mix = 1;
+
+		/// <summary>The IK constraint's name, which is unique within the skeleton.</summary>
+		public string Name {
+			get { return name; }
+		}
+
+		public int Order {
+			get { return order; }
+			set { order = value; }
+		}
+
+		/// <summary>The bones that are constrained by this IK Constraint.</summary>
+		public List<BoneData> Bones {
+			get { return bones; }
+		}
+
+		/// <summary>The bone that is the IK target.</summary>
+		public BoneData Target {
+			get { return target; }
+			set { target = value; }
+		}
+
+		/// <summary>
+		/// A percentage (0-1) that controls the mix between the constraint and unconstrained rotations.</summary>
+		public float Mix {
+			get { return mix; }
+			set { mix = value; }
+		}
+
+		/// <summary>Controls the bend direction of the IK bones, either 1 or -1.</summary>
+		public int BendDirection {
+			get { return bendDirection; }
+			set { bendDirection = value; }
+		}
+
+		/// <summary>
+		/// When true, and only a single bone is being constrained, 
+		/// if the target is too close, the bone is scaled to reach it. </summary>
+		public bool Compress {
+			get { return compress; }
+			set { compress = value; }
+		}
+
+		/// <summary>
+		/// When true, if the target is out of range, the parent bone is scaled on the X axis to reach it. 
+		/// If the bone has local nonuniform scale, stretching is not applied.</summary>
+		public bool Stretch {
+			get { return stretch; }
+			set { stretch = value; }
+		}
+
+		/// <summary>
+		/// When true, only a single bone is being constrained and Compress or Stretch is used, 
+		/// the bone is scaled both on the X and Y axes.</summary>
+		public bool Uniform {
+			get { return uniform; }
+			set { uniform = value; }
+		}
+
+		public IkConstraintData (string name) {
+			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
+			this.name = name;
+		}
+
+		override public string ToString () {
+			return name;
+		}
+	}
+}

+ 535 - 535
spine-csharp/src/Json.cs

@@ -1,535 +1,535 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-using System;
-using System.IO;
-using System.Text;
-using System.Collections;
-using System.Globalization;
-using System.Collections.Generic;
-
-namespace Spine {
-	public static class Json {
-		public static object Deserialize (TextReader text) {
-			var parser = new SharpJson.JsonDecoder();
-			parser.parseNumbersAsFloat = true;
-			return parser.Decode(text.ReadToEnd());
-		}
-	}
-}
-
-/**
- *
- * Copyright (c) 2016 Adriano Tinoco d'Oliveira Rezende
- * 
- * Based on the JSON parser by Patrick van Bergen
- * http://techblog.procurios.nl/k/news/view/14605/14863/how-do-i-write-my-own-parser-(for-json).html
- *
- * Changes made:
- * 
- * 	- Optimized parser speed (deserialize roughly near 3x faster than original)
- *  - Added support to handle lexer/parser error messages with line numbers
- *  - Added more fine grained control over type conversions during the parsing
- *  - Refactory API (Separate Lexer code from Parser code and the Encoder from Decoder)
- *
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
- * and associated documentation files (the "Software"), to deal in the Software without restriction,
- * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
- * subject to the following conditions:
- * The above copyright notice and this permission notice shall be included in all copies or substantial
- * portions of the Software.
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
- * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
- * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
- * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- * 
- */
-namespace SharpJson
-{
-	class Lexer
-	{
-		public enum Token {
-			None,
-			Null,
-			True,
-			False,
-			Colon,
-			Comma,
-			String,
-			Number,
-			CurlyOpen,
-			CurlyClose,
-			SquaredOpen,
-			SquaredClose,
-		};
-
-		public bool hasError {
-			get {
-				return !success;
-			}
-		}
-
-		public int lineNumber {
-			get;
-			private set;
-		}
-
-		public bool parseNumbersAsFloat {
-			get;
-			set;
-		}
-
-		char[] json;
-		int index = 0;
-		bool success = true;
-		char[] stringBuffer = new char[4096];
-
-		public Lexer(string text)
-		{
-			Reset();
-
-			json = text.ToCharArray();
-			parseNumbersAsFloat = false;
-		}
-
-		public void Reset()
-		{
-			index = 0;
-			lineNumber = 1;
-			success = true;
-		}
-
-		public string ParseString()
-		{
-			int idx = 0;
-			StringBuilder builder = null;
-			
-			SkipWhiteSpaces();
-			
-			// "
-			char c = json[index++];
-			
-			bool failed = false;
-			bool complete = false;
-			
-			while (!complete && !failed) {
-				if (index == json.Length)
-					break;
-				
-				c = json[index++];
-				if (c == '"') {
-					complete = true;
-					break;
-				} else if (c == '\\') {
-					if (index == json.Length)
-						break;
-					
-					c = json[index++];
-					
-					switch (c) {
-					case '"':
-						stringBuffer[idx++] = '"';
-						break;
-					case '\\':
-						stringBuffer[idx++] = '\\';
-						break;
-					case '/':
-						stringBuffer[idx++] = '/';
-						break;
-					case 'b':
-						stringBuffer[idx++] = '\b';
-						break;
-					case'f':
-							stringBuffer[idx++] = '\f';
-						break;
-					case 'n':
-						stringBuffer[idx++] = '\n';
-						break;
-					case 'r':
-						stringBuffer[idx++] = '\r';
-						break;
-					case 't':
-						stringBuffer[idx++] = '\t';
-						break;
-					case 'u':
-						int remainingLength = json.Length - index;
-						if (remainingLength >= 4) {
-							var hex = new string(json, index, 4);
-							
-							// XXX: handle UTF
-							stringBuffer[idx++] = (char) Convert.ToInt32(hex, 16);
-							
-							// skip 4 chars
-							index += 4;
-						} else {
-							failed = true;
-						}
-						break;
-					}
-				} else {
-					stringBuffer[idx++] = c;
-				}
-				
-				if (idx >= stringBuffer.Length) {
-					if (builder == null)
-						builder = new StringBuilder();
-					
-					builder.Append(stringBuffer, 0, idx);
-					idx = 0;
-				}
-			}
-			
-			if (!complete) {
-				success = false;
-				return null;
-			}
-			
-			if (builder != null)
-				return builder.ToString ();
-			else
-				return new string (stringBuffer, 0, idx);
-		}
-		
-		string GetNumberString()
-		{
-			SkipWhiteSpaces();
-
-			int lastIndex = GetLastIndexOfNumber(index);
-			int charLength = (lastIndex - index) + 1;
-			
-			var result = new string (json, index, charLength);
-			
-			index = lastIndex + 1;
-			
-			return result;
-		}
-
-		public float ParseFloatNumber()
-		{
-			float number;
-			var str = GetNumberString ();
-			
-			if (!float.TryParse (str, NumberStyles.Float, CultureInfo.InvariantCulture, out number))
-				return 0;
-			
-			return number;
-		}
-
-		public double ParseDoubleNumber()
-		{
-			double number;
-			var str = GetNumberString ();
-			
-			if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number))
-				return 0;
-			
-			return number;
-		}
-		
-		int GetLastIndexOfNumber(int index)
-		{
-			int lastIndex;
-			
-			for (lastIndex = index; lastIndex < json.Length; lastIndex++) {
-				char ch = json[lastIndex];
-				
-				if ((ch < '0' || ch > '9') && ch != '+' && ch != '-'
-				    && ch != '.' && ch != 'e' && ch != 'E')
-					break;
-			}
-			
-			return lastIndex - 1;
-		}
-
-		void SkipWhiteSpaces()
-		{
-			for (; index < json.Length; index++) {
-				char ch = json[index];
-
-				if (ch == '\n')
-					lineNumber++;
-
-				if (!char.IsWhiteSpace(json[index]))
-					break;
-			}
-		}
-
-		public Token LookAhead()
-		{
-			SkipWhiteSpaces();
-
-			int savedIndex = index;
-			return NextToken(json, ref savedIndex);
-		}
-
-		public Token NextToken()
-		{
-			SkipWhiteSpaces();
-			return NextToken(json, ref index);
-		}
-
-		static Token NextToken(char[] json, ref int index)
-		{
-			if (index == json.Length)
-				return Token.None;
-			
-			char c = json[index++];
-			
-			switch (c) {
-			case '{':
-				return Token.CurlyOpen;
-			case '}':
-				return Token.CurlyClose;
-			case '[':
-				return Token.SquaredOpen;
-			case ']':
-				return Token.SquaredClose;
-			case ',':
-				return Token.Comma;
-			case '"':
-				return Token.String;
-			case '0': case '1': case '2': case '3': case '4':
-			case '5': case '6': case '7': case '8': case '9':
-			case '-':
-				return Token.Number;
-			case ':':
-				return Token.Colon;
-			}
-
-			index--;
-			
-			int remainingLength = json.Length - index;
-			
-			// false
-			if (remainingLength >= 5) {
-				if (json[index] == 'f' &&
-				    json[index + 1] == 'a' &&
-				    json[index + 2] == 'l' &&
-				    json[index + 3] == 's' &&
-				    json[index + 4] == 'e') {
-					index += 5;
-					return Token.False;
-				}
-			}
-			
-			// true
-			if (remainingLength >= 4) {
-				if (json[index] == 't' &&
-				    json[index + 1] == 'r' &&
-				    json[index + 2] == 'u' &&
-				    json[index + 3] == 'e') {
-					index += 4;
-					return Token.True;
-				}
-			}
-			
-			// null
-			if (remainingLength >= 4) {
-				if (json[index] == 'n' &&
-				    json[index + 1] == 'u' &&
-				    json[index + 2] == 'l' &&
-				    json[index + 3] == 'l') {
-					index += 4;
-					return Token.Null;
-				}
-			}
-
-			return Token.None;
-		}
-	}
-
-	public class JsonDecoder
-	{
-		public string errorMessage {
-			get;
-			private set;
-		}
-
-		public bool parseNumbersAsFloat {
-			get;
-			set;
-		}
-
-		Lexer lexer;
-
-		public JsonDecoder()
-		{
-			errorMessage = null;
-			parseNumbersAsFloat = false;
-		}
-
-		public object Decode(string text)
-		{
-			errorMessage = null;
-
-			lexer = new Lexer(text);
-			lexer.parseNumbersAsFloat = parseNumbersAsFloat;
-
-			return ParseValue();
-		}
-
-		public static object DecodeText(string text)
-		{
-			var builder = new JsonDecoder();
-			return builder.Decode(text);
-		}
-
-		IDictionary<string, object> ParseObject()
-		{
-			var table = new Dictionary<string, object>();
-
-			// {
-			lexer.NextToken();
-
-			while (true) {
-				var token = lexer.LookAhead();
-
-				switch (token) {
-				case Lexer.Token.None:
-					TriggerError("Invalid token");
-					return null;
-				case Lexer.Token.Comma:
-					lexer.NextToken();
-					break;
-				case Lexer.Token.CurlyClose:
-					lexer.NextToken();
-					return table;
-				default:
-					// name
-					string name = EvalLexer(lexer.ParseString());
-
-					if (errorMessage != null)
-						return null;
-
-					// :
-					token = lexer.NextToken();
-
-					if (token != Lexer.Token.Colon) {
-						TriggerError("Invalid token; expected ':'");
-						return null;
-					}
-					
-					// value
-					object value = ParseValue();
-
-					if (errorMessage != null)
-						return null;
-					
-					table[name] = value;
-					break;
-				}
-			}
-			
-			//return null; // Unreachable code
-		}
-
-		IList<object> ParseArray()
-		{
-			var array = new List<object>();
-			
-			// [
-			lexer.NextToken();
-
-			while (true) {
-				var token = lexer.LookAhead();
-
-				switch (token) {
-				case Lexer.Token.None:
-					TriggerError("Invalid token");
-					return null;
-				case Lexer.Token.Comma:
-					lexer.NextToken();
-					break;
-				case Lexer.Token.SquaredClose:
-					lexer.NextToken();
-					return array;
-				default:
-					object value = ParseValue();
-
-					if (errorMessage != null)
-						return null;
-
-					array.Add(value);
-					break;
-				}
-			}
-			
-			//return null; // Unreachable code
-		}
-
-		object ParseValue()
-		{
-			switch (lexer.LookAhead()) {
-			case Lexer.Token.String:
-				return EvalLexer(lexer.ParseString());
-			case Lexer.Token.Number:
-				if (parseNumbersAsFloat)
-					return EvalLexer(lexer.ParseFloatNumber());
-				else
-					return EvalLexer(lexer.ParseDoubleNumber());
-			case Lexer.Token.CurlyOpen:
-				return ParseObject();
-			case Lexer.Token.SquaredOpen:
-				return ParseArray();
-			case Lexer.Token.True:
-				lexer.NextToken();
-				return true;
-			case Lexer.Token.False:
-				lexer.NextToken();
-				return false;
-			case Lexer.Token.Null:
-				lexer.NextToken();
-				return null;
-			case Lexer.Token.None:
-				break;
-			}
-
-			TriggerError("Unable to parse value");
-			return null;
-		}
-
-		void TriggerError(string message)
-		{
-			errorMessage = string.Format("Error: '{0}' at line {1}",
-			                             message, lexer.lineNumber);
-		}
-
-		T EvalLexer<T>(T value)
-		{
-			if (lexer.hasError)
-				TriggerError("Lexical error ocurred");
-
-			return value;
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+using System.IO;
+using System.Text;
+using System.Collections;
+using System.Globalization;
+using System.Collections.Generic;
+
+namespace Spine {
+	public static class Json {
+		public static object Deserialize (TextReader text) {
+			var parser = new SharpJson.JsonDecoder();
+			parser.parseNumbersAsFloat = true;
+			return parser.Decode(text.ReadToEnd());
+		}
+	}
+}
+
+/**
+ *
+ * Copyright (c) 2016 Adriano Tinoco d'Oliveira Rezende
+ * 
+ * Based on the JSON parser by Patrick van Bergen
+ * http://techblog.procurios.nl/k/news/view/14605/14863/how-do-i-write-my-own-parser-(for-json).html
+ *
+ * Changes made:
+ * 
+ * 	- Optimized parser speed (deserialize roughly near 3x faster than original)
+ *  - Added support to handle lexer/parser error messages with line numbers
+ *  - Added more fine grained control over type conversions during the parsing
+ *  - Refactory API (Separate Lexer code from Parser code and the Encoder from Decoder)
+ *
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
+ * and associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in all copies or substantial
+ * portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
+ * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ * 
+ */
+namespace SharpJson
+{
+	class Lexer
+	{
+		public enum Token {
+			None,
+			Null,
+			True,
+			False,
+			Colon,
+			Comma,
+			String,
+			Number,
+			CurlyOpen,
+			CurlyClose,
+			SquaredOpen,
+			SquaredClose,
+		};
+
+		public bool hasError {
+			get {
+				return !success;
+			}
+		}
+
+		public int lineNumber {
+			get;
+			private set;
+		}
+
+		public bool parseNumbersAsFloat {
+			get;
+			set;
+		}
+
+		char[] json;
+		int index = 0;
+		bool success = true;
+		char[] stringBuffer = new char[4096];
+
+		public Lexer(string text)
+		{
+			Reset();
+
+			json = text.ToCharArray();
+			parseNumbersAsFloat = false;
+		}
+
+		public void Reset()
+		{
+			index = 0;
+			lineNumber = 1;
+			success = true;
+		}
+
+		public string ParseString()
+		{
+			int idx = 0;
+			StringBuilder builder = null;
+			
+			SkipWhiteSpaces();
+			
+			// "
+			char c = json[index++];
+			
+			bool failed = false;
+			bool complete = false;
+			
+			while (!complete && !failed) {
+				if (index == json.Length)
+					break;
+				
+				c = json[index++];
+				if (c == '"') {
+					complete = true;
+					break;
+				} else if (c == '\\') {
+					if (index == json.Length)
+						break;
+					
+					c = json[index++];
+					
+					switch (c) {
+					case '"':
+						stringBuffer[idx++] = '"';
+						break;
+					case '\\':
+						stringBuffer[idx++] = '\\';
+						break;
+					case '/':
+						stringBuffer[idx++] = '/';
+						break;
+					case 'b':
+						stringBuffer[idx++] = '\b';
+						break;
+					case'f':
+							stringBuffer[idx++] = '\f';
+						break;
+					case 'n':
+						stringBuffer[idx++] = '\n';
+						break;
+					case 'r':
+						stringBuffer[idx++] = '\r';
+						break;
+					case 't':
+						stringBuffer[idx++] = '\t';
+						break;
+					case 'u':
+						int remainingLength = json.Length - index;
+						if (remainingLength >= 4) {
+							var hex = new string(json, index, 4);
+							
+							// XXX: handle UTF
+							stringBuffer[idx++] = (char) Convert.ToInt32(hex, 16);
+							
+							// skip 4 chars
+							index += 4;
+						} else {
+							failed = true;
+						}
+						break;
+					}
+				} else {
+					stringBuffer[idx++] = c;
+				}
+				
+				if (idx >= stringBuffer.Length) {
+					if (builder == null)
+						builder = new StringBuilder();
+					
+					builder.Append(stringBuffer, 0, idx);
+					idx = 0;
+				}
+			}
+			
+			if (!complete) {
+				success = false;
+				return null;
+			}
+			
+			if (builder != null)
+				return builder.ToString ();
+			else
+				return new string (stringBuffer, 0, idx);
+		}
+		
+		string GetNumberString()
+		{
+			SkipWhiteSpaces();
+
+			int lastIndex = GetLastIndexOfNumber(index);
+			int charLength = (lastIndex - index) + 1;
+			
+			var result = new string (json, index, charLength);
+			
+			index = lastIndex + 1;
+			
+			return result;
+		}
+
+		public float ParseFloatNumber()
+		{
+			float number;
+			var str = GetNumberString ();
+			
+			if (!float.TryParse (str, NumberStyles.Float, CultureInfo.InvariantCulture, out number))
+				return 0;
+			
+			return number;
+		}
+
+		public double ParseDoubleNumber()
+		{
+			double number;
+			var str = GetNumberString ();
+			
+			if (!double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out number))
+				return 0;
+			
+			return number;
+		}
+		
+		int GetLastIndexOfNumber(int index)
+		{
+			int lastIndex;
+			
+			for (lastIndex = index; lastIndex < json.Length; lastIndex++) {
+				char ch = json[lastIndex];
+				
+				if ((ch < '0' || ch > '9') && ch != '+' && ch != '-'
+				    && ch != '.' && ch != 'e' && ch != 'E')
+					break;
+			}
+			
+			return lastIndex - 1;
+		}
+
+		void SkipWhiteSpaces()
+		{
+			for (; index < json.Length; index++) {
+				char ch = json[index];
+
+				if (ch == '\n')
+					lineNumber++;
+
+				if (!char.IsWhiteSpace(json[index]))
+					break;
+			}
+		}
+
+		public Token LookAhead()
+		{
+			SkipWhiteSpaces();
+
+			int savedIndex = index;
+			return NextToken(json, ref savedIndex);
+		}
+
+		public Token NextToken()
+		{
+			SkipWhiteSpaces();
+			return NextToken(json, ref index);
+		}
+
+		static Token NextToken(char[] json, ref int index)
+		{
+			if (index == json.Length)
+				return Token.None;
+			
+			char c = json[index++];
+			
+			switch (c) {
+			case '{':
+				return Token.CurlyOpen;
+			case '}':
+				return Token.CurlyClose;
+			case '[':
+				return Token.SquaredOpen;
+			case ']':
+				return Token.SquaredClose;
+			case ',':
+				return Token.Comma;
+			case '"':
+				return Token.String;
+			case '0': case '1': case '2': case '3': case '4':
+			case '5': case '6': case '7': case '8': case '9':
+			case '-':
+				return Token.Number;
+			case ':':
+				return Token.Colon;
+			}
+
+			index--;
+			
+			int remainingLength = json.Length - index;
+			
+			// false
+			if (remainingLength >= 5) {
+				if (json[index] == 'f' &&
+				    json[index + 1] == 'a' &&
+				    json[index + 2] == 'l' &&
+				    json[index + 3] == 's' &&
+				    json[index + 4] == 'e') {
+					index += 5;
+					return Token.False;
+				}
+			}
+			
+			// true
+			if (remainingLength >= 4) {
+				if (json[index] == 't' &&
+				    json[index + 1] == 'r' &&
+				    json[index + 2] == 'u' &&
+				    json[index + 3] == 'e') {
+					index += 4;
+					return Token.True;
+				}
+			}
+			
+			// null
+			if (remainingLength >= 4) {
+				if (json[index] == 'n' &&
+				    json[index + 1] == 'u' &&
+				    json[index + 2] == 'l' &&
+				    json[index + 3] == 'l') {
+					index += 4;
+					return Token.Null;
+				}
+			}
+
+			return Token.None;
+		}
+	}
+
+	public class JsonDecoder
+	{
+		public string errorMessage {
+			get;
+			private set;
+		}
+
+		public bool parseNumbersAsFloat {
+			get;
+			set;
+		}
+
+		Lexer lexer;
+
+		public JsonDecoder()
+		{
+			errorMessage = null;
+			parseNumbersAsFloat = false;
+		}
+
+		public object Decode(string text)
+		{
+			errorMessage = null;
+
+			lexer = new Lexer(text);
+			lexer.parseNumbersAsFloat = parseNumbersAsFloat;
+
+			return ParseValue();
+		}
+
+		public static object DecodeText(string text)
+		{
+			var builder = new JsonDecoder();
+			return builder.Decode(text);
+		}
+
+		IDictionary<string, object> ParseObject()
+		{
+			var table = new Dictionary<string, object>();
+
+			// {
+			lexer.NextToken();
+
+			while (true) {
+				var token = lexer.LookAhead();
+
+				switch (token) {
+				case Lexer.Token.None:
+					TriggerError("Invalid token");
+					return null;
+				case Lexer.Token.Comma:
+					lexer.NextToken();
+					break;
+				case Lexer.Token.CurlyClose:
+					lexer.NextToken();
+					return table;
+				default:
+					// name
+					string name = EvalLexer(lexer.ParseString());
+
+					if (errorMessage != null)
+						return null;
+
+					// :
+					token = lexer.NextToken();
+
+					if (token != Lexer.Token.Colon) {
+						TriggerError("Invalid token; expected ':'");
+						return null;
+					}
+					
+					// value
+					object value = ParseValue();
+
+					if (errorMessage != null)
+						return null;
+					
+					table[name] = value;
+					break;
+				}
+			}
+			
+			//return null; // Unreachable code
+		}
+
+		IList<object> ParseArray()
+		{
+			var array = new List<object>();
+			
+			// [
+			lexer.NextToken();
+
+			while (true) {
+				var token = lexer.LookAhead();
+
+				switch (token) {
+				case Lexer.Token.None:
+					TriggerError("Invalid token");
+					return null;
+				case Lexer.Token.Comma:
+					lexer.NextToken();
+					break;
+				case Lexer.Token.SquaredClose:
+					lexer.NextToken();
+					return array;
+				default:
+					object value = ParseValue();
+
+					if (errorMessage != null)
+						return null;
+
+					array.Add(value);
+					break;
+				}
+			}
+			
+			//return null; // Unreachable code
+		}
+
+		object ParseValue()
+		{
+			switch (lexer.LookAhead()) {
+			case Lexer.Token.String:
+				return EvalLexer(lexer.ParseString());
+			case Lexer.Token.Number:
+				if (parseNumbersAsFloat)
+					return EvalLexer(lexer.ParseFloatNumber());
+				else
+					return EvalLexer(lexer.ParseDoubleNumber());
+			case Lexer.Token.CurlyOpen:
+				return ParseObject();
+			case Lexer.Token.SquaredOpen:
+				return ParseArray();
+			case Lexer.Token.True:
+				lexer.NextToken();
+				return true;
+			case Lexer.Token.False:
+				lexer.NextToken();
+				return false;
+			case Lexer.Token.Null:
+				lexer.NextToken();
+				return null;
+			case Lexer.Token.None:
+				break;
+			}
+
+			TriggerError("Unable to parse value");
+			return null;
+		}
+
+		void TriggerError(string message)
+		{
+			errorMessage = string.Format("Error: '{0}' at line {1}",
+			                             message, lexer.lineNumber);
+		}
+
+		T EvalLexer<T>(T value)
+		{
+			if (lexer.hasError)
+				TriggerError("Lexical error ocurred");
+
+			return value;
+		}
+	}
+}

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

@@ -1,146 +1,146 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-using System;
-
-namespace Spine {
-	public static class MathUtils {
-		public const float PI = 3.1415927f;
-		public const float PI2 = PI * 2;
-		public const float RadDeg = 180f / PI;
-		public const float DegRad = PI / 180;
-
-		const int SIN_BITS = 14; // 16KB. Adjust for accuracy.
-		const int SIN_MASK = ~(-1 << SIN_BITS);
-		const int SIN_COUNT = SIN_MASK + 1;
-		const float RadFull = PI * 2;
-		const float DegFull = 360;
-		const float RadToIndex = SIN_COUNT / RadFull;
-		const float DegToIndex = SIN_COUNT / DegFull;
-		static float[] sin = new float[SIN_COUNT];
-
-		static Random random = new Random();
-
-		static MathUtils () {
-			for (int i = 0; i < SIN_COUNT; i++)
-				sin[i] = (float)Math.Sin((i + 0.5f) / SIN_COUNT * RadFull);
-			for (int i = 0; i < 360; i += 90)
-				sin[(int)(i * DegToIndex) & SIN_MASK] = (float)Math.Sin(i * DegRad);
-		}
-
-		/// <summary>Returns the sine in radians from a lookup table.</summary>
-		static public float Sin (float radians) {
-			return sin[(int)(radians * RadToIndex) & SIN_MASK];
-		}
-
-		/// <summary>Returns the cosine in radians from a lookup table.</summary>
-		static public float Cos (float radians) {
-			return sin[(int)((radians + PI / 2) * RadToIndex) & SIN_MASK];
-		}
-			
-		/// <summary>Returns the sine in radians from a lookup table.</summary>
-		static public float SinDeg (float degrees) {
-			return sin[(int)(degrees * DegToIndex) & SIN_MASK];
-		}
-			
-		/// <summary>Returns the cosine in radians from a lookup table.</summary>
-		static public float CosDeg (float degrees) {
-			return sin[(int)((degrees + 90) * DegToIndex) & SIN_MASK];
-		}
-
-		/// <summary>Returns atan2 in radians, faster but less accurate than Math.Atan2. Average error of 0.00231 radians (0.1323
-		/// degrees), largest error of 0.00488 radians (0.2796 degrees).</summary>
-		static public float Atan2 (float y, float x) {
-			if (x == 0f) {
-				if (y > 0f) return PI / 2;
-				if (y == 0f) return 0f;
-				return -PI / 2;
-			}
-			float atan, z = y / x;
-			if (Math.Abs(z) < 1f) {
-				atan = z / (1f + 0.28f * z * z);
-				if (x < 0f) return atan + (y < 0f ? -PI : PI);
-				return atan;
-			}
-			atan = PI / 2 - z / (z * z + 0.28f);
-			return y < 0f ? atan - PI : atan;
-		}
-
-		static public float Clamp (float value, float min, float max) {
-			if (value < min) return min;
-			if (value > max) return max;
-			return value;
-		}
-
-		static public float RandomTriangle(float min, float max) {
-			return RandomTriangle(min, max, (min + max) * 0.5f);
-		}
-
-		static public float RandomTriangle(float min, float max, float mode) {
-			float u = (float)random.NextDouble();
-			float d = max - min;
-			if (u <= (mode - min) / d) return min + (float)Math.Sqrt(u * d * (mode - min));
-			return max - (float)Math.Sqrt((1 - u) * d * (max - mode));
-		}
-	}
-
-	public abstract class IInterpolation {
-		public static IInterpolation Pow2 = new Pow(2);
-		public static IInterpolation Pow2Out = new PowOut(2);
-
-		protected abstract float Apply(float a);
-
-		public float Apply(float start, float end, float a) {
-			return start + (end - start) * Apply(a);
-		}
-	}
-
-	public class Pow: IInterpolation {
-		public float Power { get; set; }
-
-		public Pow(float power) {
-			Power = power;
-		}
-
-		protected override float Apply(float a) {
-			if (a <= 0.5f) return (float)Math.Pow(a * 2, Power) / 2;
-			return (float)Math.Pow((a - 1) * 2, Power) / (Power % 2 == 0 ? -2 : 2) + 1;
-		}
-	}
-
-	public class PowOut : Pow {
-		public PowOut(float power) : base(power) {
-		}
-
-		protected override float Apply(float a) {
-			return (float)Math.Pow(a - 1, Power) * (Power % 2 == 0 ? -1 : 1) + 1;
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+	public static class MathUtils {
+		public const float PI = 3.1415927f;
+		public const float PI2 = PI * 2;
+		public const float RadDeg = 180f / PI;
+		public const float DegRad = PI / 180;
+
+		const int SIN_BITS = 14; // 16KB. Adjust for accuracy.
+		const int SIN_MASK = ~(-1 << SIN_BITS);
+		const int SIN_COUNT = SIN_MASK + 1;
+		const float RadFull = PI * 2;
+		const float DegFull = 360;
+		const float RadToIndex = SIN_COUNT / RadFull;
+		const float DegToIndex = SIN_COUNT / DegFull;
+		static float[] sin = new float[SIN_COUNT];
+
+		static Random random = new Random();
+
+		static MathUtils () {
+			for (int i = 0; i < SIN_COUNT; i++)
+				sin[i] = (float)Math.Sin((i + 0.5f) / SIN_COUNT * RadFull);
+			for (int i = 0; i < 360; i += 90)
+				sin[(int)(i * DegToIndex) & SIN_MASK] = (float)Math.Sin(i * DegRad);
+		}
+
+		/// <summary>Returns the sine in radians from a lookup table.</summary>
+		static public float Sin (float radians) {
+			return sin[(int)(radians * RadToIndex) & SIN_MASK];
+		}
+
+		/// <summary>Returns the cosine in radians from a lookup table.</summary>
+		static public float Cos (float radians) {
+			return sin[(int)((radians + PI / 2) * RadToIndex) & SIN_MASK];
+		}
+			
+		/// <summary>Returns the sine in radians from a lookup table.</summary>
+		static public float SinDeg (float degrees) {
+			return sin[(int)(degrees * DegToIndex) & SIN_MASK];
+		}
+			
+		/// <summary>Returns the cosine in radians from a lookup table.</summary>
+		static public float CosDeg (float degrees) {
+			return sin[(int)((degrees + 90) * DegToIndex) & SIN_MASK];
+		}
+
+		/// <summary>Returns atan2 in radians, faster but less accurate than Math.Atan2. Average error of 0.00231 radians (0.1323
+		/// degrees), largest error of 0.00488 radians (0.2796 degrees).</summary>
+		static public float Atan2 (float y, float x) {
+			if (x == 0f) {
+				if (y > 0f) return PI / 2;
+				if (y == 0f) return 0f;
+				return -PI / 2;
+			}
+			float atan, z = y / x;
+			if (Math.Abs(z) < 1f) {
+				atan = z / (1f + 0.28f * z * z);
+				if (x < 0f) return atan + (y < 0f ? -PI : PI);
+				return atan;
+			}
+			atan = PI / 2 - z / (z * z + 0.28f);
+			return y < 0f ? atan - PI : atan;
+		}
+
+		static public float Clamp (float value, float min, float max) {
+			if (value < min) return min;
+			if (value > max) return max;
+			return value;
+		}
+
+		static public float RandomTriangle(float min, float max) {
+			return RandomTriangle(min, max, (min + max) * 0.5f);
+		}
+
+		static public float RandomTriangle(float min, float max, float mode) {
+			float u = (float)random.NextDouble();
+			float d = max - min;
+			if (u <= (mode - min) / d) return min + (float)Math.Sqrt(u * d * (mode - min));
+			return max - (float)Math.Sqrt((1 - u) * d * (max - mode));
+		}
+	}
+
+	public abstract class IInterpolation {
+		public static IInterpolation Pow2 = new Pow(2);
+		public static IInterpolation Pow2Out = new PowOut(2);
+
+		protected abstract float Apply(float a);
+
+		public float Apply(float start, float end, float a) {
+			return start + (end - start) * Apply(a);
+		}
+	}
+
+	public class Pow: IInterpolation {
+		public float Power { get; set; }
+
+		public Pow(float power) {
+			Power = power;
+		}
+
+		protected override float Apply(float a) {
+			if (a <= 0.5f) return (float)Math.Pow(a * 2, Power) / 2;
+			return (float)Math.Pow((a - 1) * 2, Power) / (Power % 2 == 0 ? -2 : 2) + 1;
+		}
+	}
+
+	public class PowOut : Pow {
+		public PowOut(float power) : base(power) {
+		}
+
+		protected override float Apply(float a) {
+			return (float)Math.Pow(a - 1, Power) * (Power % 2 == 0 ? -1 : 1) + 1;
+		}
+	}
+}

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

@@ -1,436 +1,436 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-using System;
-
-namespace Spine {
-	public class PathConstraint : IConstraint {
-		const int NONE = -1, BEFORE = -2, AFTER = -3;
-		const float Epsilon = 0.00001f;
-
-		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 int Order { get { return data.order; } }
-		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;
-		}
-
-		/// <summary>Applies the constraint to the constrained bones.</summary>
-		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;
-			bool percentSpacing = data.spacingMode == SpacingMode.Percent;
-			RotateMode rotateMode = data.rotateMode;
-			bool tangents = rotateMode == RotateMode.Tangent, scale = rotateMode == RotateMode.ChainScale;
-			int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1;
-			Bone[] bonesItems = this.bones.Items;
-			ExposedList<float> spaces = this.spaces.Resize(spacesCount), lengths = null;
-			float spacing = this.spacing;
-			if (scale || !percentSpacing) {
-				if (scale) lengths = this.lengths.Resize(boneCount);
-				bool lengthSpacing = data.spacingMode == SpacingMode.Length;
-				for (int i = 0, n = spacesCount - 1; i < n;) {
-					Bone bone = bonesItems[i];
-					float setupLength = bone.data.length;
-					if (setupLength < PathConstraint.Epsilon) {
-						if (scale) lengths.Items[i] = 0;
-						spaces.Items[++i] = 0;
-					} else if (percentSpacing) {
-						if (scale) {
-							float x = setupLength * bone.a, y = setupLength * bone.c;
-							float length = (float)Math.Sqrt(x * x + y * y);
-							lengths.Items[i] = length;
-						}
-						spaces.Items[++i] = spacing;
-					} else {
-						float x = setupLength * bone.a, y = setupLength * bone.c;
-						float length = (float)Math.Sqrt(x * x + y * y);
-						if (scale) lengths.Items[i] = length;
-						spaces.Items[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength;
-					}
-				}
-			} else {
-				for (int i = 1; i < spacesCount; i++)
-					spaces.Items[i] = spacing;
-			}
-
-			float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents,
-				data.positionMode == PositionMode.Percent, percentSpacing);
-			float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation;
-			bool tip;
-			if (offsetRotation == 0) {
-				tip = rotateMode == RotateMode.Chain;
-			} else {
-				tip = false;
-				Bone p = target.bone;
-				offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.DegRad : -MathUtils.DegRad;
-			}
-			for (int i = 0, p = 3; i < boneCount; i++, p += 3) {
-				Bone bone = bonesItems[i];
-				bone.worldX += (boneX - bone.worldX) * translateMix;
-				bone.worldY += (boneY - bone.worldY) * translateMix;
-				float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY;
-				if (scale) {
-					float length = lengths.Items[i];
-					if (length >= PathConstraint.Epsilon) {
-						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] < PathConstraint.Epsilon)
-						r = positions[p + 2];
-					else
-						r = MathUtils.Atan2(dy, dx);
-					r -= MathUtils.Atan2(c, a);
-					if (tip) {
-						cos = MathUtils.Cos(r);
-						sin = MathUtils.Sin(r);
-						float length = bone.data.length;
-						boneX += (length * (cos * a - sin * c) - dx) * rotateMix;
-						boneY += (length * (sin * a + cos * c) - dy) * rotateMix;
-					} else
-						r += offsetRotation;
-					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;
-				}
-				bone.appliedValid = false;
-			}
-		}
-
-		float[] ComputeWorldPositions (PathAttachment path, int spacesCount, bool tangents, bool percentPosition,
-			bool percentSpacing) {
-
-			Slot target = this.target;
-			float position = this.position;
-			float[] spacesItems = 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 = 0;
-
-			if (!path.ConstantSpeed) {
-				float[] lengths = path.Lengths;
-				curveCount -= closed ? 1 : 2;
-				pathLength = lengths[curveCount];
-				if (percentPosition) position *= pathLength;
-				if (percentSpacing) {
-					for (int i = 1; i < spacesCount; i++)
-						spacesItems[i] *= pathLength;
-				}
-				world = this.world.Resize(8).Items;
-				for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) {
-					float space = spacesItems[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 < PathConstraint.Epsilon));
-				}
-				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;
-			else
-				position *= pathLength / path.lengths[curveCount - 1];
-
-			if (percentSpacing) {
-				for (int i = 1; i < spacesCount; i++)
-					spacesItems[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 = spacesItems[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 < PathConstraint.Epsilon));
-			}
-			return output;
-		}
-
-		static 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;
-		}
-
-		static 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;
-		}
-
-		static 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 < PathConstraint.Epsilon || float.IsNaN(p)) {
-				output[o] = x1;
-				output[o + 1] = y1;
-				output[o + 2] = (float)Math.Atan2(cy1 - y1, cx1 - x1);
-				return;
-			}
-			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) {
-				if (p < 0.001f)
-					output[o + 2] = (float)Math.Atan2(cy1 - y1, cx1 - x1);
-				else
-					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 v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+	public class PathConstraint : IConstraint {
+		const int NONE = -1, BEFORE = -2, AFTER = -3;
+		const float Epsilon = 0.00001f;
+
+		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 int Order { get { return data.order; } }
+		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;
+		}
+
+		/// <summary>Applies the constraint to the constrained bones.</summary>
+		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;
+			bool percentSpacing = data.spacingMode == SpacingMode.Percent;
+			RotateMode rotateMode = data.rotateMode;
+			bool tangents = rotateMode == RotateMode.Tangent, scale = rotateMode == RotateMode.ChainScale;
+			int boneCount = this.bones.Count, spacesCount = tangents ? boneCount : boneCount + 1;
+			Bone[] bonesItems = this.bones.Items;
+			ExposedList<float> spaces = this.spaces.Resize(spacesCount), lengths = null;
+			float spacing = this.spacing;
+			if (scale || !percentSpacing) {
+				if (scale) lengths = this.lengths.Resize(boneCount);
+				bool lengthSpacing = data.spacingMode == SpacingMode.Length;
+				for (int i = 0, n = spacesCount - 1; i < n;) {
+					Bone bone = bonesItems[i];
+					float setupLength = bone.data.length;
+					if (setupLength < PathConstraint.Epsilon) {
+						if (scale) lengths.Items[i] = 0;
+						spaces.Items[++i] = 0;
+					} else if (percentSpacing) {
+						if (scale) {
+							float x = setupLength * bone.a, y = setupLength * bone.c;
+							float length = (float)Math.Sqrt(x * x + y * y);
+							lengths.Items[i] = length;
+						}
+						spaces.Items[++i] = spacing;
+					} else {
+						float x = setupLength * bone.a, y = setupLength * bone.c;
+						float length = (float)Math.Sqrt(x * x + y * y);
+						if (scale) lengths.Items[i] = length;
+						spaces.Items[++i] = (lengthSpacing ? setupLength + spacing : spacing) * length / setupLength;
+					}
+				}
+			} else {
+				for (int i = 1; i < spacesCount; i++)
+					spaces.Items[i] = spacing;
+			}
+
+			float[] positions = ComputeWorldPositions(attachment, spacesCount, tangents,
+				data.positionMode == PositionMode.Percent, percentSpacing);
+			float boneX = positions[0], boneY = positions[1], offsetRotation = data.offsetRotation;
+			bool tip;
+			if (offsetRotation == 0) {
+				tip = rotateMode == RotateMode.Chain;
+			} else {
+				tip = false;
+				Bone p = target.bone;
+				offsetRotation *= p.a * p.d - p.b * p.c > 0 ? MathUtils.DegRad : -MathUtils.DegRad;
+			}
+			for (int i = 0, p = 3; i < boneCount; i++, p += 3) {
+				Bone bone = bonesItems[i];
+				bone.worldX += (boneX - bone.worldX) * translateMix;
+				bone.worldY += (boneY - bone.worldY) * translateMix;
+				float x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY;
+				if (scale) {
+					float length = lengths.Items[i];
+					if (length >= PathConstraint.Epsilon) {
+						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] < PathConstraint.Epsilon)
+						r = positions[p + 2];
+					else
+						r = MathUtils.Atan2(dy, dx);
+					r -= MathUtils.Atan2(c, a);
+					if (tip) {
+						cos = MathUtils.Cos(r);
+						sin = MathUtils.Sin(r);
+						float length = bone.data.length;
+						boneX += (length * (cos * a - sin * c) - dx) * rotateMix;
+						boneY += (length * (sin * a + cos * c) - dy) * rotateMix;
+					} else
+						r += offsetRotation;
+					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;
+				}
+				bone.appliedValid = false;
+			}
+		}
+
+		float[] ComputeWorldPositions (PathAttachment path, int spacesCount, bool tangents, bool percentPosition,
+			bool percentSpacing) {
+
+			Slot target = this.target;
+			float position = this.position;
+			float[] spacesItems = 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 = 0;
+
+			if (!path.ConstantSpeed) {
+				float[] lengths = path.Lengths;
+				curveCount -= closed ? 1 : 2;
+				pathLength = lengths[curveCount];
+				if (percentPosition) position *= pathLength;
+				if (percentSpacing) {
+					for (int i = 1; i < spacesCount; i++)
+						spacesItems[i] *= pathLength;
+				}
+				world = this.world.Resize(8).Items;
+				for (int i = 0, o = 0, curve = 0; i < spacesCount; i++, o += 3) {
+					float space = spacesItems[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 < PathConstraint.Epsilon));
+				}
+				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;
+			else
+				position *= pathLength / path.lengths[curveCount - 1];
+
+			if (percentSpacing) {
+				for (int i = 1; i < spacesCount; i++)
+					spacesItems[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 = spacesItems[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 < PathConstraint.Epsilon));
+			}
+			return output;
+		}
+
+		static 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;
+		}
+
+		static 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;
+		}
+
+		static 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 < PathConstraint.Epsilon || float.IsNaN(p)) {
+				output[o] = x1;
+				output[o + 1] = y1;
+				output[o + 2] = (float)Math.Atan2(cy1 - y1, cx1 - x1);
+				return;
+			}
+			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) {
+				if (p < 0.001f)
+					output[o + 2] = (float)Math.Atan2(cy1 - y1, cx1 - x1);
+				else
+					output[o + 2] = (float)Math.Atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt));
+			}
+		}
+	}
+}

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

@@ -1,79 +1,79 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-using System;
-
-namespace Spine {
-	public class PathConstraintData {
-		internal string name;
-		internal int order;
-		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 string Name { get { return name; } }
-		public int Order { get { return order; } set { order = value; } }
-		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 PathConstraintData (String name) {
-			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
-			this.name = name;
-		}
-
-		public override string ToString () {
-			return name;
-		}
-	}
-	
-	public enum PositionMode {
-		Fixed, Percent        
-	}
-
-	public enum SpacingMode {
-		Length, Fixed, Percent
-	}
-
-	public enum RotateMode {
-		Tangent, Chain, ChainScale
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+	public class PathConstraintData {
+		internal string name;
+		internal int order;
+		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 string Name { get { return name; } }
+		public int Order { get { return order; } set { order = value; } }
+		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 PathConstraintData (String name) {
+			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
+			this.name = name;
+		}
+
+		public override string ToString () {
+			return name;
+		}
+	}
+	
+	public enum PositionMode {
+		Fixed, Percent        
+	}
+
+	public enum SpacingMode {
+		Length, Fixed, Percent
+	}
+
+	public enum RotateMode {
+		Tangent, Chain, ChainScale
+	}
+}

+ 600 - 600
spine-csharp/src/Skeleton.cs

@@ -1,600 +1,600 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-using System;
-using System.Collections.Generic;
-
-namespace Spine {
-	public class Skeleton {
-		internal SkeletonData data;
-		internal ExposedList<Bone> bones;
-		internal ExposedList<Slot> slots;
-		internal ExposedList<Slot> drawOrder;
-		internal ExposedList<IkConstraint> ikConstraints;
-		internal ExposedList<TransformConstraint> transformConstraints;
-		internal ExposedList<PathConstraint> pathConstraints;
-		internal ExposedList<IUpdatable> updateCache = new ExposedList<IUpdatable>();
-		internal ExposedList<Bone> updateCacheReset = new ExposedList<Bone>();
-		internal Skin skin;
-		internal float r = 1, g = 1, b = 1, a = 1;
-		internal float time;
-		internal float scaleX = 1, scaleY = 1;
-		internal float x, y;
-
-		public SkeletonData Data { get { return data; } }
-		public ExposedList<Bone> Bones { get { return bones; } }
-		public ExposedList<IUpdatable> UpdateCacheList { get { return updateCache; } }
-		public ExposedList<Slot> Slots { get { return slots; } }
-		public ExposedList<Slot> DrawOrder { get { return drawOrder; } }
-		public ExposedList<IkConstraint> IkConstraints { get { return ikConstraints; } }
-		public ExposedList<PathConstraint> PathConstraints { get { return pathConstraints; } }
-		public ExposedList<TransformConstraint> TransformConstraints { get { return transformConstraints; } }
-		public Skin Skin { get { return skin; } set { skin = value; } }
-		public float R { get { return r; } set { r = value; } }
-		public float G { get { return g; } set { g = value; } }
-		public float B { get { return b; } set { b = value; } }
-		public float A { get { return a; } set { a = value; } }
-		public float Time { get { return time; } set { time = value; } }
-		public float X { get { return x; } set { x = value; } }
-		public float Y { get { return y; } set { y = value; } }
-		public float ScaleX { get { return scaleX; } set { scaleX = value; } }
-		public float ScaleY { get { return scaleY; } set { scaleY = value; } }
-
-		[Obsolete("Use ScaleX instead. FlipX is when ScaleX is negative.")]
-		public bool FlipX { get { return scaleX < 0; } set { scaleX = value ? -1f : 1f; } }
-
-		[Obsolete("Use ScaleY instead. FlipY is when ScaleY is negative.")]
-		public bool FlipY { get { return scaleY < 0; } set { scaleY = value ? -1f : 1f; } }
-
-		public Bone RootBone {
-			get { return bones.Count == 0 ? null : bones.Items[0]; }
-		}
-
-		public Skeleton (SkeletonData data) {
-			if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
-			this.data = data;
-
-			bones = new ExposedList<Bone>(data.bones.Count);
-			foreach (BoneData boneData in data.bones) {
-				Bone bone;
-				if (boneData.parent == null) {
-					bone = new Bone(boneData, this, null);				
-				} else {
-					Bone parent = bones.Items[boneData.parent.index];
-					bone = new Bone(boneData, this, parent);
-					parent.children.Add(bone);
-				}
-				bones.Add(bone);
-			}
-
-			slots = new ExposedList<Slot>(data.slots.Count);
-			drawOrder = new ExposedList<Slot>(data.slots.Count);
-			foreach (SlotData slotData in data.slots) {
-				Bone bone = bones.Items[slotData.boneData.index];
-				Slot slot = new Slot(slotData, bone);
-				slots.Add(slot);
-				drawOrder.Add(slot);
-			}
-
-			ikConstraints = new ExposedList<IkConstraint>(data.ikConstraints.Count);
-			foreach (IkConstraintData ikConstraintData in data.ikConstraints)
-				ikConstraints.Add(new IkConstraint(ikConstraintData, this));
-
-			transformConstraints = new ExposedList<TransformConstraint>(data.transformConstraints.Count);
-			foreach (TransformConstraintData transformConstraintData in data.transformConstraints)
-				transformConstraints.Add(new TransformConstraint(transformConstraintData, this));
-
-			pathConstraints = new ExposedList<PathConstraint> (data.pathConstraints.Count);
-			foreach (PathConstraintData pathConstraintData in data.pathConstraints)
-				pathConstraints.Add(new PathConstraint(pathConstraintData, this));
-
-			UpdateCache();
-			UpdateWorldTransform();
-		}
-
-		/// <summary>Caches information about bones and constraints. Must be called if bones, constraints or weighted path attachments are added
-		/// or removed.</summary>
-		public void UpdateCache () {
-			var updateCache = this.updateCache;
-			updateCache.Clear();
-			this.updateCacheReset.Clear();
-
-			var bones = this.bones;
-			for (int i = 0, n = bones.Count; i < n; i++)
-				bones.Items[i].sorted = false;
-
-			var ikConstraints = this.ikConstraints;
-			var transformConstraints = this.transformConstraints;
-			var pathConstraints = this.pathConstraints;
-			int ikCount = ikConstraints.Count, transformCount = transformConstraints.Count, pathCount = pathConstraints.Count;
-			int constraintCount = ikCount + transformCount + pathCount;
-			//outer:
-			for (int i = 0; i < constraintCount; i++) {
-				for (int ii = 0; ii < ikCount; ii++) {
-					IkConstraint constraint = ikConstraints.Items[ii];
-					if (constraint.data.order == i) {
-						SortIkConstraint(constraint);
-						goto continue_outer; //continue outer;
-					}
-				}
-				for (int ii = 0; ii < transformCount; ii++) {
-					TransformConstraint constraint = transformConstraints.Items[ii];
-					if (constraint.data.order == i) {
-						SortTransformConstraint(constraint);
-						goto continue_outer; //continue outer;
-					}
-				}
-				for (int ii = 0; ii < pathCount; ii++) {
-					PathConstraint constraint = pathConstraints.Items[ii];
-					if (constraint.data.order == i) {
-						SortPathConstraint(constraint);
-						goto continue_outer; //continue outer;
-					}
-				}
-				continue_outer: {}
-			}
-
-			for (int i = 0, n = bones.Count; i < n; i++)
-				SortBone(bones.Items[i]);
-		}
-
-		private void SortIkConstraint (IkConstraint constraint) {
-			Bone target = constraint.target;
-			SortBone(target);
-
-			var constrained = constraint.bones;
-			Bone parent = constrained.Items[0];
-			SortBone(parent);
-
-			if (constrained.Count > 1) {
-				Bone child = constrained.Items[constrained.Count - 1];
-				if (!updateCache.Contains(child))
-					updateCacheReset.Add(child);
-			}
-
-			updateCache.Add(constraint);
-
-			SortReset(parent.children);
-			constrained.Items[constrained.Count - 1].sorted = true;
-		}
-
-		private void SortPathConstraint (PathConstraint constraint) {
-			Slot slot = constraint.target;
-			int slotIndex = slot.data.index;
-			Bone slotBone = slot.bone;
-			if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone);
-			if (data.defaultSkin != null && data.defaultSkin != skin)
-				SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone);
-			for (int ii = 0, nn = data.skins.Count; ii < nn; ii++)
-				SortPathConstraintAttachment(data.skins.Items[ii], slotIndex, slotBone);
-
-			Attachment attachment = slot.attachment;
-			if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone);
-
-			var constrained = constraint.bones;
-			int boneCount = constrained.Count;
-			for (int i = 0; i < boneCount; i++)
-				SortBone(constrained.Items[i]);
-
-			updateCache.Add(constraint);
-
-			for (int i = 0; i < boneCount; i++)
-				SortReset(constrained.Items[i].children);
-			for (int i = 0; i < boneCount; i++)
-				constrained.Items[i].sorted = true;
-		}
-
-		private void SortTransformConstraint (TransformConstraint constraint) {
-			SortBone(constraint.target);
-
-			var constrained = constraint.bones;
-			int boneCount = constrained.Count;
-			if (constraint.data.local) {
-				for (int i = 0; i < boneCount; i++) {
-					Bone child = constrained.Items[i];
-					SortBone(child.parent);
-					if (!updateCache.Contains(child)) updateCacheReset.Add(child);
-				}
-			} else {
-				for (int i = 0; i < boneCount; i++)
-					SortBone(constrained.Items[i]);
-			}
-
-			updateCache.Add(constraint);
-
-			for (int i = 0; i < boneCount; i++)
-				SortReset(constrained.Items[i].children);
-			for (int i = 0; i < boneCount; i++)
-				constrained.Items[i].sorted = true;
-		}
-
-		private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) {
-			foreach (var entry in skin.Attachments)
-				if (entry.Key.slotIndex == slotIndex) SortPathConstraintAttachment(entry.Value, slotBone);
-		}
-
-		private void SortPathConstraintAttachment (Attachment attachment, Bone slotBone) {
-			if (!(attachment is PathAttachment)) return;
-			int[] pathBones = ((PathAttachment)attachment).bones;
-			if (pathBones == null)
-				SortBone(slotBone);
-			else {
-				var bones = this.bones;
-				for (int i = 0, n = pathBones.Length; i < n;) {
-					int nn = pathBones[i++];
-					nn += i;
-					while (i < nn)
-						SortBone(bones.Items[pathBones[i++]]);
-				}
-			}
-		}
-
-		private void SortBone (Bone bone) {
-			if (bone.sorted) return;
-			Bone parent = bone.parent;
-			if (parent != null) SortBone(parent);
-			bone.sorted = true;
-			updateCache.Add(bone);
-		}
-
-		private static void SortReset (ExposedList<Bone> bones) {
-			var bonesItems = bones.Items;
-			for (int i = 0, n = bones.Count; i < n; i++) {
-				Bone bone = bonesItems[i];
-				if (bone.sorted) SortReset(bone.children);
-				bone.sorted = false;
-			}
-		}
-
-		/// <summary>Updates the world transform for each bone and applies constraints.</summary>
-		public void UpdateWorldTransform () {
-			var updateCacheReset = this.updateCacheReset;
-			var updateCacheResetItems = updateCacheReset.Items;
-			for (int i = 0, n = updateCacheReset.Count; i < n; i++) {
-				Bone bone = updateCacheResetItems[i];
-				bone.ax = bone.x;
-				bone.ay = bone.y;
-				bone.arotation = bone.rotation;
-				bone.ascaleX = bone.scaleX;
-				bone.ascaleY = bone.scaleY;
-				bone.ashearX = bone.shearX;
-				bone.ashearY = bone.shearY;
-				bone.appliedValid = true;
-			}
-			var updateItems = this.updateCache.Items;
-			for (int i = 0, n = updateCache.Count; i < n; i++)
-				updateItems[i].Update();
-		}
-
-		/// <summary>
-		/// Updates the world transform for each bone and applies all constraints. The root bone will be temporarily parented to the specified bone.
-	 	/// </summary>
-		public void UpdateWorldTransform (Bone parent) {
-			// This partial update avoids computing the world transform for constrained bones when 1) the bone is not updated
-			// before the constraint, 2) the constraint only needs to access the applied local transform, and 3) the constraint calls
-			// updateWorldTransform.
-			var updateCacheReset = this.updateCacheReset;
-			var updateCacheResetItems = updateCacheReset.Items;
-			for (int i = 0, n = updateCacheReset.Count; i < n; i++) {
-				Bone bone = updateCacheResetItems[i];
-				bone.ax = bone.x;
-				bone.ay = bone.y;
-				bone.arotation = bone.rotation;
-				bone.ascaleX = bone.scaleX;
-				bone.ascaleY = bone.scaleY;
-				bone.ashearX = bone.shearX;
-				bone.ashearY = bone.shearY;
-				bone.appliedValid = true;
-			}
-
-			// Apply the parent bone transform to the root bone. The root bone
-			// always inherits scale, rotation and reflection.
-			Bone rootBone = this.RootBone;
-			float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
-			rootBone.worldX = pa * x + pb * y + parent.worldX;
-			rootBone.worldY = pc * x + pd * y + parent.worldY;
-
-			float rotationY = rootBone.rotation + 90 + rootBone.shearY;
-			float la = MathUtils.CosDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX;
-			float lb = MathUtils.CosDeg(rotationY) * rootBone.scaleY;
-			float lc = MathUtils.SinDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX;
-			float ld = MathUtils.SinDeg(rotationY) * rootBone.scaleY;
-			rootBone.a = (pa * la + pb * lc) * scaleX;
-			rootBone.b = (pa * lb + pb * ld) * scaleX;
-			rootBone.c = (pc * la + pd * lc) * scaleY;
-			rootBone.d = (pc * lb + pd * ld) * scaleY;
-
-			// Update everything except root bone.
-			var updateCache = this.updateCache;
-			var updateCacheItems = updateCache.Items;
-			for (int i = 0, n = updateCache.Count; i < n; i++) {
-				var updatable = updateCacheItems[i];
-				if (updatable != rootBone)
-					updatable.Update();
-			}
-		}
-
-		/// <summary>Sets the bones, constraints, and slots to their setup pose values.</summary>
-		public void SetToSetupPose () {
-			SetBonesToSetupPose();
-			SetSlotsToSetupPose();
-		}
-
-		/// <summary>Sets the bones and constraints to their setup pose values.</summary>
-		public void SetBonesToSetupPose () {
-			var bonesItems = this.bones.Items;
-			for (int i = 0, n = bones.Count; i < n; i++)
-				bonesItems[i].SetToSetupPose();
-
-			var ikConstraintsItems = this.ikConstraints.Items;
-			for (int i = 0, n = ikConstraints.Count; i < n; i++) {
-				IkConstraint constraint = ikConstraintsItems[i];
-				constraint.mix = constraint.data.mix;
-				constraint.bendDirection = constraint.data.bendDirection;
-				constraint.compress = constraint.data.compress;
-				constraint.stretch = constraint.data.stretch;
-			}
-
-			var transformConstraintsItems = this.transformConstraints.Items;
-			for (int i = 0, n = transformConstraints.Count; i < n; i++) {
-				TransformConstraint constraint = transformConstraintsItems[i];
-				TransformConstraintData constraintData = constraint.data;
-				constraint.rotateMix = constraintData.rotateMix;
-				constraint.translateMix = constraintData.translateMix;
-				constraint.scaleMix = constraintData.scaleMix;
-				constraint.shearMix = constraintData.shearMix;
-			}
-
-			var pathConstraintItems = this.pathConstraints.Items;
-			for (int i = 0, n = pathConstraints.Count; i < n; i++) {
-				PathConstraint constraint = pathConstraintItems[i];
-				PathConstraintData constraintData = constraint.data;
-				constraint.position = constraintData.position;
-				constraint.spacing = constraintData.spacing;
-				constraint.rotateMix = constraintData.rotateMix;
-				constraint.translateMix = constraintData.translateMix;
-			}
-		}
-
-		public void SetSlotsToSetupPose () {
-			var slots = this.slots;
-			var slotsItems = slots.Items;
-			drawOrder.Clear();
-			for (int i = 0, n = slots.Count; i < n; i++)
-				drawOrder.Add(slotsItems[i]);
-
-			for (int i = 0, n = slots.Count; i < n; i++)
-				slotsItems[i].SetToSetupPose();
-		}
-
-		/// <returns>May be null.</returns>
-		public Bone FindBone (string boneName) {
-			if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
-			var bones = this.bones;
-			var bonesItems = bones.Items;
-			for (int i = 0, n = bones.Count; i < n; i++) {
-				Bone bone = bonesItems[i];
-				if (bone.data.name == boneName) return bone;
-			}
-			return null;
-		}
-
-		/// <returns>-1 if the bone was not found.</returns>
-		public int FindBoneIndex (string boneName) {
-			if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
-			var bones = this.bones;
-			var bonesItems = bones.Items;
-			for (int i = 0, n = bones.Count; i < n; i++)
-				if (bonesItems[i].data.name == boneName) return i;
-			return -1;
-		}
-
-		/// <returns>May be null.</returns>
-		public Slot FindSlot (string slotName) {
-			if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
-			var slots = this.slots;
-			var slotsItems = slots.Items;
-			for (int i = 0, n = slots.Count; i < n; i++) {
-				Slot slot = slotsItems[i];
-				if (slot.data.name == slotName) return slot;
-			}
-			return null;
-		}
-
-		/// <returns>-1 if the bone was not found.</returns>
-		public int FindSlotIndex (string slotName) {
-			if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
-			var slots = this.slots;
-			var slotsItems = slots.Items;
-			for (int i = 0, n = slots.Count; i < n; i++)
-				if (slotsItems[i].data.name.Equals(slotName)) return i;
-			return -1;
-		}
-
-		/// <summary>Sets a skin by name (see SetSkin).</summary>
-		public void SetSkin (string skinName) {
-			Skin foundSkin = data.FindSkin(skinName);
-			if (foundSkin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName");
-			SetSkin(foundSkin);
-		}
-
-		/// <summary>
-		/// <para>Attachments from the new skin are attached if the corresponding attachment from the old skin was attached. 
-		/// If there was no old skin, each slot's setup mode attachment is attached from the new skin.</para>
-		/// <para>After changing the skin, the visible attachments can be reset to those attached in the setup pose by calling 
-		/// <see cref="Skeleton.SetSlotsToSetupPose()"/>. 
-		/// Also, often <see cref="AnimationState.Apply(Skeleton)"/> is called before the next time the 
-		/// skeleton is rendered to allow any attachment keys in the current animation(s) to hide or show attachments from the new skin.</para>
-		/// </summary>
-		/// <param name="newSkin">May be null.</param>
-		public void SetSkin (Skin newSkin) {
-			if (newSkin != null) {
-				if (skin != null)
-					newSkin.AttachAll(this, skin);
-				else {
-					ExposedList<Slot> slots = this.slots;
-					for (int i = 0, n = slots.Count; i < n; i++) {
-						Slot slot = slots.Items[i];
-						string name = slot.data.attachmentName;
-						if (name != null) {
-							Attachment attachment = newSkin.GetAttachment(i, name);
-							if (attachment != null) slot.Attachment = attachment;
-						}
-					}
-				}
-			}
-			skin = newSkin;
-		}
-
-		/// <summary>Finds an attachment by looking in the {@link #skin} and {@link SkeletonData#defaultSkin} using the slot name and attachment name.</summary>
-		/// <returns>May be null.</returns>
-		public Attachment GetAttachment (string slotName, string attachmentName) {
-			return GetAttachment(data.FindSlotIndex(slotName), attachmentName);
-		}
-
-		/// <summary>Finds an attachment by looking in the skin and skeletonData.defaultSkin using the slot index and attachment name.First the skin is checked and if the attachment was not found, the default skin is checked.</summary>
-		/// <returns>May be null.</returns>
-		public Attachment GetAttachment (int slotIndex, string attachmentName) {
-			if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null.");
-			if (skin != null) {
-				Attachment attachment = skin.GetAttachment(slotIndex, attachmentName);
-				if (attachment != null) return attachment;
-			}
-			return data.defaultSkin != null ? data.defaultSkin.GetAttachment(slotIndex, attachmentName) : null;
-		}
-
-		/// <summary>A convenience method to set an attachment by finding the slot with FindSlot, finding the attachment with GetAttachment, then setting the slot's slot.Attachment.</summary>
-		/// <param name="attachmentName">May be null.</param>
-		public void SetAttachment (string slotName, string attachmentName) {
-			if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
-			ExposedList<Slot> slots = this.slots;
-			for (int i = 0, n = slots.Count; i < n; i++) {
-				Slot slot = slots.Items[i];
-				if (slot.data.name == slotName) {
-					Attachment attachment = null;
-					if (attachmentName != null) {
-						attachment = GetAttachment(i, attachmentName);
-						if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName);
-					}
-					slot.Attachment = attachment;
-					return;
-				}
-			}
-			throw new Exception("Slot not found: " + slotName);
-		}
-			
-		/// <returns>May be null.</returns>
-		public IkConstraint FindIkConstraint (string constraintName) {
-			if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
-			ExposedList<IkConstraint> ikConstraints = this.ikConstraints;
-			for (int i = 0, n = ikConstraints.Count; i < n; i++) {
-				IkConstraint ikConstraint = ikConstraints.Items[i];
-				if (ikConstraint.data.name == constraintName) return ikConstraint;
-			}
-			return null;
-		}
-
-		/// <returns>May be null.</returns>
-		public TransformConstraint FindTransformConstraint (string constraintName) {
-			if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
-			ExposedList<TransformConstraint> transformConstraints = this.transformConstraints;
-			for (int i = 0, n = transformConstraints.Count; i < n; i++) {
-				TransformConstraint transformConstraint = transformConstraints.Items[i];
-				if (transformConstraint.data.name == constraintName) return transformConstraint;
-			}
-			return null;
-		}
-
-		/// <returns>May be null.</returns>
-		public PathConstraint FindPathConstraint (string constraintName) {
-			if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
-			ExposedList<PathConstraint> pathConstraints = this.pathConstraints;
-			for (int i = 0, n = pathConstraints.Count; i < n; i++) {
-				PathConstraint constraint = pathConstraints.Items[i];
-				if (constraint.data.name.Equals(constraintName)) return constraint;
-			}
-			return null;
-		}
-
-		public void Update (float delta) {
-			time += delta;
-		}
-
-		/// <summary>Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose.</summary>
-		/// <param name="x">The horizontal distance between the skeleton origin and the left side of the AABB.</param>
-		/// <param name="y">The vertical distance between the skeleton origin and the bottom side of the AABB.</param>
-		/// <param name="width">The width of the AABB</param>
-		/// <param name="height">The height of the AABB.</param>
-		/// <param name="vertexBuffer">Reference to hold a float[]. May be a null reference. This method will assign it a new float[] with the appropriate size as needed.</param>
-		public void GetBounds (out float x, out float y, out float width, out float height, ref float[] vertexBuffer) {
-			float[] temp = vertexBuffer;
-			temp = temp ?? new float[8];
-			var drawOrderItems = this.drawOrder.Items;
-			float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue;
-			for (int i = 0, n = this.drawOrder.Count; i < n; i++) {
-				Slot slot = drawOrderItems[i];
-				int verticesLength = 0;
-				float[] vertices = null;
-				Attachment attachment = slot.attachment;
-				var regionAttachment = attachment as RegionAttachment;
-				if (regionAttachment != null) {
-					verticesLength = 8;
-					vertices = temp;
-					if (vertices.Length < 8) vertices = temp = new float[8];
-					regionAttachment.ComputeWorldVertices(slot.bone, temp, 0);
-				} else {
-					var meshAttachment = attachment as MeshAttachment;
-					if (meshAttachment != null) {
-						MeshAttachment mesh = meshAttachment;
-						verticesLength = mesh.WorldVerticesLength;
-						vertices = temp;
-						if (vertices.Length < verticesLength) vertices = temp = new float[verticesLength];
-						mesh.ComputeWorldVertices(slot, 0, verticesLength, temp, 0);
-					}
-				}
-
-				if (vertices != null) {
-					for (int ii = 0; ii < verticesLength; ii += 2) {
-						float vx = vertices[ii], vy = vertices[ii + 1];
-						minX = Math.Min(minX, vx);
-						minY = Math.Min(minY, vy);
-						maxX = Math.Max(maxX, vx);
-						maxY = Math.Max(maxY, vy);
-					}
-				}
-			}
-			x = minX;
-			y = minY;
-			width = maxX - minX;
-			height = maxY - minY;
-			vertexBuffer = temp;
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+using System.Collections.Generic;
+
+namespace Spine {
+	public class Skeleton {
+		internal SkeletonData data;
+		internal ExposedList<Bone> bones;
+		internal ExposedList<Slot> slots;
+		internal ExposedList<Slot> drawOrder;
+		internal ExposedList<IkConstraint> ikConstraints;
+		internal ExposedList<TransformConstraint> transformConstraints;
+		internal ExposedList<PathConstraint> pathConstraints;
+		internal ExposedList<IUpdatable> updateCache = new ExposedList<IUpdatable>();
+		internal ExposedList<Bone> updateCacheReset = new ExposedList<Bone>();
+		internal Skin skin;
+		internal float r = 1, g = 1, b = 1, a = 1;
+		internal float time;
+		internal float scaleX = 1, scaleY = 1;
+		internal float x, y;
+
+		public SkeletonData Data { get { return data; } }
+		public ExposedList<Bone> Bones { get { return bones; } }
+		public ExposedList<IUpdatable> UpdateCacheList { get { return updateCache; } }
+		public ExposedList<Slot> Slots { get { return slots; } }
+		public ExposedList<Slot> DrawOrder { get { return drawOrder; } }
+		public ExposedList<IkConstraint> IkConstraints { get { return ikConstraints; } }
+		public ExposedList<PathConstraint> PathConstraints { get { return pathConstraints; } }
+		public ExposedList<TransformConstraint> TransformConstraints { get { return transformConstraints; } }
+		public Skin Skin { get { return skin; } set { skin = value; } }
+		public float R { get { return r; } set { r = value; } }
+		public float G { get { return g; } set { g = value; } }
+		public float B { get { return b; } set { b = value; } }
+		public float A { get { return a; } set { a = value; } }
+		public float Time { get { return time; } set { time = value; } }
+		public float X { get { return x; } set { x = value; } }
+		public float Y { get { return y; } set { y = value; } }
+		public float ScaleX { get { return scaleX; } set { scaleX = value; } }
+		public float ScaleY { get { return scaleY; } set { scaleY = value; } }
+
+		[Obsolete("Use ScaleX instead. FlipX is when ScaleX is negative.")]
+		public bool FlipX { get { return scaleX < 0; } set { scaleX = value ? -1f : 1f; } }
+
+		[Obsolete("Use ScaleY instead. FlipY is when ScaleY is negative.")]
+		public bool FlipY { get { return scaleY < 0; } set { scaleY = value ? -1f : 1f; } }
+
+		public Bone RootBone {
+			get { return bones.Count == 0 ? null : bones.Items[0]; }
+		}
+
+		public Skeleton (SkeletonData data) {
+			if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
+			this.data = data;
+
+			bones = new ExposedList<Bone>(data.bones.Count);
+			foreach (BoneData boneData in data.bones) {
+				Bone bone;
+				if (boneData.parent == null) {
+					bone = new Bone(boneData, this, null);				
+				} else {
+					Bone parent = bones.Items[boneData.parent.index];
+					bone = new Bone(boneData, this, parent);
+					parent.children.Add(bone);
+				}
+				bones.Add(bone);
+			}
+
+			slots = new ExposedList<Slot>(data.slots.Count);
+			drawOrder = new ExposedList<Slot>(data.slots.Count);
+			foreach (SlotData slotData in data.slots) {
+				Bone bone = bones.Items[slotData.boneData.index];
+				Slot slot = new Slot(slotData, bone);
+				slots.Add(slot);
+				drawOrder.Add(slot);
+			}
+
+			ikConstraints = new ExposedList<IkConstraint>(data.ikConstraints.Count);
+			foreach (IkConstraintData ikConstraintData in data.ikConstraints)
+				ikConstraints.Add(new IkConstraint(ikConstraintData, this));
+
+			transformConstraints = new ExposedList<TransformConstraint>(data.transformConstraints.Count);
+			foreach (TransformConstraintData transformConstraintData in data.transformConstraints)
+				transformConstraints.Add(new TransformConstraint(transformConstraintData, this));
+
+			pathConstraints = new ExposedList<PathConstraint> (data.pathConstraints.Count);
+			foreach (PathConstraintData pathConstraintData in data.pathConstraints)
+				pathConstraints.Add(new PathConstraint(pathConstraintData, this));
+
+			UpdateCache();
+			UpdateWorldTransform();
+		}
+
+		/// <summary>Caches information about bones and constraints. Must be called if bones, constraints or weighted path attachments are added
+		/// or removed.</summary>
+		public void UpdateCache () {
+			var updateCache = this.updateCache;
+			updateCache.Clear();
+			this.updateCacheReset.Clear();
+
+			var bones = this.bones;
+			for (int i = 0, n = bones.Count; i < n; i++)
+				bones.Items[i].sorted = false;
+
+			var ikConstraints = this.ikConstraints;
+			var transformConstraints = this.transformConstraints;
+			var pathConstraints = this.pathConstraints;
+			int ikCount = ikConstraints.Count, transformCount = transformConstraints.Count, pathCount = pathConstraints.Count;
+			int constraintCount = ikCount + transformCount + pathCount;
+			//outer:
+			for (int i = 0; i < constraintCount; i++) {
+				for (int ii = 0; ii < ikCount; ii++) {
+					IkConstraint constraint = ikConstraints.Items[ii];
+					if (constraint.data.order == i) {
+						SortIkConstraint(constraint);
+						goto continue_outer; //continue outer;
+					}
+				}
+				for (int ii = 0; ii < transformCount; ii++) {
+					TransformConstraint constraint = transformConstraints.Items[ii];
+					if (constraint.data.order == i) {
+						SortTransformConstraint(constraint);
+						goto continue_outer; //continue outer;
+					}
+				}
+				for (int ii = 0; ii < pathCount; ii++) {
+					PathConstraint constraint = pathConstraints.Items[ii];
+					if (constraint.data.order == i) {
+						SortPathConstraint(constraint);
+						goto continue_outer; //continue outer;
+					}
+				}
+				continue_outer: {}
+			}
+
+			for (int i = 0, n = bones.Count; i < n; i++)
+				SortBone(bones.Items[i]);
+		}
+
+		private void SortIkConstraint (IkConstraint constraint) {
+			Bone target = constraint.target;
+			SortBone(target);
+
+			var constrained = constraint.bones;
+			Bone parent = constrained.Items[0];
+			SortBone(parent);
+
+			if (constrained.Count > 1) {
+				Bone child = constrained.Items[constrained.Count - 1];
+				if (!updateCache.Contains(child))
+					updateCacheReset.Add(child);
+			}
+
+			updateCache.Add(constraint);
+
+			SortReset(parent.children);
+			constrained.Items[constrained.Count - 1].sorted = true;
+		}
+
+		private void SortPathConstraint (PathConstraint constraint) {
+			Slot slot = constraint.target;
+			int slotIndex = slot.data.index;
+			Bone slotBone = slot.bone;
+			if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone);
+			if (data.defaultSkin != null && data.defaultSkin != skin)
+				SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone);
+			for (int ii = 0, nn = data.skins.Count; ii < nn; ii++)
+				SortPathConstraintAttachment(data.skins.Items[ii], slotIndex, slotBone);
+
+			Attachment attachment = slot.attachment;
+			if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone);
+
+			var constrained = constraint.bones;
+			int boneCount = constrained.Count;
+			for (int i = 0; i < boneCount; i++)
+				SortBone(constrained.Items[i]);
+
+			updateCache.Add(constraint);
+
+			for (int i = 0; i < boneCount; i++)
+				SortReset(constrained.Items[i].children);
+			for (int i = 0; i < boneCount; i++)
+				constrained.Items[i].sorted = true;
+		}
+
+		private void SortTransformConstraint (TransformConstraint constraint) {
+			SortBone(constraint.target);
+
+			var constrained = constraint.bones;
+			int boneCount = constrained.Count;
+			if (constraint.data.local) {
+				for (int i = 0; i < boneCount; i++) {
+					Bone child = constrained.Items[i];
+					SortBone(child.parent);
+					if (!updateCache.Contains(child)) updateCacheReset.Add(child);
+				}
+			} else {
+				for (int i = 0; i < boneCount; i++)
+					SortBone(constrained.Items[i]);
+			}
+
+			updateCache.Add(constraint);
+
+			for (int i = 0; i < boneCount; i++)
+				SortReset(constrained.Items[i].children);
+			for (int i = 0; i < boneCount; i++)
+				constrained.Items[i].sorted = true;
+		}
+
+		private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) {
+			foreach (var entry in skin.Attachments)
+				if (entry.Key.slotIndex == slotIndex) SortPathConstraintAttachment(entry.Value, slotBone);
+		}
+
+		private void SortPathConstraintAttachment (Attachment attachment, Bone slotBone) {
+			if (!(attachment is PathAttachment)) return;
+			int[] pathBones = ((PathAttachment)attachment).bones;
+			if (pathBones == null)
+				SortBone(slotBone);
+			else {
+				var bones = this.bones;
+				for (int i = 0, n = pathBones.Length; i < n;) {
+					int nn = pathBones[i++];
+					nn += i;
+					while (i < nn)
+						SortBone(bones.Items[pathBones[i++]]);
+				}
+			}
+		}
+
+		private void SortBone (Bone bone) {
+			if (bone.sorted) return;
+			Bone parent = bone.parent;
+			if (parent != null) SortBone(parent);
+			bone.sorted = true;
+			updateCache.Add(bone);
+		}
+
+		private static void SortReset (ExposedList<Bone> bones) {
+			var bonesItems = bones.Items;
+			for (int i = 0, n = bones.Count; i < n; i++) {
+				Bone bone = bonesItems[i];
+				if (bone.sorted) SortReset(bone.children);
+				bone.sorted = false;
+			}
+		}
+
+		/// <summary>Updates the world transform for each bone and applies constraints.</summary>
+		public void UpdateWorldTransform () {
+			var updateCacheReset = this.updateCacheReset;
+			var updateCacheResetItems = updateCacheReset.Items;
+			for (int i = 0, n = updateCacheReset.Count; i < n; i++) {
+				Bone bone = updateCacheResetItems[i];
+				bone.ax = bone.x;
+				bone.ay = bone.y;
+				bone.arotation = bone.rotation;
+				bone.ascaleX = bone.scaleX;
+				bone.ascaleY = bone.scaleY;
+				bone.ashearX = bone.shearX;
+				bone.ashearY = bone.shearY;
+				bone.appliedValid = true;
+			}
+			var updateItems = this.updateCache.Items;
+			for (int i = 0, n = updateCache.Count; i < n; i++)
+				updateItems[i].Update();
+		}
+
+		/// <summary>
+		/// Updates the world transform for each bone and applies all constraints. The root bone will be temporarily parented to the specified bone.
+	 	/// </summary>
+		public void UpdateWorldTransform (Bone parent) {
+			// This partial update avoids computing the world transform for constrained bones when 1) the bone is not updated
+			// before the constraint, 2) the constraint only needs to access the applied local transform, and 3) the constraint calls
+			// updateWorldTransform.
+			var updateCacheReset = this.updateCacheReset;
+			var updateCacheResetItems = updateCacheReset.Items;
+			for (int i = 0, n = updateCacheReset.Count; i < n; i++) {
+				Bone bone = updateCacheResetItems[i];
+				bone.ax = bone.x;
+				bone.ay = bone.y;
+				bone.arotation = bone.rotation;
+				bone.ascaleX = bone.scaleX;
+				bone.ascaleY = bone.scaleY;
+				bone.ashearX = bone.shearX;
+				bone.ashearY = bone.shearY;
+				bone.appliedValid = true;
+			}
+
+			// Apply the parent bone transform to the root bone. The root bone
+			// always inherits scale, rotation and reflection.
+			Bone rootBone = this.RootBone;
+			float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
+			rootBone.worldX = pa * x + pb * y + parent.worldX;
+			rootBone.worldY = pc * x + pd * y + parent.worldY;
+
+			float rotationY = rootBone.rotation + 90 + rootBone.shearY;
+			float la = MathUtils.CosDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX;
+			float lb = MathUtils.CosDeg(rotationY) * rootBone.scaleY;
+			float lc = MathUtils.SinDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX;
+			float ld = MathUtils.SinDeg(rotationY) * rootBone.scaleY;
+			rootBone.a = (pa * la + pb * lc) * scaleX;
+			rootBone.b = (pa * lb + pb * ld) * scaleX;
+			rootBone.c = (pc * la + pd * lc) * scaleY;
+			rootBone.d = (pc * lb + pd * ld) * scaleY;
+
+			// Update everything except root bone.
+			var updateCache = this.updateCache;
+			var updateCacheItems = updateCache.Items;
+			for (int i = 0, n = updateCache.Count; i < n; i++) {
+				var updatable = updateCacheItems[i];
+				if (updatable != rootBone)
+					updatable.Update();
+			}
+		}
+
+		/// <summary>Sets the bones, constraints, and slots to their setup pose values.</summary>
+		public void SetToSetupPose () {
+			SetBonesToSetupPose();
+			SetSlotsToSetupPose();
+		}
+
+		/// <summary>Sets the bones and constraints to their setup pose values.</summary>
+		public void SetBonesToSetupPose () {
+			var bonesItems = this.bones.Items;
+			for (int i = 0, n = bones.Count; i < n; i++)
+				bonesItems[i].SetToSetupPose();
+
+			var ikConstraintsItems = this.ikConstraints.Items;
+			for (int i = 0, n = ikConstraints.Count; i < n; i++) {
+				IkConstraint constraint = ikConstraintsItems[i];
+				constraint.mix = constraint.data.mix;
+				constraint.bendDirection = constraint.data.bendDirection;
+				constraint.compress = constraint.data.compress;
+				constraint.stretch = constraint.data.stretch;
+			}
+
+			var transformConstraintsItems = this.transformConstraints.Items;
+			for (int i = 0, n = transformConstraints.Count; i < n; i++) {
+				TransformConstraint constraint = transformConstraintsItems[i];
+				TransformConstraintData constraintData = constraint.data;
+				constraint.rotateMix = constraintData.rotateMix;
+				constraint.translateMix = constraintData.translateMix;
+				constraint.scaleMix = constraintData.scaleMix;
+				constraint.shearMix = constraintData.shearMix;
+			}
+
+			var pathConstraintItems = this.pathConstraints.Items;
+			for (int i = 0, n = pathConstraints.Count; i < n; i++) {
+				PathConstraint constraint = pathConstraintItems[i];
+				PathConstraintData constraintData = constraint.data;
+				constraint.position = constraintData.position;
+				constraint.spacing = constraintData.spacing;
+				constraint.rotateMix = constraintData.rotateMix;
+				constraint.translateMix = constraintData.translateMix;
+			}
+		}
+
+		public void SetSlotsToSetupPose () {
+			var slots = this.slots;
+			var slotsItems = slots.Items;
+			drawOrder.Clear();
+			for (int i = 0, n = slots.Count; i < n; i++)
+				drawOrder.Add(slotsItems[i]);
+
+			for (int i = 0, n = slots.Count; i < n; i++)
+				slotsItems[i].SetToSetupPose();
+		}
+
+		/// <returns>May be null.</returns>
+		public Bone FindBone (string boneName) {
+			if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
+			var bones = this.bones;
+			var bonesItems = bones.Items;
+			for (int i = 0, n = bones.Count; i < n; i++) {
+				Bone bone = bonesItems[i];
+				if (bone.data.name == boneName) return bone;
+			}
+			return null;
+		}
+
+		/// <returns>-1 if the bone was not found.</returns>
+		public int FindBoneIndex (string boneName) {
+			if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
+			var bones = this.bones;
+			var bonesItems = bones.Items;
+			for (int i = 0, n = bones.Count; i < n; i++)
+				if (bonesItems[i].data.name == boneName) return i;
+			return -1;
+		}
+
+		/// <returns>May be null.</returns>
+		public Slot FindSlot (string slotName) {
+			if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
+			var slots = this.slots;
+			var slotsItems = slots.Items;
+			for (int i = 0, n = slots.Count; i < n; i++) {
+				Slot slot = slotsItems[i];
+				if (slot.data.name == slotName) return slot;
+			}
+			return null;
+		}
+
+		/// <returns>-1 if the bone was not found.</returns>
+		public int FindSlotIndex (string slotName) {
+			if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
+			var slots = this.slots;
+			var slotsItems = slots.Items;
+			for (int i = 0, n = slots.Count; i < n; i++)
+				if (slotsItems[i].data.name.Equals(slotName)) return i;
+			return -1;
+		}
+
+		/// <summary>Sets a skin by name (see SetSkin).</summary>
+		public void SetSkin (string skinName) {
+			Skin foundSkin = data.FindSkin(skinName);
+			if (foundSkin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName");
+			SetSkin(foundSkin);
+		}
+
+		/// <summary>
+		/// <para>Attachments from the new skin are attached if the corresponding attachment from the old skin was attached. 
+		/// If there was no old skin, each slot's setup mode attachment is attached from the new skin.</para>
+		/// <para>After changing the skin, the visible attachments can be reset to those attached in the setup pose by calling 
+		/// <see cref="Skeleton.SetSlotsToSetupPose()"/>. 
+		/// Also, often <see cref="AnimationState.Apply(Skeleton)"/> is called before the next time the 
+		/// skeleton is rendered to allow any attachment keys in the current animation(s) to hide or show attachments from the new skin.</para>
+		/// </summary>
+		/// <param name="newSkin">May be null.</param>
+		public void SetSkin (Skin newSkin) {
+			if (newSkin != null) {
+				if (skin != null)
+					newSkin.AttachAll(this, skin);
+				else {
+					ExposedList<Slot> slots = this.slots;
+					for (int i = 0, n = slots.Count; i < n; i++) {
+						Slot slot = slots.Items[i];
+						string name = slot.data.attachmentName;
+						if (name != null) {
+							Attachment attachment = newSkin.GetAttachment(i, name);
+							if (attachment != null) slot.Attachment = attachment;
+						}
+					}
+				}
+			}
+			skin = newSkin;
+		}
+
+		/// <summary>Finds an attachment by looking in the {@link #skin} and {@link SkeletonData#defaultSkin} using the slot name and attachment name.</summary>
+		/// <returns>May be null.</returns>
+		public Attachment GetAttachment (string slotName, string attachmentName) {
+			return GetAttachment(data.FindSlotIndex(slotName), attachmentName);
+		}
+
+		/// <summary>Finds an attachment by looking in the skin and skeletonData.defaultSkin using the slot index and attachment name.First the skin is checked and if the attachment was not found, the default skin is checked.</summary>
+		/// <returns>May be null.</returns>
+		public Attachment GetAttachment (int slotIndex, string attachmentName) {
+			if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null.");
+			if (skin != null) {
+				Attachment attachment = skin.GetAttachment(slotIndex, attachmentName);
+				if (attachment != null) return attachment;
+			}
+			return data.defaultSkin != null ? data.defaultSkin.GetAttachment(slotIndex, attachmentName) : null;
+		}
+
+		/// <summary>A convenience method to set an attachment by finding the slot with FindSlot, finding the attachment with GetAttachment, then setting the slot's slot.Attachment.</summary>
+		/// <param name="attachmentName">May be null.</param>
+		public void SetAttachment (string slotName, string attachmentName) {
+			if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
+			ExposedList<Slot> slots = this.slots;
+			for (int i = 0, n = slots.Count; i < n; i++) {
+				Slot slot = slots.Items[i];
+				if (slot.data.name == slotName) {
+					Attachment attachment = null;
+					if (attachmentName != null) {
+						attachment = GetAttachment(i, attachmentName);
+						if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName);
+					}
+					slot.Attachment = attachment;
+					return;
+				}
+			}
+			throw new Exception("Slot not found: " + slotName);
+		}
+			
+		/// <returns>May be null.</returns>
+		public IkConstraint FindIkConstraint (string constraintName) {
+			if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
+			ExposedList<IkConstraint> ikConstraints = this.ikConstraints;
+			for (int i = 0, n = ikConstraints.Count; i < n; i++) {
+				IkConstraint ikConstraint = ikConstraints.Items[i];
+				if (ikConstraint.data.name == constraintName) return ikConstraint;
+			}
+			return null;
+		}
+
+		/// <returns>May be null.</returns>
+		public TransformConstraint FindTransformConstraint (string constraintName) {
+			if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
+			ExposedList<TransformConstraint> transformConstraints = this.transformConstraints;
+			for (int i = 0, n = transformConstraints.Count; i < n; i++) {
+				TransformConstraint transformConstraint = transformConstraints.Items[i];
+				if (transformConstraint.data.name == constraintName) return transformConstraint;
+			}
+			return null;
+		}
+
+		/// <returns>May be null.</returns>
+		public PathConstraint FindPathConstraint (string constraintName) {
+			if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
+			ExposedList<PathConstraint> pathConstraints = this.pathConstraints;
+			for (int i = 0, n = pathConstraints.Count; i < n; i++) {
+				PathConstraint constraint = pathConstraints.Items[i];
+				if (constraint.data.name.Equals(constraintName)) return constraint;
+			}
+			return null;
+		}
+
+		public void Update (float delta) {
+			time += delta;
+		}
+
+		/// <summary>Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose.</summary>
+		/// <param name="x">The horizontal distance between the skeleton origin and the left side of the AABB.</param>
+		/// <param name="y">The vertical distance between the skeleton origin and the bottom side of the AABB.</param>
+		/// <param name="width">The width of the AABB</param>
+		/// <param name="height">The height of the AABB.</param>
+		/// <param name="vertexBuffer">Reference to hold a float[]. May be a null reference. This method will assign it a new float[] with the appropriate size as needed.</param>
+		public void GetBounds (out float x, out float y, out float width, out float height, ref float[] vertexBuffer) {
+			float[] temp = vertexBuffer;
+			temp = temp ?? new float[8];
+			var drawOrderItems = this.drawOrder.Items;
+			float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue;
+			for (int i = 0, n = this.drawOrder.Count; i < n; i++) {
+				Slot slot = drawOrderItems[i];
+				int verticesLength = 0;
+				float[] vertices = null;
+				Attachment attachment = slot.attachment;
+				var regionAttachment = attachment as RegionAttachment;
+				if (regionAttachment != null) {
+					verticesLength = 8;
+					vertices = temp;
+					if (vertices.Length < 8) vertices = temp = new float[8];
+					regionAttachment.ComputeWorldVertices(slot.bone, temp, 0);
+				} else {
+					var meshAttachment = attachment as MeshAttachment;
+					if (meshAttachment != null) {
+						MeshAttachment mesh = meshAttachment;
+						verticesLength = mesh.WorldVerticesLength;
+						vertices = temp;
+						if (vertices.Length < verticesLength) vertices = temp = new float[verticesLength];
+						mesh.ComputeWorldVertices(slot, 0, verticesLength, temp, 0);
+					}
+				}
+
+				if (vertices != null) {
+					for (int ii = 0; ii < verticesLength; ii += 2) {
+						float vx = vertices[ii], vy = vertices[ii + 1];
+						minX = Math.Min(minX, vx);
+						minY = Math.Min(minY, vy);
+						maxX = Math.Max(maxX, vx);
+						maxY = Math.Max(maxY, vy);
+					}
+				}
+			}
+			x = minX;
+			y = minY;
+			width = maxX - minX;
+			height = maxY - minY;
+			vertexBuffer = temp;
+		}
+	}
+}

+ 911 - 911
spine-csharp/src/SkeletonBinary.cs

@@ -1,911 +1,911 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-#if (UNITY_5 || UNITY_5_3_OR_NEWER || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1)
-#define IS_UNITY
-#endif
-
-using System;
-using System.IO;
-using System.Collections.Generic;
-
-#if WINDOWS_STOREAPP
-using System.Threading.Tasks;
-using Windows.Storage;
-#endif
-
-namespace Spine {
-	public class SkeletonBinary {
-		public const int BONE_ROTATE = 0;
-		public const int BONE_TRANSLATE = 1;
-		public const int BONE_SCALE = 2;
-		public const int BONE_SHEAR = 3;
-
-		public const int SLOT_ATTACHMENT = 0;
-		public const int SLOT_COLOR = 1;
-		public const int SLOT_TWO_COLOR = 2;
-
-		public const int PATH_POSITION = 0;
-		public const int PATH_SPACING = 1;
-		public const int PATH_MIX = 2;
-
-		public const int CURVE_LINEAR = 0;
-		public const int CURVE_STEPPED = 1;
-		public const int CURVE_BEZIER = 2;
-
-		public float Scale { get; set; }
-
-		private AttachmentLoader attachmentLoader;
-		private byte[] buffer = new byte[32];
-		private List<SkeletonJson.LinkedMesh> linkedMeshes = new List<SkeletonJson.LinkedMesh>();
-
-		public SkeletonBinary (params Atlas[] atlasArray)
-			: this(new AtlasAttachmentLoader(atlasArray)) {
-		}
-
-		public SkeletonBinary (AttachmentLoader attachmentLoader) {
-			if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader");
-			this.attachmentLoader = attachmentLoader;
-			Scale = 1;
-		}
-			
-		#if !ISUNITY && WINDOWS_STOREAPP
-		private async Task<SkeletonData> ReadFile(string path) {
-			var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
-			using (var input = new BufferedStream(await folder.GetFileAsync(path).AsTask().ConfigureAwait(false))) {
-				SkeletonData skeletonData = ReadSkeletonData(input);
-				skeletonData.Name = Path.GetFileNameWithoutExtension(path);
-				return skeletonData;
-			}
-		}
-
-		public SkeletonData ReadSkeletonData (String path) {
-			return this.ReadFile(path).Result;
-		}
-		#else
-		public SkeletonData ReadSkeletonData (String path) {
-		#if WINDOWS_PHONE
-			using (var input = new BufferedStream(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) {
-		#else
-			using (var input = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) {
-		#endif
-				SkeletonData skeletonData = ReadSkeletonData(input);
-				skeletonData.name = Path.GetFileNameWithoutExtension(path);
-				return skeletonData;
-			}
-		}
-		#endif // WINDOWS_STOREAPP
-
-		public static readonly TransformMode[] TransformModeValues = {
-			TransformMode.Normal,
-			TransformMode.OnlyTranslation,
-			TransformMode.NoRotationOrReflection,
-			TransformMode.NoScale,
-			TransformMode.NoScaleOrReflection
-		};
-
-		/// <summary>Returns the version string of binary skeleton data.</summary>
-		public static string GetVersionString (Stream input) {
-			if (input == null) throw new ArgumentNullException("input");
-
-			try {
-				// Hash.
-				int byteCount = ReadVarint(input, true);
-				if (byteCount > 1) input.Position += byteCount - 1;
-
-				// Version.
-				byteCount = ReadVarint(input, true);
-				if (byteCount > 1) {
-					byteCount--;
-					var buffer = new byte[byteCount];
-					ReadFully(input, buffer, 0, byteCount);
-					return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount);
-				}
-
-				throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.", "input");
-			} catch (Exception e) {
-				throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.\n" + e, "input");
-			}
-		}
-
-		public SkeletonData ReadSkeletonData (Stream input) {
-			if (input == null) throw new ArgumentNullException("input");
-			float scale = Scale;
-
-			var skeletonData = new SkeletonData();
-			skeletonData.hash = ReadString(input);
-			if (skeletonData.hash.Length == 0) skeletonData.hash = null;
-			skeletonData.version = ReadString(input);
-			if (skeletonData.version.Length == 0) skeletonData.version = null;
-			skeletonData.width = ReadFloat(input);
-			skeletonData.height = ReadFloat(input);
-
-			bool nonessential = ReadBoolean(input);
-
-			if (nonessential) {
-				skeletonData.fps = ReadFloat(input);
-
-				skeletonData.imagesPath = ReadString(input);
-				if (string.IsNullOrEmpty(skeletonData.imagesPath)) skeletonData.imagesPath = null;
-
-				skeletonData.audioPath = ReadString(input);
-				if (string.IsNullOrEmpty(skeletonData.audioPath)) skeletonData.audioPath = null;
-			}
-
-			// Bones.
-			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
-				String name = ReadString(input);
-				BoneData parent = i == 0 ? null : skeletonData.bones.Items[ReadVarint(input, true)];
-				BoneData data = new BoneData(i, name, parent);
-				data.rotation = ReadFloat(input);		
-				data.x = ReadFloat(input) * scale;
-				data.y = ReadFloat(input) * scale;
-				data.scaleX = ReadFloat(input);
-				data.scaleY = ReadFloat(input);
-				data.shearX = ReadFloat(input);
-				data.shearY = ReadFloat(input);
-				data.length = ReadFloat(input) * scale;
-				data.transformMode = TransformModeValues[ReadVarint(input, true)];
-				if (nonessential) ReadInt(input); // Skip bone color.
-				skeletonData.bones.Add(data);
-			}
-
-			// Slots.
-			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
-				String slotName = ReadString(input);
-				BoneData boneData = skeletonData.bones.Items[ReadVarint(input, true)];
-				SlotData slotData = new SlotData(i, slotName, boneData);
-				int color = ReadInt(input);
-				slotData.r = ((color & 0xff000000) >> 24) / 255f;
-				slotData.g = ((color & 0x00ff0000) >> 16) / 255f;
-				slotData.b = ((color & 0x0000ff00) >> 8) / 255f;
-				slotData.a = ((color & 0x000000ff)) / 255f;
-
-				int darkColor = ReadInt(input); // 0x00rrggbb
-				if (darkColor != -1) {
-					slotData.hasSecondColor = true;
-					slotData.r2 = ((darkColor & 0x00ff0000) >> 16) / 255f;
-					slotData.g2 = ((darkColor & 0x0000ff00) >> 8) / 255f;
-					slotData.b2 = ((darkColor & 0x000000ff)) / 255f;
-				}
-
-				slotData.attachmentName = ReadString(input);
-				slotData.blendMode = (BlendMode)ReadVarint(input, true);
-				skeletonData.slots.Add(slotData);
-			}
-
-			// IK constraints.
-			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
-				IkConstraintData data = new IkConstraintData(ReadString(input));
-				data.order = ReadVarint(input, true);
-				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++)
-					data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]);
-				data.target = skeletonData.bones.Items[ReadVarint(input, true)];
-				data.mix = ReadFloat(input);
-				data.bendDirection = ReadSByte(input);
-				data.compress = ReadBoolean(input);
-				data.stretch = ReadBoolean(input);
-				data.uniform = ReadBoolean(input);
-				skeletonData.ikConstraints.Add(data);
-			}
-
-			// Transform constraints.
-			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
-				TransformConstraintData data = new TransformConstraintData(ReadString(input));
-				data.order = ReadVarint(input, true);
-				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++)
-				    data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]);
-				data.target = skeletonData.bones.Items[ReadVarint(input, true)];
-				data.local = ReadBoolean(input);
-				data.relative = ReadBoolean(input);
-				data.offsetRotation = ReadFloat(input);
-				data.offsetX = ReadFloat(input) * scale;
-				data.offsetY = ReadFloat(input) * scale;
-				data.offsetScaleX = ReadFloat(input);
-				data.offsetScaleY = ReadFloat(input);
-				data.offsetShearY = ReadFloat(input);
-				data.rotateMix = ReadFloat(input);
-				data.translateMix = ReadFloat(input);
-				data.scaleMix = ReadFloat(input);
-				data.shearMix = ReadFloat(input);
-				skeletonData.transformConstraints.Add(data);
-			}
-
-			// Path constraints
-			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
-				PathConstraintData data = new PathConstraintData(ReadString(input));
-				data.order = ReadVarint(input, true);
-				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++)
-					data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]);
-				data.target = skeletonData.slots.Items[ReadVarint(input, true)];
-				data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(ReadVarint(input, true));
-				data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue(ReadVarint(input, true));
-				data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(ReadVarint(input, true));
-				data.offsetRotation = ReadFloat(input);
-				data.position = ReadFloat(input);
-				if (data.positionMode == PositionMode.Fixed) data.position *= scale;
-				data.spacing = ReadFloat(input);
-				if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale;
-				data.rotateMix = ReadFloat(input);
-				data.translateMix = ReadFloat(input);
-				skeletonData.pathConstraints.Add(data);
-			}
-
-			// Default skin.
-			Skin defaultSkin = ReadSkin(input, skeletonData, "default", nonessential);
-			if (defaultSkin != null) {
-				skeletonData.defaultSkin = defaultSkin;
-				skeletonData.skins.Add(defaultSkin);
-			}
-
-			// Skins.
-			for (int i = 0, n = ReadVarint(input, true); i < n; i++)
-				skeletonData.skins.Add(ReadSkin(input, skeletonData, ReadString(input), nonessential));
-
-			// Linked meshes.
-			for (int i = 0, n = linkedMeshes.Count; i < n; i++) {
-				SkeletonJson.LinkedMesh linkedMesh = linkedMeshes[i];
-				Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin);
-				if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin);
-				Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent);
-				if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent);
-				linkedMesh.mesh.ParentMesh = (MeshAttachment)parent;
-				linkedMesh.mesh.UpdateUVs();
-			}
-			linkedMeshes.Clear();
-
-			// Events.
-			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
-				EventData data = new EventData(ReadString(input));
-				data.Int = ReadVarint(input, false);
-				data.Float = ReadFloat(input);
-				data.String = ReadString(input);
-				data.AudioPath = ReadString(input);
-				if (data.AudioPath != null) {
-					data.Volume = ReadFloat(input);
-					data.Balance = ReadFloat(input);
-				}
-				skeletonData.events.Add(data);
-			}
-
-			// Animations.
-			for (int i = 0, n = ReadVarint(input, true); i < n; i++)
-				ReadAnimation(ReadString(input), input, skeletonData);
-
-			skeletonData.bones.TrimExcess();
-			skeletonData.slots.TrimExcess();
-			skeletonData.skins.TrimExcess();
-			skeletonData.events.TrimExcess();
-			skeletonData.animations.TrimExcess();
-			skeletonData.ikConstraints.TrimExcess();
-			skeletonData.pathConstraints.TrimExcess();
-			return skeletonData;
-		}
-
-
-		/// <returns>May be null.</returns>
-		private Skin ReadSkin (Stream input, SkeletonData skeletonData, String skinName, bool nonessential) {
-			int slotCount = ReadVarint(input, true);
-			if (slotCount == 0) return null;
-			Skin skin = new Skin(skinName);
-			for (int i = 0; i < slotCount; i++) {
-				int slotIndex = ReadVarint(input, true);
-				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) {
-					String name = ReadString(input);
-					Attachment attachment = ReadAttachment(input, skeletonData, skin, slotIndex, name, nonessential);
-					if (attachment != null) skin.AddAttachment(slotIndex, name, attachment);
-				}
-			}
-			return skin;
-		}
-
-		private Attachment ReadAttachment (Stream input, SkeletonData skeletonData, Skin skin, int slotIndex, String attachmentName, bool nonessential) {
-			float scale = Scale;
-
-			String name = ReadString(input);
-			if (name == null) name = attachmentName;
-
-			AttachmentType type = (AttachmentType)input.ReadByte();
-			switch (type) {
-			case AttachmentType.Region: {
-					String path = ReadString(input);
-					float rotation = ReadFloat(input);		
-					float x = ReadFloat(input);
-					float y = ReadFloat(input);
-					float scaleX = ReadFloat(input);
-					float scaleY = ReadFloat(input);
-					float width = ReadFloat(input);
-					float height = ReadFloat(input);
-					int color = ReadInt(input);
-
-					if (path == null) path = name;
-					RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path);
-					if (region == null) return null;
-					region.Path = path;
-					region.x = x * scale;
-					region.y = y * scale;
-					region.scaleX = scaleX;
-					region.scaleY = scaleY;
-					region.rotation = rotation;
-					region.width = width * scale;
-					region.height = height * scale;
-					region.r = ((color & 0xff000000) >> 24) / 255f;
-					region.g = ((color & 0x00ff0000) >> 16) / 255f;
-					region.b = ((color & 0x0000ff00) >> 8) / 255f;
-					region.a = ((color & 0x000000ff)) / 255f;
-					region.UpdateOffset();
-					return region;
-				}
-			case AttachmentType.Boundingbox: {
-					int vertexCount = ReadVarint(input, true);
-					Vertices vertices = ReadVertices(input, vertexCount);
-					if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; // Avoid unused local warning.
-					
-					BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name);
-					if (box == null) return null;
-					box.worldVerticesLength = vertexCount << 1;
-					box.vertices = vertices.vertices;
-					box.bones = vertices.bones;                    
-					return box;
-				}
-			case AttachmentType.Mesh: {
-					String path = ReadString(input);
-					int color = ReadInt(input);
-					int vertexCount = ReadVarint(input, true);					
-					float[] uvs = ReadFloatArray(input, vertexCount << 1, 1);
-					int[] triangles = ReadShortArray(input);
-					Vertices vertices = ReadVertices(input, vertexCount);
-					int hullLength = ReadVarint(input, true);
-					int[] edges = null;
-					float width = 0, height = 0;
-					if (nonessential) {
-						edges = ReadShortArray(input);
-						width = ReadFloat(input);
-						height = ReadFloat(input);
-					}
-
-					if (path == null) path = name;
-					MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path);
-					if (mesh == null) return null;
-					mesh.Path = path;
-					mesh.r = ((color & 0xff000000) >> 24) / 255f;
-					mesh.g = ((color & 0x00ff0000) >> 16) / 255f;
-					mesh.b = ((color & 0x0000ff00) >> 8) / 255f;
-					mesh.a = ((color & 0x000000ff)) / 255f;
-					mesh.bones = vertices.bones;
-					mesh.vertices = vertices.vertices;
-					mesh.WorldVerticesLength = vertexCount << 1;
-					mesh.triangles = triangles;
-					mesh.regionUVs = uvs;
-					mesh.UpdateUVs();
-					mesh.HullLength = hullLength << 1;
-					if (nonessential) {
-						mesh.Edges = edges;
-						mesh.Width = width * scale;
-						mesh.Height = height * scale;
-					}
-					return mesh;
-				}
-			case AttachmentType.Linkedmesh: {
-					String path = ReadString(input);
-					int color = ReadInt(input);
-					String skinName = ReadString(input);
-					String parent = ReadString(input);
-					bool inheritDeform = ReadBoolean(input);
-					float width = 0, height = 0;
-					if (nonessential) {
-						width = ReadFloat(input);
-						height = ReadFloat(input);
-					}
-
-					if (path == null) path = name;
-					MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path);
-					if (mesh == null) return null;
-					mesh.Path = path;
-					mesh.r = ((color & 0xff000000) >> 24) / 255f;
-					mesh.g = ((color & 0x00ff0000) >> 16) / 255f;
-					mesh.b = ((color & 0x0000ff00) >> 8) / 255f;
-					mesh.a = ((color & 0x000000ff)) / 255f;
-					mesh.inheritDeform = inheritDeform;
-					if (nonessential) {
-						mesh.Width = width * scale;
-						mesh.Height = height * scale;
-					}
-					linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent));
-					return mesh;
-				}
-			case AttachmentType.Path: {
-					bool closed = ReadBoolean(input);
-					bool constantSpeed = ReadBoolean(input);
-					int vertexCount = ReadVarint(input, true);
-					Vertices vertices = ReadVertices(input, vertexCount);
-					float[] lengths = new float[vertexCount / 3];
-					for (int i = 0, n = lengths.Length; i < n; i++)
-						lengths[i] = ReadFloat(input) * scale;
-					if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0;
-
-					PathAttachment path = attachmentLoader.NewPathAttachment(skin, name);
-					if (path == null) return null;
-					path.closed = closed;
-					path.constantSpeed = constantSpeed;
-					path.worldVerticesLength = vertexCount << 1;
-					path.vertices = vertices.vertices;
-					path.bones = vertices.bones;
-					path.lengths = lengths;
-					return path;                    
-				}
-			case AttachmentType.Point: {
-					float rotation = ReadFloat(input);
-					float x = ReadFloat(input);
-					float y = ReadFloat(input);
-					if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0;
-
-					PointAttachment point = attachmentLoader.NewPointAttachment(skin, name);
-					if (point == null) return null;
-					point.x = x * scale;
-					point.y = y * scale;
-					point.rotation = rotation;
-					//if (nonessential) point.color = color;
-					return point;
-				}
-			case AttachmentType.Clipping: {
-					int endSlotIndex = ReadVarint(input, true);
-					int vertexCount = ReadVarint(input, true);
-					Vertices vertices = ReadVertices(input, vertexCount);
-					if (nonessential) ReadInt(input);
-
-					ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name);
-					if (clip == null) return null;
-					clip.EndSlot = skeletonData.slots.Items[endSlotIndex];
-					clip.worldVerticesLength = vertexCount << 1;
-					clip.vertices = vertices.vertices;
-					clip.bones = vertices.bones;
-					return clip;
-				}
-			}
-			return null;
-		}
-
-		private Vertices ReadVertices (Stream input, int vertexCount) {
-			float scale = Scale;
-			int verticesLength = vertexCount << 1;
-			Vertices vertices = new Vertices();
-			if(!ReadBoolean(input)) {
-				vertices.vertices = ReadFloatArray(input, verticesLength, scale);
-				return vertices;
-			}
-			var weights = new ExposedList<float>(verticesLength * 3 * 3);
-			var bonesArray = new ExposedList<int>(verticesLength * 3);
-			for (int i = 0; i < vertexCount; i++) {
-				int boneCount = ReadVarint(input, true);
-				bonesArray.Add(boneCount);
-				for (int ii = 0; ii < boneCount; ii++) {
-					bonesArray.Add(ReadVarint(input, true));
-					weights.Add(ReadFloat(input) * scale);
-					weights.Add(ReadFloat(input) * scale);
-					weights.Add(ReadFloat(input));
-				}
-			}
-
-			vertices.vertices = weights.ToArray();
-			vertices.bones = bonesArray.ToArray();
-			return vertices;
-		}
-
-		private float[] ReadFloatArray (Stream input, int n, float scale) {
-			float[] array = new float[n];
-			if (scale == 1) {
-				for (int i = 0; i < n; i++)
-					array[i] = ReadFloat(input);
-			} else {
-				for (int i = 0; i < n; i++)
-					array[i] = ReadFloat(input) * scale;
-			}
-			return array;
-		}
-
-		private int[] ReadShortArray (Stream input) {
-			int n = ReadVarint(input, true);
-			int[] array = new int[n];
-			for (int i = 0; i < n; i++) 
-				array[i] = (input.ReadByte() << 8) | input.ReadByte();
-			return array;
-		}
-
-		private void ReadAnimation (String name, Stream input, SkeletonData skeletonData) {
-			var timelines = new ExposedList<Timeline>();
-			float scale = Scale;
-			float duration = 0;
-
-			// Slot timelines.
-			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
-				int slotIndex = ReadVarint(input, true);
-				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) {
-					int timelineType = input.ReadByte();
-					int frameCount = ReadVarint(input, true);
-					switch (timelineType) {
-					case SLOT_ATTACHMENT: {
-							AttachmentTimeline timeline = new AttachmentTimeline(frameCount);
-							timeline.slotIndex = slotIndex;
-							for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
-								timeline.SetFrame(frameIndex, ReadFloat(input), ReadString(input));
-							timelines.Add(timeline);
-							duration = Math.Max(duration, timeline.frames[frameCount - 1]);
-							break;
-						}
-					case SLOT_COLOR: {
-							ColorTimeline timeline = new ColorTimeline(frameCount);
-							timeline.slotIndex = slotIndex;
-							for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
-								float time = ReadFloat(input);
-								int color = ReadInt(input);
-								float r = ((color & 0xff000000) >> 24) / 255f;
-								float g = ((color & 0x00ff0000) >> 16) / 255f;
-								float b = ((color & 0x0000ff00) >> 8) / 255f;
-								float a = ((color & 0x000000ff)) / 255f;
-								timeline.SetFrame(frameIndex, time, r, g, b, a);
-								if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
-							}
-							timelines.Add(timeline);
-							duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]);
-							break;
-						}
-					case SLOT_TWO_COLOR: {
-							TwoColorTimeline timeline = new TwoColorTimeline(frameCount);
-							timeline.slotIndex = slotIndex;
-							for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
-								float time = ReadFloat(input);
-								int color = ReadInt(input);
-								float r = ((color & 0xff000000) >> 24) / 255f;
-								float g = ((color & 0x00ff0000) >> 16) / 255f;
-								float b = ((color & 0x0000ff00) >> 8) / 255f;
-								float a = ((color & 0x000000ff)) / 255f;
-								int color2 = ReadInt(input); // 0x00rrggbb
-								float r2 = ((color2 & 0x00ff0000) >> 16) / 255f;
-								float g2 = ((color2 & 0x0000ff00) >> 8) / 255f;
-								float b2 = ((color2 & 0x000000ff)) / 255f;
-
-								timeline.SetFrame(frameIndex, time, r, g, b, a, r2, g2, b2);
-								if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
-							}
-							timelines.Add(timeline);
-							duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TwoColorTimeline.ENTRIES]);
-							break;
-						}
-					}
-				}
-			}
-
-			// Bone timelines.
-			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
-				int boneIndex = ReadVarint(input, true);
-				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) {
-					int timelineType = input.ReadByte();
-					int frameCount = ReadVarint(input, true);
-					switch (timelineType) {
-					case BONE_ROTATE: {
-							RotateTimeline timeline = new RotateTimeline(frameCount);
-							timeline.boneIndex = boneIndex;
-							for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
-								timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input));
-								if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
-							}
-							timelines.Add(timeline);
-							duration = Math.Max(duration, timeline.frames[(frameCount - 1) * RotateTimeline.ENTRIES]);
-							break;
-						}
-					case BONE_TRANSLATE:
-					case BONE_SCALE:
-					case BONE_SHEAR: {
-							TranslateTimeline timeline;
-							float timelineScale = 1;
-							if (timelineType == BONE_SCALE)
-								timeline = new ScaleTimeline(frameCount);
-							else if (timelineType == BONE_SHEAR)
-								timeline = new ShearTimeline(frameCount);
-							else {
-								timeline = new TranslateTimeline(frameCount);
-								timelineScale = scale;
-							}
-							timeline.boneIndex = boneIndex;
-							for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
-								timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale, ReadFloat(input)
-									* timelineScale);
-								if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
-							}
-							timelines.Add(timeline);
-							duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TranslateTimeline.ENTRIES]);
-							break;
-						}
-					}
-				}
-			}
-
-			// IK timelines.
-			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {				
-				int index = ReadVarint(input, true);
-				int frameCount = ReadVarint(input, true);
-				IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount) {
-					ikConstraintIndex = index
-				};
-				for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
-					timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input), ReadBoolean(input), ReadBoolean(input));
-					if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
-				}
-				timelines.Add(timeline);
-				duration = Math.Max(duration, timeline.frames[(frameCount - 1) * IkConstraintTimeline.ENTRIES]);
-			}
-
-			// Transform constraint timelines.
-			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
-				int index = ReadVarint(input, true);
-				int frameCount = ReadVarint(input, true);
-				TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount);
-				timeline.transformConstraintIndex = index;
-				for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
-					timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input));
-					if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
-				}
-				timelines.Add(timeline);
-				duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TransformConstraintTimeline.ENTRIES]);
-			}
-
-			// Path constraint timelines.
-			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
-				int index = ReadVarint(input, true);
-				PathConstraintData data = skeletonData.pathConstraints.Items[index];
-				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) {
-					int timelineType = ReadSByte(input);
-					int frameCount = ReadVarint(input, true);
-					switch(timelineType) {
-						case PATH_POSITION:
-						case PATH_SPACING: {
-								PathConstraintPositionTimeline timeline;
-								float timelineScale = 1;
-								if (timelineType == PATH_SPACING) {
-									timeline = new PathConstraintSpacingTimeline(frameCount);
-									if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; 
-								} else {
-									timeline = new PathConstraintPositionTimeline(frameCount);
-									if (data.positionMode == PositionMode.Fixed) timelineScale = scale;
-								}
-								timeline.pathConstraintIndex = index;
-								for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {                                    
-									timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale);
-									if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
-								}
-								timelines.Add(timeline);
-								duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintPositionTimeline.ENTRIES]);
-								break;
-							}
-						case PATH_MIX: {
-								PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount);
-								timeline.pathConstraintIndex = index;
-								for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
-									timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input));
-									if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
-								}
-								timelines.Add(timeline);
-								duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintMixTimeline.ENTRIES]);
-								break;
-							}
-					}
-				}
-			}
-
-			// Deform timelines.
-			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
-				Skin skin = skeletonData.skins.Items[ReadVarint(input, true)];
-				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) {
-					int slotIndex = ReadVarint(input, true);
-					for (int iii = 0, nnn = ReadVarint(input, true); iii < nnn; iii++) {
-						VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, ReadString(input));
-						bool weighted = attachment.bones != null;
-						float[] vertices = attachment.vertices;
-						int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length;
-
-						int frameCount = ReadVarint(input, true);
-						DeformTimeline timeline = new DeformTimeline(frameCount);
-						timeline.slotIndex = slotIndex;
-						timeline.attachment = attachment;
-
-						for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
-							float time = ReadFloat(input);
-							float[] deform;
-							int end = ReadVarint(input, true);
-							if (end == 0)
-								deform = weighted ? new float[deformLength] : vertices;
-							else {
-								deform = new float[deformLength];
-								int start = ReadVarint(input, true);
-								end += start;
-								if (scale == 1) {
-									for (int v = start; v < end; v++)
-										deform[v] = ReadFloat(input);
-								} else {
-									for (int v = start; v < end; v++)
-										deform[v] = ReadFloat(input) * scale;
-								}
-								if (!weighted) {
-									for (int v = 0, vn = deform.Length; v < vn; v++)
-										deform[v] += vertices[v];
-								}
-							}
-
-							timeline.SetFrame(frameIndex, time, deform);
-							if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
-						}							
-						timelines.Add(timeline);
-						duration = Math.Max(duration, timeline.frames[frameCount - 1]);
-					}
-				}
-			}
-
-			// Draw order timeline.
-			int drawOrderCount = ReadVarint(input, true);
-			if (drawOrderCount > 0) {
-				DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount);
-				int slotCount = skeletonData.slots.Count;
-				for (int i = 0; i < drawOrderCount; i++) {
-					float time = ReadFloat(input);
-					int offsetCount = ReadVarint(input, true);
-					int[] drawOrder = new int[slotCount];
-					for (int ii = slotCount - 1; ii >= 0; ii--)
-						drawOrder[ii] = -1;
-					int[] unchanged = new int[slotCount - offsetCount];
-					int originalIndex = 0, unchangedIndex = 0;
-					for (int ii = 0; ii < offsetCount; ii++) {
-						int slotIndex = ReadVarint(input, true);
-						// Collect unchanged items.
-						while (originalIndex != slotIndex)
-							unchanged[unchangedIndex++] = originalIndex++;
-						// Set changed items.
-						drawOrder[originalIndex + ReadVarint(input, true)] = originalIndex++;
-					}
-					// Collect remaining unchanged items.
-					while (originalIndex < slotCount)
-						unchanged[unchangedIndex++] = originalIndex++;
-					// Fill in unchanged items.
-					for (int ii = slotCount - 1; ii >= 0; ii--)
-						if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex];
-					timeline.SetFrame(i, time, drawOrder);
-				}
-				timelines.Add(timeline);
-				duration = Math.Max(duration, timeline.frames[drawOrderCount - 1]);
-			}
-
-			// Event timeline.
-			int eventCount = ReadVarint(input, true);
-			if (eventCount > 0) {
-				EventTimeline timeline = new EventTimeline(eventCount);
-				for (int i = 0; i < eventCount; i++) {
-					float time = ReadFloat(input);
-					EventData eventData = skeletonData.events.Items[ReadVarint(input, true)];
-					Event e = new Event(time, eventData) {
-						Int = ReadVarint(input, false),
-						Float = ReadFloat(input),
-						String = ReadBoolean(input) ? ReadString(input) : eventData.String
-					};
-					if (e.data.AudioPath != null) {
-						e.volume = ReadFloat(input);
-						e.balance = ReadFloat(input);
-					}
-					timeline.SetFrame(i, e);
-				}
-				timelines.Add(timeline);
-				duration = Math.Max(duration, timeline.frames[eventCount - 1]);
-			}
-
-			timelines.TrimExcess();
-			skeletonData.animations.Add(new Animation(name, timelines, duration));
-		}
-
-		private void ReadCurve (Stream input, int frameIndex, CurveTimeline timeline) {
-			switch (input.ReadByte()) {
-			case CURVE_STEPPED:
-				timeline.SetStepped(frameIndex);
-				break;
-			case CURVE_BEZIER:
-				timeline.SetCurve(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input));
-				break;
-			}
-		}
-
-		private static sbyte ReadSByte (Stream input) {
-			int value = input.ReadByte();
-			if (value == -1) throw new EndOfStreamException();
-			return (sbyte)value;
-		}
-
-		private static bool ReadBoolean (Stream input) {
-			return input.ReadByte() != 0;
-		}
-
-		private float ReadFloat (Stream input) {
-			buffer[3] = (byte)input.ReadByte();
-			buffer[2] = (byte)input.ReadByte();
-			buffer[1] = (byte)input.ReadByte();
-			buffer[0] = (byte)input.ReadByte();
-			return BitConverter.ToSingle(buffer, 0);
-		}
-
-		private static int ReadInt (Stream input) {
-			return (input.ReadByte() << 24) + (input.ReadByte() << 16) + (input.ReadByte() << 8) + input.ReadByte();
-		}
-
-		private static int ReadVarint (Stream input, bool optimizePositive) {
-			int b = input.ReadByte();
-			int result = b & 0x7F;
-			if ((b & 0x80) != 0) {
-				b = input.ReadByte();
-				result |= (b & 0x7F) << 7;
-				if ((b & 0x80) != 0) {
-					b = input.ReadByte();
-					result |= (b & 0x7F) << 14;
-					if ((b & 0x80) != 0) {
-						b = input.ReadByte();
-						result |= (b & 0x7F) << 21;
-						if ((b & 0x80) != 0) result |= (input.ReadByte() & 0x7F) << 28;
-					}
-				}
-			}
-			return optimizePositive ? result : ((result >> 1) ^ -(result & 1));
-		}
-
-		private string ReadString (Stream input) {
-			int byteCount = ReadVarint(input, true);
-			switch (byteCount) {
-			case 0:
-				return null;
-			case 1:
-				return "";
-			}
-			byteCount--;
-			byte[] buffer = this.buffer;
-			if (buffer.Length < byteCount) buffer = new byte[byteCount];
-			ReadFully(input, buffer, 0, byteCount);
-			return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount);
-		}
-
-		private static void ReadFully (Stream input, byte[] buffer, int offset, int length) {
-			while (length > 0) {
-				int count = input.Read(buffer, offset, length);
-				if (count <= 0) throw new EndOfStreamException();
-				offset += count;
-				length -= count;
-			}
-		}
-
-		internal class Vertices {
-			public int[] bones;
-			public float[] vertices;
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+#if (UNITY_5 || UNITY_5_3_OR_NEWER || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1)
+#define IS_UNITY
+#endif
+
+using System;
+using System.IO;
+using System.Collections.Generic;
+
+#if WINDOWS_STOREAPP
+using System.Threading.Tasks;
+using Windows.Storage;
+#endif
+
+namespace Spine {
+	public class SkeletonBinary {
+		public const int BONE_ROTATE = 0;
+		public const int BONE_TRANSLATE = 1;
+		public const int BONE_SCALE = 2;
+		public const int BONE_SHEAR = 3;
+
+		public const int SLOT_ATTACHMENT = 0;
+		public const int SLOT_COLOR = 1;
+		public const int SLOT_TWO_COLOR = 2;
+
+		public const int PATH_POSITION = 0;
+		public const int PATH_SPACING = 1;
+		public const int PATH_MIX = 2;
+
+		public const int CURVE_LINEAR = 0;
+		public const int CURVE_STEPPED = 1;
+		public const int CURVE_BEZIER = 2;
+
+		public float Scale { get; set; }
+
+		private AttachmentLoader attachmentLoader;
+		private byte[] buffer = new byte[32];
+		private List<SkeletonJson.LinkedMesh> linkedMeshes = new List<SkeletonJson.LinkedMesh>();
+
+		public SkeletonBinary (params Atlas[] atlasArray)
+			: this(new AtlasAttachmentLoader(atlasArray)) {
+		}
+
+		public SkeletonBinary (AttachmentLoader attachmentLoader) {
+			if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader");
+			this.attachmentLoader = attachmentLoader;
+			Scale = 1;
+		}
+			
+		#if !ISUNITY && WINDOWS_STOREAPP
+		private async Task<SkeletonData> ReadFile(string path) {
+			var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
+			using (var input = new BufferedStream(await folder.GetFileAsync(path).AsTask().ConfigureAwait(false))) {
+				SkeletonData skeletonData = ReadSkeletonData(input);
+				skeletonData.Name = Path.GetFileNameWithoutExtension(path);
+				return skeletonData;
+			}
+		}
+
+		public SkeletonData ReadSkeletonData (String path) {
+			return this.ReadFile(path).Result;
+		}
+		#else
+		public SkeletonData ReadSkeletonData (String path) {
+		#if WINDOWS_PHONE
+			using (var input = new BufferedStream(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) {
+		#else
+			using (var input = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) {
+		#endif
+				SkeletonData skeletonData = ReadSkeletonData(input);
+				skeletonData.name = Path.GetFileNameWithoutExtension(path);
+				return skeletonData;
+			}
+		}
+		#endif // WINDOWS_STOREAPP
+
+		public static readonly TransformMode[] TransformModeValues = {
+			TransformMode.Normal,
+			TransformMode.OnlyTranslation,
+			TransformMode.NoRotationOrReflection,
+			TransformMode.NoScale,
+			TransformMode.NoScaleOrReflection
+		};
+
+		/// <summary>Returns the version string of binary skeleton data.</summary>
+		public static string GetVersionString (Stream input) {
+			if (input == null) throw new ArgumentNullException("input");
+
+			try {
+				// Hash.
+				int byteCount = ReadVarint(input, true);
+				if (byteCount > 1) input.Position += byteCount - 1;
+
+				// Version.
+				byteCount = ReadVarint(input, true);
+				if (byteCount > 1) {
+					byteCount--;
+					var buffer = new byte[byteCount];
+					ReadFully(input, buffer, 0, byteCount);
+					return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount);
+				}
+
+				throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.", "input");
+			} catch (Exception e) {
+				throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.\n" + e, "input");
+			}
+		}
+
+		public SkeletonData ReadSkeletonData (Stream input) {
+			if (input == null) throw new ArgumentNullException("input");
+			float scale = Scale;
+
+			var skeletonData = new SkeletonData();
+			skeletonData.hash = ReadString(input);
+			if (skeletonData.hash.Length == 0) skeletonData.hash = null;
+			skeletonData.version = ReadString(input);
+			if (skeletonData.version.Length == 0) skeletonData.version = null;
+			skeletonData.width = ReadFloat(input);
+			skeletonData.height = ReadFloat(input);
+
+			bool nonessential = ReadBoolean(input);
+
+			if (nonessential) {
+				skeletonData.fps = ReadFloat(input);
+
+				skeletonData.imagesPath = ReadString(input);
+				if (string.IsNullOrEmpty(skeletonData.imagesPath)) skeletonData.imagesPath = null;
+
+				skeletonData.audioPath = ReadString(input);
+				if (string.IsNullOrEmpty(skeletonData.audioPath)) skeletonData.audioPath = null;
+			}
+
+			// Bones.
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
+				String name = ReadString(input);
+				BoneData parent = i == 0 ? null : skeletonData.bones.Items[ReadVarint(input, true)];
+				BoneData data = new BoneData(i, name, parent);
+				data.rotation = ReadFloat(input);		
+				data.x = ReadFloat(input) * scale;
+				data.y = ReadFloat(input) * scale;
+				data.scaleX = ReadFloat(input);
+				data.scaleY = ReadFloat(input);
+				data.shearX = ReadFloat(input);
+				data.shearY = ReadFloat(input);
+				data.length = ReadFloat(input) * scale;
+				data.transformMode = TransformModeValues[ReadVarint(input, true)];
+				if (nonessential) ReadInt(input); // Skip bone color.
+				skeletonData.bones.Add(data);
+			}
+
+			// Slots.
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
+				String slotName = ReadString(input);
+				BoneData boneData = skeletonData.bones.Items[ReadVarint(input, true)];
+				SlotData slotData = new SlotData(i, slotName, boneData);
+				int color = ReadInt(input);
+				slotData.r = ((color & 0xff000000) >> 24) / 255f;
+				slotData.g = ((color & 0x00ff0000) >> 16) / 255f;
+				slotData.b = ((color & 0x0000ff00) >> 8) / 255f;
+				slotData.a = ((color & 0x000000ff)) / 255f;
+
+				int darkColor = ReadInt(input); // 0x00rrggbb
+				if (darkColor != -1) {
+					slotData.hasSecondColor = true;
+					slotData.r2 = ((darkColor & 0x00ff0000) >> 16) / 255f;
+					slotData.g2 = ((darkColor & 0x0000ff00) >> 8) / 255f;
+					slotData.b2 = ((darkColor & 0x000000ff)) / 255f;
+				}
+
+				slotData.attachmentName = ReadString(input);
+				slotData.blendMode = (BlendMode)ReadVarint(input, true);
+				skeletonData.slots.Add(slotData);
+			}
+
+			// IK constraints.
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
+				IkConstraintData data = new IkConstraintData(ReadString(input));
+				data.order = ReadVarint(input, true);
+				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++)
+					data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]);
+				data.target = skeletonData.bones.Items[ReadVarint(input, true)];
+				data.mix = ReadFloat(input);
+				data.bendDirection = ReadSByte(input);
+				data.compress = ReadBoolean(input);
+				data.stretch = ReadBoolean(input);
+				data.uniform = ReadBoolean(input);
+				skeletonData.ikConstraints.Add(data);
+			}
+
+			// Transform constraints.
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
+				TransformConstraintData data = new TransformConstraintData(ReadString(input));
+				data.order = ReadVarint(input, true);
+				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++)
+				    data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]);
+				data.target = skeletonData.bones.Items[ReadVarint(input, true)];
+				data.local = ReadBoolean(input);
+				data.relative = ReadBoolean(input);
+				data.offsetRotation = ReadFloat(input);
+				data.offsetX = ReadFloat(input) * scale;
+				data.offsetY = ReadFloat(input) * scale;
+				data.offsetScaleX = ReadFloat(input);
+				data.offsetScaleY = ReadFloat(input);
+				data.offsetShearY = ReadFloat(input);
+				data.rotateMix = ReadFloat(input);
+				data.translateMix = ReadFloat(input);
+				data.scaleMix = ReadFloat(input);
+				data.shearMix = ReadFloat(input);
+				skeletonData.transformConstraints.Add(data);
+			}
+
+			// Path constraints
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
+				PathConstraintData data = new PathConstraintData(ReadString(input));
+				data.order = ReadVarint(input, true);
+				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++)
+					data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]);
+				data.target = skeletonData.slots.Items[ReadVarint(input, true)];
+				data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(ReadVarint(input, true));
+				data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue(ReadVarint(input, true));
+				data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(ReadVarint(input, true));
+				data.offsetRotation = ReadFloat(input);
+				data.position = ReadFloat(input);
+				if (data.positionMode == PositionMode.Fixed) data.position *= scale;
+				data.spacing = ReadFloat(input);
+				if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale;
+				data.rotateMix = ReadFloat(input);
+				data.translateMix = ReadFloat(input);
+				skeletonData.pathConstraints.Add(data);
+			}
+
+			// Default skin.
+			Skin defaultSkin = ReadSkin(input, skeletonData, "default", nonessential);
+			if (defaultSkin != null) {
+				skeletonData.defaultSkin = defaultSkin;
+				skeletonData.skins.Add(defaultSkin);
+			}
+
+			// Skins.
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++)
+				skeletonData.skins.Add(ReadSkin(input, skeletonData, ReadString(input), nonessential));
+
+			// Linked meshes.
+			for (int i = 0, n = linkedMeshes.Count; i < n; i++) {
+				SkeletonJson.LinkedMesh linkedMesh = linkedMeshes[i];
+				Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin);
+				if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin);
+				Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent);
+				if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent);
+				linkedMesh.mesh.ParentMesh = (MeshAttachment)parent;
+				linkedMesh.mesh.UpdateUVs();
+			}
+			linkedMeshes.Clear();
+
+			// Events.
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
+				EventData data = new EventData(ReadString(input));
+				data.Int = ReadVarint(input, false);
+				data.Float = ReadFloat(input);
+				data.String = ReadString(input);
+				data.AudioPath = ReadString(input);
+				if (data.AudioPath != null) {
+					data.Volume = ReadFloat(input);
+					data.Balance = ReadFloat(input);
+				}
+				skeletonData.events.Add(data);
+			}
+
+			// Animations.
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++)
+				ReadAnimation(ReadString(input), input, skeletonData);
+
+			skeletonData.bones.TrimExcess();
+			skeletonData.slots.TrimExcess();
+			skeletonData.skins.TrimExcess();
+			skeletonData.events.TrimExcess();
+			skeletonData.animations.TrimExcess();
+			skeletonData.ikConstraints.TrimExcess();
+			skeletonData.pathConstraints.TrimExcess();
+			return skeletonData;
+		}
+
+
+		/// <returns>May be null.</returns>
+		private Skin ReadSkin (Stream input, SkeletonData skeletonData, String skinName, bool nonessential) {
+			int slotCount = ReadVarint(input, true);
+			if (slotCount == 0) return null;
+			Skin skin = new Skin(skinName);
+			for (int i = 0; i < slotCount; i++) {
+				int slotIndex = ReadVarint(input, true);
+				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) {
+					String name = ReadString(input);
+					Attachment attachment = ReadAttachment(input, skeletonData, skin, slotIndex, name, nonessential);
+					if (attachment != null) skin.AddAttachment(slotIndex, name, attachment);
+				}
+			}
+			return skin;
+		}
+
+		private Attachment ReadAttachment (Stream input, SkeletonData skeletonData, Skin skin, int slotIndex, String attachmentName, bool nonessential) {
+			float scale = Scale;
+
+			String name = ReadString(input);
+			if (name == null) name = attachmentName;
+
+			AttachmentType type = (AttachmentType)input.ReadByte();
+			switch (type) {
+			case AttachmentType.Region: {
+					String path = ReadString(input);
+					float rotation = ReadFloat(input);		
+					float x = ReadFloat(input);
+					float y = ReadFloat(input);
+					float scaleX = ReadFloat(input);
+					float scaleY = ReadFloat(input);
+					float width = ReadFloat(input);
+					float height = ReadFloat(input);
+					int color = ReadInt(input);
+
+					if (path == null) path = name;
+					RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path);
+					if (region == null) return null;
+					region.Path = path;
+					region.x = x * scale;
+					region.y = y * scale;
+					region.scaleX = scaleX;
+					region.scaleY = scaleY;
+					region.rotation = rotation;
+					region.width = width * scale;
+					region.height = height * scale;
+					region.r = ((color & 0xff000000) >> 24) / 255f;
+					region.g = ((color & 0x00ff0000) >> 16) / 255f;
+					region.b = ((color & 0x0000ff00) >> 8) / 255f;
+					region.a = ((color & 0x000000ff)) / 255f;
+					region.UpdateOffset();
+					return region;
+				}
+			case AttachmentType.Boundingbox: {
+					int vertexCount = ReadVarint(input, true);
+					Vertices vertices = ReadVertices(input, vertexCount);
+					if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; // Avoid unused local warning.
+					
+					BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name);
+					if (box == null) return null;
+					box.worldVerticesLength = vertexCount << 1;
+					box.vertices = vertices.vertices;
+					box.bones = vertices.bones;                    
+					return box;
+				}
+			case AttachmentType.Mesh: {
+					String path = ReadString(input);
+					int color = ReadInt(input);
+					int vertexCount = ReadVarint(input, true);					
+					float[] uvs = ReadFloatArray(input, vertexCount << 1, 1);
+					int[] triangles = ReadShortArray(input);
+					Vertices vertices = ReadVertices(input, vertexCount);
+					int hullLength = ReadVarint(input, true);
+					int[] edges = null;
+					float width = 0, height = 0;
+					if (nonessential) {
+						edges = ReadShortArray(input);
+						width = ReadFloat(input);
+						height = ReadFloat(input);
+					}
+
+					if (path == null) path = name;
+					MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path);
+					if (mesh == null) return null;
+					mesh.Path = path;
+					mesh.r = ((color & 0xff000000) >> 24) / 255f;
+					mesh.g = ((color & 0x00ff0000) >> 16) / 255f;
+					mesh.b = ((color & 0x0000ff00) >> 8) / 255f;
+					mesh.a = ((color & 0x000000ff)) / 255f;
+					mesh.bones = vertices.bones;
+					mesh.vertices = vertices.vertices;
+					mesh.WorldVerticesLength = vertexCount << 1;
+					mesh.triangles = triangles;
+					mesh.regionUVs = uvs;
+					mesh.UpdateUVs();
+					mesh.HullLength = hullLength << 1;
+					if (nonessential) {
+						mesh.Edges = edges;
+						mesh.Width = width * scale;
+						mesh.Height = height * scale;
+					}
+					return mesh;
+				}
+			case AttachmentType.Linkedmesh: {
+					String path = ReadString(input);
+					int color = ReadInt(input);
+					String skinName = ReadString(input);
+					String parent = ReadString(input);
+					bool inheritDeform = ReadBoolean(input);
+					float width = 0, height = 0;
+					if (nonessential) {
+						width = ReadFloat(input);
+						height = ReadFloat(input);
+					}
+
+					if (path == null) path = name;
+					MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path);
+					if (mesh == null) return null;
+					mesh.Path = path;
+					mesh.r = ((color & 0xff000000) >> 24) / 255f;
+					mesh.g = ((color & 0x00ff0000) >> 16) / 255f;
+					mesh.b = ((color & 0x0000ff00) >> 8) / 255f;
+					mesh.a = ((color & 0x000000ff)) / 255f;
+					mesh.inheritDeform = inheritDeform;
+					if (nonessential) {
+						mesh.Width = width * scale;
+						mesh.Height = height * scale;
+					}
+					linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent));
+					return mesh;
+				}
+			case AttachmentType.Path: {
+					bool closed = ReadBoolean(input);
+					bool constantSpeed = ReadBoolean(input);
+					int vertexCount = ReadVarint(input, true);
+					Vertices vertices = ReadVertices(input, vertexCount);
+					float[] lengths = new float[vertexCount / 3];
+					for (int i = 0, n = lengths.Length; i < n; i++)
+						lengths[i] = ReadFloat(input) * scale;
+					if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0;
+
+					PathAttachment path = attachmentLoader.NewPathAttachment(skin, name);
+					if (path == null) return null;
+					path.closed = closed;
+					path.constantSpeed = constantSpeed;
+					path.worldVerticesLength = vertexCount << 1;
+					path.vertices = vertices.vertices;
+					path.bones = vertices.bones;
+					path.lengths = lengths;
+					return path;                    
+				}
+			case AttachmentType.Point: {
+					float rotation = ReadFloat(input);
+					float x = ReadFloat(input);
+					float y = ReadFloat(input);
+					if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0;
+
+					PointAttachment point = attachmentLoader.NewPointAttachment(skin, name);
+					if (point == null) return null;
+					point.x = x * scale;
+					point.y = y * scale;
+					point.rotation = rotation;
+					//if (nonessential) point.color = color;
+					return point;
+				}
+			case AttachmentType.Clipping: {
+					int endSlotIndex = ReadVarint(input, true);
+					int vertexCount = ReadVarint(input, true);
+					Vertices vertices = ReadVertices(input, vertexCount);
+					if (nonessential) ReadInt(input);
+
+					ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name);
+					if (clip == null) return null;
+					clip.EndSlot = skeletonData.slots.Items[endSlotIndex];
+					clip.worldVerticesLength = vertexCount << 1;
+					clip.vertices = vertices.vertices;
+					clip.bones = vertices.bones;
+					return clip;
+				}
+			}
+			return null;
+		}
+
+		private Vertices ReadVertices (Stream input, int vertexCount) {
+			float scale = Scale;
+			int verticesLength = vertexCount << 1;
+			Vertices vertices = new Vertices();
+			if(!ReadBoolean(input)) {
+				vertices.vertices = ReadFloatArray(input, verticesLength, scale);
+				return vertices;
+			}
+			var weights = new ExposedList<float>(verticesLength * 3 * 3);
+			var bonesArray = new ExposedList<int>(verticesLength * 3);
+			for (int i = 0; i < vertexCount; i++) {
+				int boneCount = ReadVarint(input, true);
+				bonesArray.Add(boneCount);
+				for (int ii = 0; ii < boneCount; ii++) {
+					bonesArray.Add(ReadVarint(input, true));
+					weights.Add(ReadFloat(input) * scale);
+					weights.Add(ReadFloat(input) * scale);
+					weights.Add(ReadFloat(input));
+				}
+			}
+
+			vertices.vertices = weights.ToArray();
+			vertices.bones = bonesArray.ToArray();
+			return vertices;
+		}
+
+		private float[] ReadFloatArray (Stream input, int n, float scale) {
+			float[] array = new float[n];
+			if (scale == 1) {
+				for (int i = 0; i < n; i++)
+					array[i] = ReadFloat(input);
+			} else {
+				for (int i = 0; i < n; i++)
+					array[i] = ReadFloat(input) * scale;
+			}
+			return array;
+		}
+
+		private int[] ReadShortArray (Stream input) {
+			int n = ReadVarint(input, true);
+			int[] array = new int[n];
+			for (int i = 0; i < n; i++) 
+				array[i] = (input.ReadByte() << 8) | input.ReadByte();
+			return array;
+		}
+
+		private void ReadAnimation (String name, Stream input, SkeletonData skeletonData) {
+			var timelines = new ExposedList<Timeline>();
+			float scale = Scale;
+			float duration = 0;
+
+			// Slot timelines.
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
+				int slotIndex = ReadVarint(input, true);
+				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) {
+					int timelineType = input.ReadByte();
+					int frameCount = ReadVarint(input, true);
+					switch (timelineType) {
+					case SLOT_ATTACHMENT: {
+							AttachmentTimeline timeline = new AttachmentTimeline(frameCount);
+							timeline.slotIndex = slotIndex;
+							for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
+								timeline.SetFrame(frameIndex, ReadFloat(input), ReadString(input));
+							timelines.Add(timeline);
+							duration = Math.Max(duration, timeline.frames[frameCount - 1]);
+							break;
+						}
+					case SLOT_COLOR: {
+							ColorTimeline timeline = new ColorTimeline(frameCount);
+							timeline.slotIndex = slotIndex;
+							for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
+								float time = ReadFloat(input);
+								int color = ReadInt(input);
+								float r = ((color & 0xff000000) >> 24) / 255f;
+								float g = ((color & 0x00ff0000) >> 16) / 255f;
+								float b = ((color & 0x0000ff00) >> 8) / 255f;
+								float a = ((color & 0x000000ff)) / 255f;
+								timeline.SetFrame(frameIndex, time, r, g, b, a);
+								if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
+							}
+							timelines.Add(timeline);
+							duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]);
+							break;
+						}
+					case SLOT_TWO_COLOR: {
+							TwoColorTimeline timeline = new TwoColorTimeline(frameCount);
+							timeline.slotIndex = slotIndex;
+							for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
+								float time = ReadFloat(input);
+								int color = ReadInt(input);
+								float r = ((color & 0xff000000) >> 24) / 255f;
+								float g = ((color & 0x00ff0000) >> 16) / 255f;
+								float b = ((color & 0x0000ff00) >> 8) / 255f;
+								float a = ((color & 0x000000ff)) / 255f;
+								int color2 = ReadInt(input); // 0x00rrggbb
+								float r2 = ((color2 & 0x00ff0000) >> 16) / 255f;
+								float g2 = ((color2 & 0x0000ff00) >> 8) / 255f;
+								float b2 = ((color2 & 0x000000ff)) / 255f;
+
+								timeline.SetFrame(frameIndex, time, r, g, b, a, r2, g2, b2);
+								if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
+							}
+							timelines.Add(timeline);
+							duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TwoColorTimeline.ENTRIES]);
+							break;
+						}
+					}
+				}
+			}
+
+			// Bone timelines.
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
+				int boneIndex = ReadVarint(input, true);
+				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) {
+					int timelineType = input.ReadByte();
+					int frameCount = ReadVarint(input, true);
+					switch (timelineType) {
+					case BONE_ROTATE: {
+							RotateTimeline timeline = new RotateTimeline(frameCount);
+							timeline.boneIndex = boneIndex;
+							for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
+								timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input));
+								if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
+							}
+							timelines.Add(timeline);
+							duration = Math.Max(duration, timeline.frames[(frameCount - 1) * RotateTimeline.ENTRIES]);
+							break;
+						}
+					case BONE_TRANSLATE:
+					case BONE_SCALE:
+					case BONE_SHEAR: {
+							TranslateTimeline timeline;
+							float timelineScale = 1;
+							if (timelineType == BONE_SCALE)
+								timeline = new ScaleTimeline(frameCount);
+							else if (timelineType == BONE_SHEAR)
+								timeline = new ShearTimeline(frameCount);
+							else {
+								timeline = new TranslateTimeline(frameCount);
+								timelineScale = scale;
+							}
+							timeline.boneIndex = boneIndex;
+							for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
+								timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale, ReadFloat(input)
+									* timelineScale);
+								if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
+							}
+							timelines.Add(timeline);
+							duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TranslateTimeline.ENTRIES]);
+							break;
+						}
+					}
+				}
+			}
+
+			// IK timelines.
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {				
+				int index = ReadVarint(input, true);
+				int frameCount = ReadVarint(input, true);
+				IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount) {
+					ikConstraintIndex = index
+				};
+				for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
+					timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadSByte(input), ReadBoolean(input), ReadBoolean(input));
+					if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
+				}
+				timelines.Add(timeline);
+				duration = Math.Max(duration, timeline.frames[(frameCount - 1) * IkConstraintTimeline.ENTRIES]);
+			}
+
+			// Transform constraint timelines.
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
+				int index = ReadVarint(input, true);
+				int frameCount = ReadVarint(input, true);
+				TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount);
+				timeline.transformConstraintIndex = index;
+				for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
+					timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input));
+					if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
+				}
+				timelines.Add(timeline);
+				duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TransformConstraintTimeline.ENTRIES]);
+			}
+
+			// Path constraint timelines.
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
+				int index = ReadVarint(input, true);
+				PathConstraintData data = skeletonData.pathConstraints.Items[index];
+				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) {
+					int timelineType = ReadSByte(input);
+					int frameCount = ReadVarint(input, true);
+					switch(timelineType) {
+						case PATH_POSITION:
+						case PATH_SPACING: {
+								PathConstraintPositionTimeline timeline;
+								float timelineScale = 1;
+								if (timelineType == PATH_SPACING) {
+									timeline = new PathConstraintSpacingTimeline(frameCount);
+									if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale; 
+								} else {
+									timeline = new PathConstraintPositionTimeline(frameCount);
+									if (data.positionMode == PositionMode.Fixed) timelineScale = scale;
+								}
+								timeline.pathConstraintIndex = index;
+								for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {                                    
+									timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input) * timelineScale);
+									if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
+								}
+								timelines.Add(timeline);
+								duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintPositionTimeline.ENTRIES]);
+								break;
+							}
+						case PATH_MIX: {
+								PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount);
+								timeline.pathConstraintIndex = index;
+								for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
+									timeline.SetFrame(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input));
+									if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
+								}
+								timelines.Add(timeline);
+								duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintMixTimeline.ENTRIES]);
+								break;
+							}
+					}
+				}
+			}
+
+			// Deform timelines.
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
+				Skin skin = skeletonData.skins.Items[ReadVarint(input, true)];
+				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) {
+					int slotIndex = ReadVarint(input, true);
+					for (int iii = 0, nnn = ReadVarint(input, true); iii < nnn; iii++) {
+						VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, ReadString(input));
+						bool weighted = attachment.bones != null;
+						float[] vertices = attachment.vertices;
+						int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length;
+
+						int frameCount = ReadVarint(input, true);
+						DeformTimeline timeline = new DeformTimeline(frameCount);
+						timeline.slotIndex = slotIndex;
+						timeline.attachment = attachment;
+
+						for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
+							float time = ReadFloat(input);
+							float[] deform;
+							int end = ReadVarint(input, true);
+							if (end == 0)
+								deform = weighted ? new float[deformLength] : vertices;
+							else {
+								deform = new float[deformLength];
+								int start = ReadVarint(input, true);
+								end += start;
+								if (scale == 1) {
+									for (int v = start; v < end; v++)
+										deform[v] = ReadFloat(input);
+								} else {
+									for (int v = start; v < end; v++)
+										deform[v] = ReadFloat(input) * scale;
+								}
+								if (!weighted) {
+									for (int v = 0, vn = deform.Length; v < vn; v++)
+										deform[v] += vertices[v];
+								}
+							}
+
+							timeline.SetFrame(frameIndex, time, deform);
+							if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
+						}							
+						timelines.Add(timeline);
+						duration = Math.Max(duration, timeline.frames[frameCount - 1]);
+					}
+				}
+			}
+
+			// Draw order timeline.
+			int drawOrderCount = ReadVarint(input, true);
+			if (drawOrderCount > 0) {
+				DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount);
+				int slotCount = skeletonData.slots.Count;
+				for (int i = 0; i < drawOrderCount; i++) {
+					float time = ReadFloat(input);
+					int offsetCount = ReadVarint(input, true);
+					int[] drawOrder = new int[slotCount];
+					for (int ii = slotCount - 1; ii >= 0; ii--)
+						drawOrder[ii] = -1;
+					int[] unchanged = new int[slotCount - offsetCount];
+					int originalIndex = 0, unchangedIndex = 0;
+					for (int ii = 0; ii < offsetCount; ii++) {
+						int slotIndex = ReadVarint(input, true);
+						// Collect unchanged items.
+						while (originalIndex != slotIndex)
+							unchanged[unchangedIndex++] = originalIndex++;
+						// Set changed items.
+						drawOrder[originalIndex + ReadVarint(input, true)] = originalIndex++;
+					}
+					// Collect remaining unchanged items.
+					while (originalIndex < slotCount)
+						unchanged[unchangedIndex++] = originalIndex++;
+					// Fill in unchanged items.
+					for (int ii = slotCount - 1; ii >= 0; ii--)
+						if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex];
+					timeline.SetFrame(i, time, drawOrder);
+				}
+				timelines.Add(timeline);
+				duration = Math.Max(duration, timeline.frames[drawOrderCount - 1]);
+			}
+
+			// Event timeline.
+			int eventCount = ReadVarint(input, true);
+			if (eventCount > 0) {
+				EventTimeline timeline = new EventTimeline(eventCount);
+				for (int i = 0; i < eventCount; i++) {
+					float time = ReadFloat(input);
+					EventData eventData = skeletonData.events.Items[ReadVarint(input, true)];
+					Event e = new Event(time, eventData) {
+						Int = ReadVarint(input, false),
+						Float = ReadFloat(input),
+						String = ReadBoolean(input) ? ReadString(input) : eventData.String
+					};
+					if (e.data.AudioPath != null) {
+						e.volume = ReadFloat(input);
+						e.balance = ReadFloat(input);
+					}
+					timeline.SetFrame(i, e);
+				}
+				timelines.Add(timeline);
+				duration = Math.Max(duration, timeline.frames[eventCount - 1]);
+			}
+
+			timelines.TrimExcess();
+			skeletonData.animations.Add(new Animation(name, timelines, duration));
+		}
+
+		private void ReadCurve (Stream input, int frameIndex, CurveTimeline timeline) {
+			switch (input.ReadByte()) {
+			case CURVE_STEPPED:
+				timeline.SetStepped(frameIndex);
+				break;
+			case CURVE_BEZIER:
+				timeline.SetCurve(frameIndex, ReadFloat(input), ReadFloat(input), ReadFloat(input), ReadFloat(input));
+				break;
+			}
+		}
+
+		private static sbyte ReadSByte (Stream input) {
+			int value = input.ReadByte();
+			if (value == -1) throw new EndOfStreamException();
+			return (sbyte)value;
+		}
+
+		private static bool ReadBoolean (Stream input) {
+			return input.ReadByte() != 0;
+		}
+
+		private float ReadFloat (Stream input) {
+			buffer[3] = (byte)input.ReadByte();
+			buffer[2] = (byte)input.ReadByte();
+			buffer[1] = (byte)input.ReadByte();
+			buffer[0] = (byte)input.ReadByte();
+			return BitConverter.ToSingle(buffer, 0);
+		}
+
+		private static int ReadInt (Stream input) {
+			return (input.ReadByte() << 24) + (input.ReadByte() << 16) + (input.ReadByte() << 8) + input.ReadByte();
+		}
+
+		private static int ReadVarint (Stream input, bool optimizePositive) {
+			int b = input.ReadByte();
+			int result = b & 0x7F;
+			if ((b & 0x80) != 0) {
+				b = input.ReadByte();
+				result |= (b & 0x7F) << 7;
+				if ((b & 0x80) != 0) {
+					b = input.ReadByte();
+					result |= (b & 0x7F) << 14;
+					if ((b & 0x80) != 0) {
+						b = input.ReadByte();
+						result |= (b & 0x7F) << 21;
+						if ((b & 0x80) != 0) result |= (input.ReadByte() & 0x7F) << 28;
+					}
+				}
+			}
+			return optimizePositive ? result : ((result >> 1) ^ -(result & 1));
+		}
+
+		private string ReadString (Stream input) {
+			int byteCount = ReadVarint(input, true);
+			switch (byteCount) {
+			case 0:
+				return null;
+			case 1:
+				return "";
+			}
+			byteCount--;
+			byte[] buffer = this.buffer;
+			if (buffer.Length < byteCount) buffer = new byte[byteCount];
+			ReadFully(input, buffer, 0, byteCount);
+			return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount);
+		}
+
+		private static void ReadFully (Stream input, byte[] buffer, int offset, int length) {
+			while (length > 0) {
+				int count = input.Read(buffer, offset, length);
+				if (count <= 0) throw new EndOfStreamException();
+				offset += count;
+				length -= count;
+			}
+		}
+
+		internal class Vertices {
+			public int[] bones;
+			public float[] vertices;
+		}
+	}
+}

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

@@ -1,234 +1,234 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-using System;
-
-namespace Spine {
-
-	/// <summary>
-	/// Collects each BoundingBoxAttachment that is visible and computes the world vertices for its polygon.
-	/// The polygon vertices are provided along with convenience methods for doing hit detection.
-	/// </summary>
-	public class SkeletonBounds {
-		private ExposedList<Polygon> polygonPool = new ExposedList<Polygon>();
-		private float minX, minY, maxX, maxY;
-
-		public ExposedList<BoundingBoxAttachment> BoundingBoxes { get; private set; }
-		public ExposedList<Polygon> Polygons { get; private set; }
-		public float MinX { get { return minX; } set { minX = value; } }
-		public float MinY { get { return minY; } set { minY = value; } }
-		public float MaxX { get { return maxX; } set { maxX = value; } }
-		public float MaxY { get { return maxY; } set { maxY = value; } }
-		public float Width { get { return maxX - minX; } }
-		public float Height { get { return maxY - minY; } }
-
-		public SkeletonBounds () {
-			BoundingBoxes = new ExposedList<BoundingBoxAttachment>();
-			Polygons = new ExposedList<Polygon>();
-		}
-
-		/// <summary>
-		/// Clears any previous polygons, finds all visible bounding box attachments,
-		/// and computes the world vertices for each bounding box's polygon.</summary>
-		/// <param name="skeleton">The skeleton.</param>
-		/// <param name="updateAabb">
-		/// If true, the axis aligned bounding box containing all the polygons is computed.
-		/// If false, the SkeletonBounds AABB methods will always return true.
-		/// </param>
-		public void Update (Skeleton skeleton, bool updateAabb) {
-			ExposedList<BoundingBoxAttachment> boundingBoxes = BoundingBoxes;
-			ExposedList<Polygon> polygons = Polygons;
-			ExposedList<Slot> slots = skeleton.slots;
-			int slotCount = slots.Count;
-
-			boundingBoxes.Clear();
-			for (int i = 0, n = polygons.Count; i < n; i++)
-				polygonPool.Add(polygons.Items[i]);
-			polygons.Clear();
-
-			for (int i = 0; i < slotCount; i++) {
-				Slot slot = slots.Items[i];
-				BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment;
-				if (boundingBox == null) continue;
-				boundingBoxes.Add(boundingBox);
-
-				Polygon polygon = null;
-				int poolCount = polygonPool.Count;
-				if (poolCount > 0) {
-					polygon = polygonPool.Items[poolCount - 1];
-					polygonPool.RemoveAt(poolCount - 1);
-				} else
-					polygon = new Polygon();
-				polygons.Add(polygon);
-
-				int count = boundingBox.worldVerticesLength;
-				polygon.Count = count;
-				if (polygon.Vertices.Length < count) polygon.Vertices = new float[count];
-				boundingBox.ComputeWorldVertices(slot, polygon.Vertices);
-			}
-
-			if (updateAabb) {
-				AabbCompute();
-			} else {
-				minX = int.MinValue;
-				minY = int.MinValue;
-				maxX = int.MaxValue;
-				maxY = int.MaxValue;
-			}
-		}
-
-		private void AabbCompute () {
-			float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue;
-			ExposedList<Polygon> polygons = Polygons;
-			for (int i = 0, n = polygons.Count; i < n; i++) {
-				Polygon polygon = polygons.Items[i];
-				float[] vertices = polygon.Vertices;
-				for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) {
-					float x = vertices[ii];
-					float y = vertices[ii + 1];
-					minX = Math.Min(minX, x);
-					minY = Math.Min(minY, y);
-					maxX = Math.Max(maxX, x);
-					maxY = Math.Max(maxY, y);
-				}
-			}
-			this.minX = minX;
-			this.minY = minY;
-			this.maxX = maxX;
-			this.maxY = maxY;
-		}
-
-
-		/// <summary>Returns true if the axis aligned bounding box contains the point.</summary>
-		public bool AabbContainsPoint (float x, float y) {
-			return x >= minX && x <= maxX && y >= minY && y <= maxY;
-		}
-
-		/// <summary>Returns true if the axis aligned bounding box intersects the line segment.</summary>
-		public bool AabbIntersectsSegment (float x1, float y1, float x2, float y2) {
-			float minX = this.minX;
-			float minY = this.minY;
-			float maxX = this.maxX;
-			float maxY = this.maxY;
-			if ((x1 <= minX && x2 <= minX) || (y1 <= minY && y2 <= minY) || (x1 >= maxX && x2 >= maxX) || (y1 >= maxY && y2 >= maxY))
-				return false;
-			float m = (y2 - y1) / (x2 - x1);
-			float y = m * (minX - x1) + y1;
-			if (y > minY && y < maxY) return true;
-			y = m * (maxX - x1) + y1;
-			if (y > minY && y < maxY) return true;
-			float x = (minY - y1) / m + x1;
-			if (x > minX && x < maxX) return true;
-			x = (maxY - y1) / m + x1;
-			if (x > minX && x < maxX) return true;
-			return false;
-		}
-
-		/// <summary>Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds.</summary>
-		public bool AabbIntersectsSkeleton (SkeletonBounds bounds) {
-			return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY;
-		}
-
-		/// <summary>Returns true if the polygon contains the point.</summary>
-		public bool ContainsPoint (Polygon polygon, float x, float y) {
-			float[] vertices = polygon.Vertices;
-			int nn = polygon.Count;
-
-			int prevIndex = nn - 2;
-			bool inside = false;
-			for (int ii = 0; ii < nn; ii += 2) {
-				float vertexY = vertices[ii + 1];
-				float prevY = vertices[prevIndex + 1];
-				if ((vertexY < y && prevY >= y) || (prevY < y && vertexY >= y)) {
-					float vertexX = vertices[ii];
-					if (vertexX + (y - vertexY) / (prevY - vertexY) * (vertices[prevIndex] - vertexX) < x) inside = !inside;
-				}
-				prevIndex = ii;
-			}
-			return inside;
-		}
-
-		/// <summary>Returns the first bounding box attachment that contains the point, or null. When doing many checks, it is usually more
-		/// efficient to only call this method if {@link #aabbContainsPoint(float, float)} returns true.</summary>
-		public BoundingBoxAttachment ContainsPoint (float x, float y) {
-			ExposedList<Polygon> polygons = Polygons;
-			for (int i = 0, n = polygons.Count; i < n; i++)
-				if (ContainsPoint(polygons.Items[i], x, y)) return BoundingBoxes.Items[i];
-			return null;
-		}
-
-		/// <summary>Returns the first bounding box attachment that contains the line segment, or null. When doing many checks, it is usually
-		/// more efficient to only call this method if {@link #aabbIntersectsSegment(float, float, float, float)} returns true.</summary>
-		public BoundingBoxAttachment IntersectsSegment (float x1, float y1, float x2, float y2) {
-			ExposedList<Polygon> polygons = Polygons;
-			for (int i = 0, n = polygons.Count; i < n; i++)
-				if (IntersectsSegment(polygons.Items[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i];
-			return null;
-		}
-
-		/// <summary>Returns true if the polygon contains the line segment.</summary>
-		public bool IntersectsSegment (Polygon polygon, float x1, float y1, float x2, float y2) {
-			float[] vertices = polygon.Vertices;
-			int nn = polygon.Count;
-
-			float width12 = x1 - x2, height12 = y1 - y2;
-			float det1 = x1 * y2 - y1 * x2;
-			float x3 = vertices[nn - 2], y3 = vertices[nn - 1];
-			for (int ii = 0; ii < nn; ii += 2) {
-				float x4 = vertices[ii], y4 = vertices[ii + 1];
-				float det2 = x3 * y4 - y3 * x4;
-				float width34 = x3 - x4, height34 = y3 - y4;
-				float det3 = width12 * height34 - height12 * width34;
-				float x = (det1 * width34 - width12 * det2) / det3;
-				if (((x >= x3 && x <= x4) || (x >= x4 && x <= x3)) && ((x >= x1 && x <= x2) || (x >= x2 && x <= x1))) {
-					float y = (det1 * height34 - height12 * det2) / det3;
-					if (((y >= y3 && y <= y4) || (y >= y4 && y <= y3)) && ((y >= y1 && y <= y2) || (y >= y2 && y <= y1))) return true;
-				}
-				x3 = x4;
-				y3 = y4;
-			}
-			return false;
-		}
-
-		public Polygon GetPolygon (BoundingBoxAttachment attachment) {
-			int index = BoundingBoxes.IndexOf(attachment);
-			return index == -1 ? null : Polygons.Items[index];
-		}
-	}
-
-	public class Polygon {
-		public float[] Vertices { get; set; }
-		public int Count { get; set; }
-
-		public Polygon () {
-			Vertices = new float[16];
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+
+	/// <summary>
+	/// Collects each BoundingBoxAttachment that is visible and computes the world vertices for its polygon.
+	/// The polygon vertices are provided along with convenience methods for doing hit detection.
+	/// </summary>
+	public class SkeletonBounds {
+		private ExposedList<Polygon> polygonPool = new ExposedList<Polygon>();
+		private float minX, minY, maxX, maxY;
+
+		public ExposedList<BoundingBoxAttachment> BoundingBoxes { get; private set; }
+		public ExposedList<Polygon> Polygons { get; private set; }
+		public float MinX { get { return minX; } set { minX = value; } }
+		public float MinY { get { return minY; } set { minY = value; } }
+		public float MaxX { get { return maxX; } set { maxX = value; } }
+		public float MaxY { get { return maxY; } set { maxY = value; } }
+		public float Width { get { return maxX - minX; } }
+		public float Height { get { return maxY - minY; } }
+
+		public SkeletonBounds () {
+			BoundingBoxes = new ExposedList<BoundingBoxAttachment>();
+			Polygons = new ExposedList<Polygon>();
+		}
+
+		/// <summary>
+		/// Clears any previous polygons, finds all visible bounding box attachments,
+		/// and computes the world vertices for each bounding box's polygon.</summary>
+		/// <param name="skeleton">The skeleton.</param>
+		/// <param name="updateAabb">
+		/// If true, the axis aligned bounding box containing all the polygons is computed.
+		/// If false, the SkeletonBounds AABB methods will always return true.
+		/// </param>
+		public void Update (Skeleton skeleton, bool updateAabb) {
+			ExposedList<BoundingBoxAttachment> boundingBoxes = BoundingBoxes;
+			ExposedList<Polygon> polygons = Polygons;
+			ExposedList<Slot> slots = skeleton.slots;
+			int slotCount = slots.Count;
+
+			boundingBoxes.Clear();
+			for (int i = 0, n = polygons.Count; i < n; i++)
+				polygonPool.Add(polygons.Items[i]);
+			polygons.Clear();
+
+			for (int i = 0; i < slotCount; i++) {
+				Slot slot = slots.Items[i];
+				BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment;
+				if (boundingBox == null) continue;
+				boundingBoxes.Add(boundingBox);
+
+				Polygon polygon = null;
+				int poolCount = polygonPool.Count;
+				if (poolCount > 0) {
+					polygon = polygonPool.Items[poolCount - 1];
+					polygonPool.RemoveAt(poolCount - 1);
+				} else
+					polygon = new Polygon();
+				polygons.Add(polygon);
+
+				int count = boundingBox.worldVerticesLength;
+				polygon.Count = count;
+				if (polygon.Vertices.Length < count) polygon.Vertices = new float[count];
+				boundingBox.ComputeWorldVertices(slot, polygon.Vertices);
+			}
+
+			if (updateAabb) {
+				AabbCompute();
+			} else {
+				minX = int.MinValue;
+				minY = int.MinValue;
+				maxX = int.MaxValue;
+				maxY = int.MaxValue;
+			}
+		}
+
+		private void AabbCompute () {
+			float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue;
+			ExposedList<Polygon> polygons = Polygons;
+			for (int i = 0, n = polygons.Count; i < n; i++) {
+				Polygon polygon = polygons.Items[i];
+				float[] vertices = polygon.Vertices;
+				for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) {
+					float x = vertices[ii];
+					float y = vertices[ii + 1];
+					minX = Math.Min(minX, x);
+					minY = Math.Min(minY, y);
+					maxX = Math.Max(maxX, x);
+					maxY = Math.Max(maxY, y);
+				}
+			}
+			this.minX = minX;
+			this.minY = minY;
+			this.maxX = maxX;
+			this.maxY = maxY;
+		}
+
+
+		/// <summary>Returns true if the axis aligned bounding box contains the point.</summary>
+		public bool AabbContainsPoint (float x, float y) {
+			return x >= minX && x <= maxX && y >= minY && y <= maxY;
+		}
+
+		/// <summary>Returns true if the axis aligned bounding box intersects the line segment.</summary>
+		public bool AabbIntersectsSegment (float x1, float y1, float x2, float y2) {
+			float minX = this.minX;
+			float minY = this.minY;
+			float maxX = this.maxX;
+			float maxY = this.maxY;
+			if ((x1 <= minX && x2 <= minX) || (y1 <= minY && y2 <= minY) || (x1 >= maxX && x2 >= maxX) || (y1 >= maxY && y2 >= maxY))
+				return false;
+			float m = (y2 - y1) / (x2 - x1);
+			float y = m * (minX - x1) + y1;
+			if (y > minY && y < maxY) return true;
+			y = m * (maxX - x1) + y1;
+			if (y > minY && y < maxY) return true;
+			float x = (minY - y1) / m + x1;
+			if (x > minX && x < maxX) return true;
+			x = (maxY - y1) / m + x1;
+			if (x > minX && x < maxX) return true;
+			return false;
+		}
+
+		/// <summary>Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds.</summary>
+		public bool AabbIntersectsSkeleton (SkeletonBounds bounds) {
+			return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY;
+		}
+
+		/// <summary>Returns true if the polygon contains the point.</summary>
+		public bool ContainsPoint (Polygon polygon, float x, float y) {
+			float[] vertices = polygon.Vertices;
+			int nn = polygon.Count;
+
+			int prevIndex = nn - 2;
+			bool inside = false;
+			for (int ii = 0; ii < nn; ii += 2) {
+				float vertexY = vertices[ii + 1];
+				float prevY = vertices[prevIndex + 1];
+				if ((vertexY < y && prevY >= y) || (prevY < y && vertexY >= y)) {
+					float vertexX = vertices[ii];
+					if (vertexX + (y - vertexY) / (prevY - vertexY) * (vertices[prevIndex] - vertexX) < x) inside = !inside;
+				}
+				prevIndex = ii;
+			}
+			return inside;
+		}
+
+		/// <summary>Returns the first bounding box attachment that contains the point, or null. When doing many checks, it is usually more
+		/// efficient to only call this method if {@link #aabbContainsPoint(float, float)} returns true.</summary>
+		public BoundingBoxAttachment ContainsPoint (float x, float y) {
+			ExposedList<Polygon> polygons = Polygons;
+			for (int i = 0, n = polygons.Count; i < n; i++)
+				if (ContainsPoint(polygons.Items[i], x, y)) return BoundingBoxes.Items[i];
+			return null;
+		}
+
+		/// <summary>Returns the first bounding box attachment that contains the line segment, or null. When doing many checks, it is usually
+		/// more efficient to only call this method if {@link #aabbIntersectsSegment(float, float, float, float)} returns true.</summary>
+		public BoundingBoxAttachment IntersectsSegment (float x1, float y1, float x2, float y2) {
+			ExposedList<Polygon> polygons = Polygons;
+			for (int i = 0, n = polygons.Count; i < n; i++)
+				if (IntersectsSegment(polygons.Items[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i];
+			return null;
+		}
+
+		/// <summary>Returns true if the polygon contains the line segment.</summary>
+		public bool IntersectsSegment (Polygon polygon, float x1, float y1, float x2, float y2) {
+			float[] vertices = polygon.Vertices;
+			int nn = polygon.Count;
+
+			float width12 = x1 - x2, height12 = y1 - y2;
+			float det1 = x1 * y2 - y1 * x2;
+			float x3 = vertices[nn - 2], y3 = vertices[nn - 1];
+			for (int ii = 0; ii < nn; ii += 2) {
+				float x4 = vertices[ii], y4 = vertices[ii + 1];
+				float det2 = x3 * y4 - y3 * x4;
+				float width34 = x3 - x4, height34 = y3 - y4;
+				float det3 = width12 * height34 - height12 * width34;
+				float x = (det1 * width34 - width12 * det2) / det3;
+				if (((x >= x3 && x <= x4) || (x >= x4 && x <= x3)) && ((x >= x1 && x <= x2) || (x >= x2 && x <= x1))) {
+					float y = (det1 * height34 - height12 * det2) / det3;
+					if (((y >= y3 && y <= y4) || (y >= y4 && y <= y3)) && ((y >= y1 && y <= y2) || (y >= y2 && y <= y1))) return true;
+				}
+				x3 = x4;
+				y3 = y4;
+			}
+			return false;
+		}
+
+		public Polygon GetPolygon (BoundingBoxAttachment attachment) {
+			int index = BoundingBoxes.IndexOf(attachment);
+			return index == -1 ? null : Polygons.Items[index];
+		}
+	}
+
+	public class Polygon {
+		public float[] Vertices { get; set; }
+		public int Count { get; set; }
+
+		public Polygon () {
+			Vertices = new float[16];
+		}
+	}
+}

+ 229 - 229
spine-csharp/src/SkeletonData.cs

@@ -1,229 +1,229 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-using System;
-
-namespace Spine {
-	
-	/// <summary>Stores the setup pose and all of the stateless data for a skeleton.</summary>
-	public class SkeletonData {
-		internal string name;
-		internal ExposedList<BoneData> bones = new ExposedList<BoneData>(); // Ordered parents first
-		internal ExposedList<SlotData> slots = new ExposedList<SlotData>(); // Setup pose draw order.
-		internal ExposedList<Skin> skins = new ExposedList<Skin>();
-		internal Skin defaultSkin;
-		internal ExposedList<EventData> events = new ExposedList<EventData>();
-		internal ExposedList<Animation> animations = new ExposedList<Animation>();
-		internal ExposedList<IkConstraintData> ikConstraints = new ExposedList<IkConstraintData>();
-		internal ExposedList<TransformConstraintData> transformConstraints = new ExposedList<TransformConstraintData>();
-		internal ExposedList<PathConstraintData> pathConstraints = new ExposedList<PathConstraintData>();
-		internal float width, height;
-		internal string version, hash;
-
-		// Nonessential.
-		internal float fps;
-		internal string imagesPath, audioPath;
-
-		public string Name { get { return name; } set { name = value; } }
-
-		/// <summary>The skeleton's bones, sorted parent first. The root bone is always the first bone.</summary>
-		public ExposedList<BoneData> Bones { get { return bones; } }
-
-		public ExposedList<SlotData> Slots { get { return slots; } }
-
-		/// <summary>All skins, including the default skin.</summary>
-		public ExposedList<Skin> Skins { get { return skins; } set { skins = value; } }
-
-		/// <summary>
-		/// The skeleton's default skin.
-		/// By default this skin contains all attachments that were not in a skin in Spine.
-		/// </summary>
-		/// <return>May be null.</return>
-		public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } }
-
-		public ExposedList<EventData> Events { get { return events; } set { events = value; } }
-		public ExposedList<Animation> Animations { get { return animations; } set { animations = value; } }
-		public ExposedList<IkConstraintData> IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } }
-		public ExposedList<TransformConstraintData> TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } }
-		public ExposedList<PathConstraintData> PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } }
-
-		public float Width { get { return width; } set { width = value; } }
-		public float Height { get { return height; } set { height = value; } }
-		/// <summary>The Spine version used to export this data, or null.</summary>
-		public string Version { get { return version; } set { version = value; } }
-		public string Hash { get { return hash; } set { hash = value; } }
-
-		/// <summary>The path to the images directory as defined in Spine. Available only when nonessential data was exported. May be null</summary>
-		public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } }
-		
-		/// <summary>The path to the audio directory defined in Spine. Available only when nonessential data was exported. May be null.</summary>
-		public string AudioPath { get { return audioPath; } set { audioPath = value; } }
-
-		/// <summary>
-		/// The dopesheet FPS in Spine. Available only when nonessential data was exported.</summary>
-		public float Fps { get { return fps; } set { fps = value; } }
-
-		// --- Bones.
-
-		/// <summary>
-		/// Finds a bone by comparing each bone's name.
-		/// It is more efficient to cache the results of this method than to call it multiple times.</summary>
-		/// <returns>May be null.</returns>
-		public BoneData FindBone (string boneName) {
-			if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
-			var bones = this.bones;
-			var bonesItems = bones.Items;
-			for (int i = 0, n = bones.Count; i < n; i++) {
-				BoneData bone = bonesItems[i];
-				if (bone.name == boneName) return bone;
-			}
-			return null;
-		}
-
-		/// <returns>-1 if the bone was not found.</returns>
-		public int FindBoneIndex (string boneName) {
-			if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
-			var bones = this.bones;
-			var bonesItems = bones.Items;
-			for (int i = 0, n = bones.Count; i < n; i++)
-				if (bonesItems[i].name == boneName) return i;
-			return -1;
-		}
-
-		// --- Slots.
-
-		/// <returns>May be null.</returns>
-		public SlotData FindSlot (string slotName) {
-			if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
-			ExposedList<SlotData> slots = this.slots;
-			for (int i = 0, n = slots.Count; i < n; i++) {
-				SlotData slot = slots.Items[i];
-				if (slot.name == slotName) return slot;
-			}
-			return null;
-		}
-
-		/// <returns>-1 if the slot was not found.</returns>
-		public int FindSlotIndex (string slotName) {
-			if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
-			ExposedList<SlotData> slots = this.slots;
-			for (int i = 0, n = slots.Count; i < n; i++)
-				if (slots.Items[i].name == slotName) return i;
-			return -1;
-		}
-
-		// --- Skins.
-
-		/// <returns>May be null.</returns>
-		public Skin FindSkin (string skinName) {
-			if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null.");
-			foreach (Skin skin in skins)
-				if (skin.name == skinName) return skin;
-			return null;
-		}
-
-		// --- Events.
-
-		/// <returns>May be null.</returns>
-		public EventData FindEvent (string eventDataName) {
-			if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null.");
-			foreach (EventData eventData in events)
-				if (eventData.name == eventDataName) return eventData;
-			return null;
-		}
-
-		// --- Animations.
-
-		/// <returns>May be null.</returns>
-		public Animation FindAnimation (string animationName) {
-			if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null.");
-			ExposedList<Animation> animations = this.animations;
-			for (int i = 0, n = animations.Count; i < n; i++) {
-				Animation animation = animations.Items[i];
-				if (animation.name == animationName) return animation;
-			}
-			return null;
-		}
-
-		// --- IK constraints.
-
-		/// <returns>May be null.</returns>
-		public IkConstraintData FindIkConstraint (string constraintName) {
-			if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
-			ExposedList<IkConstraintData> ikConstraints = this.ikConstraints;
-			for (int i = 0, n = ikConstraints.Count; i < n; i++) {
-				IkConstraintData ikConstraint = ikConstraints.Items[i];
-				if (ikConstraint.name == constraintName) return ikConstraint;
-			}
-			return null;
-		}
-
-		// --- Transform constraints.
-
-		/// <returns>May be null.</returns>
-		public TransformConstraintData FindTransformConstraint (string constraintName) {
-			if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
-			ExposedList<TransformConstraintData> transformConstraints = this.transformConstraints;
-			for (int i = 0, n = transformConstraints.Count; i < n; i++) {
-				TransformConstraintData transformConstraint = transformConstraints.Items[i];
-				if (transformConstraint.name == constraintName) return transformConstraint;
-			}
-			return null;
-		}
-
-		// --- Path constraints.
-
-		/// <returns>May be null.</returns>
-		public PathConstraintData FindPathConstraint (string constraintName) {
-			if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
-			ExposedList<PathConstraintData> pathConstraints = this.pathConstraints;
-			for (int i = 0, n = pathConstraints.Count; i < n; i++) {
-				PathConstraintData constraint = pathConstraints.Items[i];
-				if (constraint.name.Equals(constraintName)) return constraint;
-			}
-			return null;
-		}
-
-		/// <returns>-1 if the path constraint was not found.</returns>
-		public int FindPathConstraintIndex (string pathConstraintName) {
-			if (pathConstraintName == null) throw new ArgumentNullException("pathConstraintName", "pathConstraintName cannot be null.");
-			ExposedList<PathConstraintData> pathConstraints = this.pathConstraints;
-			for (int i = 0, n = pathConstraints.Count; i < n; i++)
-				if (pathConstraints.Items[i].name.Equals(pathConstraintName)) return i;
-			return -1;
-		}
-
-		// ---
-
-		override public string ToString () {
-			return name ?? base.ToString();
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+	
+	/// <summary>Stores the setup pose and all of the stateless data for a skeleton.</summary>
+	public class SkeletonData {
+		internal string name;
+		internal ExposedList<BoneData> bones = new ExposedList<BoneData>(); // Ordered parents first
+		internal ExposedList<SlotData> slots = new ExposedList<SlotData>(); // Setup pose draw order.
+		internal ExposedList<Skin> skins = new ExposedList<Skin>();
+		internal Skin defaultSkin;
+		internal ExposedList<EventData> events = new ExposedList<EventData>();
+		internal ExposedList<Animation> animations = new ExposedList<Animation>();
+		internal ExposedList<IkConstraintData> ikConstraints = new ExposedList<IkConstraintData>();
+		internal ExposedList<TransformConstraintData> transformConstraints = new ExposedList<TransformConstraintData>();
+		internal ExposedList<PathConstraintData> pathConstraints = new ExposedList<PathConstraintData>();
+		internal float width, height;
+		internal string version, hash;
+
+		// Nonessential.
+		internal float fps;
+		internal string imagesPath, audioPath;
+
+		public string Name { get { return name; } set { name = value; } }
+
+		/// <summary>The skeleton's bones, sorted parent first. The root bone is always the first bone.</summary>
+		public ExposedList<BoneData> Bones { get { return bones; } }
+
+		public ExposedList<SlotData> Slots { get { return slots; } }
+
+		/// <summary>All skins, including the default skin.</summary>
+		public ExposedList<Skin> Skins { get { return skins; } set { skins = value; } }
+
+		/// <summary>
+		/// The skeleton's default skin.
+		/// By default this skin contains all attachments that were not in a skin in Spine.
+		/// </summary>
+		/// <return>May be null.</return>
+		public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } }
+
+		public ExposedList<EventData> Events { get { return events; } set { events = value; } }
+		public ExposedList<Animation> Animations { get { return animations; } set { animations = value; } }
+		public ExposedList<IkConstraintData> IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } }
+		public ExposedList<TransformConstraintData> TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } }
+		public ExposedList<PathConstraintData> PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } }
+
+		public float Width { get { return width; } set { width = value; } }
+		public float Height { get { return height; } set { height = value; } }
+		/// <summary>The Spine version used to export this data, or null.</summary>
+		public string Version { get { return version; } set { version = value; } }
+		public string Hash { get { return hash; } set { hash = value; } }
+
+		/// <summary>The path to the images directory as defined in Spine. Available only when nonessential data was exported. May be null</summary>
+		public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } }
+		
+		/// <summary>The path to the audio directory defined in Spine. Available only when nonessential data was exported. May be null.</summary>
+		public string AudioPath { get { return audioPath; } set { audioPath = value; } }
+
+		/// <summary>
+		/// The dopesheet FPS in Spine. Available only when nonessential data was exported.</summary>
+		public float Fps { get { return fps; } set { fps = value; } }
+
+		// --- Bones.
+
+		/// <summary>
+		/// Finds a bone by comparing each bone's name.
+		/// It is more efficient to cache the results of this method than to call it multiple times.</summary>
+		/// <returns>May be null.</returns>
+		public BoneData FindBone (string boneName) {
+			if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
+			var bones = this.bones;
+			var bonesItems = bones.Items;
+			for (int i = 0, n = bones.Count; i < n; i++) {
+				BoneData bone = bonesItems[i];
+				if (bone.name == boneName) return bone;
+			}
+			return null;
+		}
+
+		/// <returns>-1 if the bone was not found.</returns>
+		public int FindBoneIndex (string boneName) {
+			if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
+			var bones = this.bones;
+			var bonesItems = bones.Items;
+			for (int i = 0, n = bones.Count; i < n; i++)
+				if (bonesItems[i].name == boneName) return i;
+			return -1;
+		}
+
+		// --- Slots.
+
+		/// <returns>May be null.</returns>
+		public SlotData FindSlot (string slotName) {
+			if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
+			ExposedList<SlotData> slots = this.slots;
+			for (int i = 0, n = slots.Count; i < n; i++) {
+				SlotData slot = slots.Items[i];
+				if (slot.name == slotName) return slot;
+			}
+			return null;
+		}
+
+		/// <returns>-1 if the slot was not found.</returns>
+		public int FindSlotIndex (string slotName) {
+			if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
+			ExposedList<SlotData> slots = this.slots;
+			for (int i = 0, n = slots.Count; i < n; i++)
+				if (slots.Items[i].name == slotName) return i;
+			return -1;
+		}
+
+		// --- Skins.
+
+		/// <returns>May be null.</returns>
+		public Skin FindSkin (string skinName) {
+			if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null.");
+			foreach (Skin skin in skins)
+				if (skin.name == skinName) return skin;
+			return null;
+		}
+
+		// --- Events.
+
+		/// <returns>May be null.</returns>
+		public EventData FindEvent (string eventDataName) {
+			if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null.");
+			foreach (EventData eventData in events)
+				if (eventData.name == eventDataName) return eventData;
+			return null;
+		}
+
+		// --- Animations.
+
+		/// <returns>May be null.</returns>
+		public Animation FindAnimation (string animationName) {
+			if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null.");
+			ExposedList<Animation> animations = this.animations;
+			for (int i = 0, n = animations.Count; i < n; i++) {
+				Animation animation = animations.Items[i];
+				if (animation.name == animationName) return animation;
+			}
+			return null;
+		}
+
+		// --- IK constraints.
+
+		/// <returns>May be null.</returns>
+		public IkConstraintData FindIkConstraint (string constraintName) {
+			if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
+			ExposedList<IkConstraintData> ikConstraints = this.ikConstraints;
+			for (int i = 0, n = ikConstraints.Count; i < n; i++) {
+				IkConstraintData ikConstraint = ikConstraints.Items[i];
+				if (ikConstraint.name == constraintName) return ikConstraint;
+			}
+			return null;
+		}
+
+		// --- Transform constraints.
+
+		/// <returns>May be null.</returns>
+		public TransformConstraintData FindTransformConstraint (string constraintName) {
+			if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
+			ExposedList<TransformConstraintData> transformConstraints = this.transformConstraints;
+			for (int i = 0, n = transformConstraints.Count; i < n; i++) {
+				TransformConstraintData transformConstraint = transformConstraints.Items[i];
+				if (transformConstraint.name == constraintName) return transformConstraint;
+			}
+			return null;
+		}
+
+		// --- Path constraints.
+
+		/// <returns>May be null.</returns>
+		public PathConstraintData FindPathConstraint (string constraintName) {
+			if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
+			ExposedList<PathConstraintData> pathConstraints = this.pathConstraints;
+			for (int i = 0, n = pathConstraints.Count; i < n; i++) {
+				PathConstraintData constraint = pathConstraints.Items[i];
+				if (constraint.name.Equals(constraintName)) return constraint;
+			}
+			return null;
+		}
+
+		/// <returns>-1 if the path constraint was not found.</returns>
+		public int FindPathConstraintIndex (string pathConstraintName) {
+			if (pathConstraintName == null) throw new ArgumentNullException("pathConstraintName", "pathConstraintName cannot be null.");
+			ExposedList<PathConstraintData> pathConstraints = this.pathConstraints;
+			for (int i = 0, n = pathConstraints.Count; i < n; i++)
+				if (pathConstraints.Items[i].name.Equals(pathConstraintName)) return i;
+			return -1;
+		}
+
+		// ---
+
+		override public string ToString () {
+			return name ?? base.ToString();
+		}
+	}
+}

+ 882 - 882
spine-csharp/src/SkeletonJson.cs

@@ -1,882 +1,882 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-#if (UNITY_5 || UNITY_5_3_OR_NEWER || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1)
-#define IS_UNITY
-#endif
-
-using System;
-using System.IO;
-using System.Collections.Generic;
-
-#if WINDOWS_STOREAPP
-using System.Threading.Tasks;
-using Windows.Storage;
-#endif
-
-namespace Spine {
-	public class SkeletonJson {
-		public float Scale { get; set; }
-
-		private AttachmentLoader attachmentLoader;
-		private List<LinkedMesh> linkedMeshes = new List<LinkedMesh>();
-
-		public SkeletonJson (params Atlas[] atlasArray)
-			: this(new AtlasAttachmentLoader(atlasArray)) {
-		}
-
-		public SkeletonJson (AttachmentLoader attachmentLoader) {
-			if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null.");
-			this.attachmentLoader = attachmentLoader;
-			Scale = 1;
-		}
-
-		#if !IS_UNITY && WINDOWS_STOREAPP
-		private async Task<SkeletonData> ReadFile(string path) {
-			var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
-			var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false);
-			using (var reader = new StreamReader(await file.OpenStreamForReadAsync().ConfigureAwait(false))) {
-				SkeletonData skeletonData = ReadSkeletonData(reader);
-				skeletonData.Name = Path.GetFileNameWithoutExtension(path);
-				return skeletonData;
-			}
-		}
-
-		public SkeletonData ReadSkeletonData (string path) {
-			return this.ReadFile(path).Result;
-		}
-		#else
-		public SkeletonData ReadSkeletonData (string path) {
-		#if WINDOWS_PHONE
-			using (var reader = new StreamReader(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) {
-		#else
-			using (var reader = new StreamReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))) {
-		#endif
-				SkeletonData skeletonData = ReadSkeletonData(reader);
-				skeletonData.name = Path.GetFileNameWithoutExtension(path);
-				return skeletonData;
-			}
-		}
-		#endif
-
-		public SkeletonData ReadSkeletonData (TextReader reader) {
-			if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null.");
-
-			float scale = this.Scale;
-			var skeletonData = new SkeletonData();
-
-			var root = Json.Deserialize(reader) as Dictionary<string, Object>;
-			if (root == null) throw new Exception("Invalid JSON.");
-
-			// Skeleton.
-			if (root.ContainsKey("skeleton")) {
-				var skeletonMap = (Dictionary<string, Object>)root["skeleton"];
-				skeletonData.hash = (string)skeletonMap["hash"];
-				skeletonData.version = (string)skeletonMap["spine"];
-				skeletonData.width = GetFloat(skeletonMap, "width", 0);
-				skeletonData.height = GetFloat(skeletonMap, "height", 0);
-				skeletonData.fps = GetFloat(skeletonMap, "fps", 0);
-				skeletonData.imagesPath = GetString(skeletonMap, "images", null);
-				skeletonData.audioPath = GetString(skeletonMap, "audio", null);
-			}
-
-			// Bones.
-			foreach (Dictionary<string, Object> boneMap in (List<Object>)root["bones"]) {
-				BoneData parent = null;
-				if (boneMap.ContainsKey("parent")) {
-					parent = skeletonData.FindBone((string)boneMap["parent"]);
-					if (parent == null)
-						throw new Exception("Parent bone not found: " + boneMap["parent"]);
-				}
-				var data = new BoneData(skeletonData.Bones.Count, (string)boneMap["name"], parent);
-				data.length = GetFloat(boneMap, "length", 0) * scale;
-				data.x = GetFloat(boneMap, "x", 0) * scale;
-				data.y = GetFloat(boneMap, "y", 0) * scale;
-				data.rotation = GetFloat(boneMap, "rotation", 0);
-				data.scaleX = GetFloat(boneMap, "scaleX", 1);
-				data.scaleY = GetFloat(boneMap, "scaleY", 1);
-				data.shearX = GetFloat(boneMap, "shearX", 0);
-				data.shearY = GetFloat(boneMap, "shearY", 0);
-
-				string tm = GetString(boneMap, "transform", TransformMode.Normal.ToString());
-				data.transformMode = (TransformMode)Enum.Parse(typeof(TransformMode), tm, true);
-
-				skeletonData.bones.Add(data);
-			}
-
-			// Slots.
-			if (root.ContainsKey("slots")) {
-				foreach (Dictionary<string, Object> slotMap in (List<Object>)root["slots"]) {
-					var slotName = (string)slotMap["name"];
-					var boneName = (string)slotMap["bone"];
-					BoneData boneData = skeletonData.FindBone(boneName);
-					if (boneData == null) throw new Exception("Slot bone not found: " + boneName);
-					var data = new SlotData(skeletonData.Slots.Count, slotName, boneData);
-
-					if (slotMap.ContainsKey("color")) {
-						string color = (string)slotMap["color"];
-						data.r = ToColor(color, 0);
-						data.g = ToColor(color, 1);
-						data.b = ToColor(color, 2);
-						data.a = ToColor(color, 3);
-					}
-
-					if (slotMap.ContainsKey("dark")) {
-						var color2 = (string)slotMap["dark"];
-						data.r2 = ToColor(color2, 0, 6); // expectedLength = 6. ie. "RRGGBB"
-						data.g2 = ToColor(color2, 1, 6);
-						data.b2 = ToColor(color2, 2, 6);
-						data.hasSecondColor = true;
-					}
-						
-					data.attachmentName = GetString(slotMap, "attachment", null);
-					if (slotMap.ContainsKey("blend"))
-						data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (string)slotMap["blend"], true);
-					else
-						data.blendMode = BlendMode.Normal;
-					skeletonData.slots.Add(data);
-				}
-			}
-
-			// IK constraints.
-			if (root.ContainsKey("ik")) {
-				foreach (Dictionary<string, Object> constraintMap in (List<Object>)root["ik"]) {
-					IkConstraintData data = new IkConstraintData((string)constraintMap["name"]);
-					data.order = GetInt(constraintMap, "order", 0);
-
-					foreach (string boneName in (List<Object>)constraintMap["bones"]) {
-						BoneData bone = skeletonData.FindBone(boneName);
-						if (bone == null) throw new Exception("IK constraint bone not found: " + boneName);
-						data.bones.Add(bone);
-					}
-
-					string targetName = (string)constraintMap["target"];
-					data.target = skeletonData.FindBone(targetName);
-					if (data.target == null) throw new Exception("Target bone not found: " + targetName);
-					data.mix = GetFloat(constraintMap, "mix", 1);
-					data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1;
-					data.compress = GetBoolean(constraintMap, "compress", false);
-					data.stretch = GetBoolean(constraintMap, "stretch", false);
-					data.uniform = GetBoolean(constraintMap, "uniform", false);
-
-					skeletonData.ikConstraints.Add(data);
-				}
-			}
-
-			// Transform constraints.
-			if (root.ContainsKey("transform")) {
-				foreach (Dictionary<string, Object> constraintMap in (List<Object>)root["transform"]) {
-					TransformConstraintData data = new TransformConstraintData((string)constraintMap["name"]);
-					data.order = GetInt(constraintMap, "order", 0);
-
-					foreach (string boneName in (List<Object>)constraintMap["bones"]) {
-						BoneData bone = skeletonData.FindBone(boneName);
-						if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName);
-						data.bones.Add(bone);
-					}
-
-					string targetName = (string)constraintMap["target"];
-					data.target = skeletonData.FindBone(targetName);
-					if (data.target == null) throw new Exception("Target bone not found: " + targetName);
-
-					data.local = GetBoolean(constraintMap, "local", false);
-					data.relative = GetBoolean(constraintMap, "relative", false);
-
-					data.offsetRotation = GetFloat(constraintMap, "rotation", 0);
-					data.offsetX = GetFloat(constraintMap, "x", 0) * scale;
-					data.offsetY = GetFloat(constraintMap, "y", 0) * scale;
-					data.offsetScaleX = GetFloat(constraintMap, "scaleX", 0);
-					data.offsetScaleY = GetFloat(constraintMap, "scaleY", 0);
-					data.offsetShearY = GetFloat(constraintMap, "shearY", 0);
-
-					data.rotateMix = GetFloat(constraintMap, "rotateMix", 1);
-					data.translateMix = GetFloat(constraintMap, "translateMix", 1);
-					data.scaleMix = GetFloat(constraintMap, "scaleMix", 1);
-					data.shearMix = GetFloat(constraintMap, "shearMix", 1);
-
-					skeletonData.transformConstraints.Add(data);
-				}
-			}
-
-			// Path constraints.
-			if(root.ContainsKey("path")) {
-				foreach (Dictionary<string, Object> constraintMap in (List<Object>)root["path"]) {
-					PathConstraintData data = new PathConstraintData((string)constraintMap["name"]);
-					data.order = GetInt(constraintMap, "order", 0);
-
-					foreach (string boneName in (List<Object>)constraintMap["bones"]) {
-						BoneData bone = skeletonData.FindBone(boneName);
-						if (bone == null) throw new Exception("Path bone not found: " + boneName);
-						data.bones.Add(bone);
-					}
-
-					string targetName = (string)constraintMap["target"];
-					data.target = skeletonData.FindSlot(targetName);
-					if (data.target == null) throw new Exception("Target slot not found: " + targetName);
-
-					data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true);
-					data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true);
-					data.rotateMode = (RotateMode)Enum.Parse(typeof(RotateMode), GetString(constraintMap, "rotateMode", "tangent"), true);
-					data.offsetRotation = GetFloat(constraintMap, "rotation", 0);
-					data.position = GetFloat(constraintMap, "position", 0);
-					if (data.positionMode == PositionMode.Fixed) data.position *= scale;
-					data.spacing = GetFloat(constraintMap, "spacing", 0);
-					if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale;
-					data.rotateMix = GetFloat(constraintMap, "rotateMix", 1);
-					data.translateMix = GetFloat(constraintMap, "translateMix", 1);
-
-					skeletonData.pathConstraints.Add(data);
-				}
-			}
-
-			// Skins.
-			if (root.ContainsKey("skins")) {
-					foreach (KeyValuePair<string, Object> skinMap in (Dictionary<string, Object>)root["skins"]) {
-					var skin = new Skin(skinMap.Key);
-					foreach (KeyValuePair<string, Object> slotEntry in (Dictionary<string, Object>)skinMap.Value) {
-						int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key);
-						foreach (KeyValuePair<string, Object> entry in ((Dictionary<string, Object>)slotEntry.Value)) {
-							try {
-								Attachment attachment = ReadAttachment((Dictionary<string, Object>)entry.Value, skin, slotIndex, entry.Key, skeletonData);
-								if (attachment != null) skin.AddAttachment(slotIndex, entry.Key, attachment);
-							} catch (Exception e) {
-								throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e);
-							}
-						} 
-					}
-					skeletonData.skins.Add(skin);
-					if (skin.name == "default") skeletonData.defaultSkin = skin;
-				}
-			}
-
-			// Linked meshes.
-			for (int i = 0, n = linkedMeshes.Count; i < n; i++) {
-				LinkedMesh linkedMesh = linkedMeshes[i];
-				Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin);
-				if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin);
-				Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent);
-				if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent);
-				linkedMesh.mesh.ParentMesh = (MeshAttachment)parent;
-				linkedMesh.mesh.UpdateUVs();
-			}
-			linkedMeshes.Clear();
-
-			// Events.
-			if (root.ContainsKey("events")) {
-				foreach (KeyValuePair<string, Object> entry in (Dictionary<string, Object>)root["events"]) {
-					var entryMap = (Dictionary<string, Object>)entry.Value;
-					var data = new EventData(entry.Key);
-					data.Int = GetInt(entryMap, "int", 0);
-					data.Float = GetFloat(entryMap, "float", 0);
-					data.String = GetString(entryMap, "string", string.Empty);
-					data.AudioPath = GetString(entryMap, "audio", null);
-					if (data.AudioPath != null) {
-						data.Volume = GetFloat(entryMap, "volume", 1);
-						data.Balance = GetFloat(entryMap, "balance", 0);
-					}
-					skeletonData.events.Add(data);
-				}
-			}
-
-			// Animations.
-			if (root.ContainsKey("animations")) {
-				foreach (KeyValuePair<string, Object> entry in (Dictionary<string, Object>)root["animations"]) {
-					try {
-						ReadAnimation((Dictionary<string, Object>)entry.Value, entry.Key, skeletonData);
-					} catch (Exception e) {
-						throw new Exception("Error reading animation: " + entry.Key, e);
-					}
-				}   
-			}
-
-			skeletonData.bones.TrimExcess();
-			skeletonData.slots.TrimExcess();
-			skeletonData.skins.TrimExcess();
-			skeletonData.events.TrimExcess();
-			skeletonData.animations.TrimExcess();
-			skeletonData.ikConstraints.TrimExcess();
-			return skeletonData;
-		}
-
-		private Attachment ReadAttachment (Dictionary<string, Object> map, Skin skin, int slotIndex, string name, SkeletonData skeletonData) {
-			float scale = this.Scale;
-			name = GetString(map, "name", name);
-
-			var typeName = GetString(map, "type", "region");
-			if (typeName == "skinnedmesh") typeName = "weightedmesh";
-			if (typeName == "weightedmesh") typeName = "mesh";
-			if (typeName == "weightedlinkedmesh") typeName = "linkedmesh";
-			var type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, true);
-
-			string path = GetString(map, "path", name);
-
-			switch (type) {
-			case AttachmentType.Region:
-				RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path);
-				if (region == null) return null;
-				region.Path = path;
-				region.x = GetFloat(map, "x", 0) * scale;
-				region.y = GetFloat(map, "y", 0) * scale;
-				region.scaleX = GetFloat(map, "scaleX", 1);
-				region.scaleY = GetFloat(map, "scaleY", 1);
-				region.rotation = GetFloat(map, "rotation", 0);
-				region.width = GetFloat(map, "width", 32) * scale;
-				region.height = GetFloat(map, "height", 32) * scale;
-
-				if (map.ContainsKey("color")) {
-					var color = (string)map["color"];
-					region.r = ToColor(color, 0);
-					region.g = ToColor(color, 1);
-					region.b = ToColor(color, 2);
-					region.a = ToColor(color, 3);
-				}
-
-				region.UpdateOffset();
-				return region;
-			case AttachmentType.Boundingbox:
-				BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name);
-				if (box == null) return null;
-				ReadVertices(map, box, GetInt(map, "vertexCount", 0) << 1);
-				return box;
-			case AttachmentType.Mesh:
-			case AttachmentType.Linkedmesh: {
-					MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path);
-					if (mesh == null) return null;
-					mesh.Path = path;
-
-					if (map.ContainsKey("color")) {
-						var color = (string)map["color"];
-						mesh.r = ToColor(color, 0);
-						mesh.g = ToColor(color, 1);
-						mesh.b = ToColor(color, 2);
-						mesh.a = ToColor(color, 3);
-					}
-
-					mesh.Width = GetFloat(map, "width", 0) * scale;
-					mesh.Height = GetFloat(map, "height", 0) * scale;
-
-					string parent = GetString(map, "parent", null);
-					if (parent != null) {
-						mesh.InheritDeform = GetBoolean(map, "deform", true);
-						linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent));
-						return mesh;
-					}
-
-					float[] uvs = GetFloatArray(map, "uvs", 1);
-					ReadVertices(map, mesh, uvs.Length);
-					mesh.triangles = GetIntArray(map, "triangles");
-					mesh.regionUVs = uvs;
-					mesh.UpdateUVs();
-
-					if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) * 2;
-					if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges");
-					return mesh;
-				}
-			case AttachmentType.Path: {
-					PathAttachment pathAttachment = attachmentLoader.NewPathAttachment(skin, name);
-					if (pathAttachment == null) return null;
-					pathAttachment.closed = GetBoolean(map, "closed", false);
-					pathAttachment.constantSpeed = GetBoolean(map, "constantSpeed", true);
-
-					int vertexCount = GetInt(map, "vertexCount", 0);
-					ReadVertices(map, pathAttachment, vertexCount << 1);
-
-					// potential BOZO see Java impl
-					pathAttachment.lengths = GetFloatArray(map, "lengths", scale);
-					return pathAttachment;
-				}
-			case AttachmentType.Point: {
-					PointAttachment point = attachmentLoader.NewPointAttachment(skin, name);
-					if (point == null) return null;
-					point.x = GetFloat(map, "x", 0) * scale;
-					point.y = GetFloat(map, "y", 0) * scale;
-					point.rotation = GetFloat(map, "rotation", 0);
-
-					//string color = GetString(map, "color", null);
-					//if (color != null) point.color = color;
-					return point;
-				}
-			case AttachmentType.Clipping: {
-					ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name);
-					if (clip == null) return null;
-
-					string end = GetString(map, "end", null);
-					if (end != null) {
-						SlotData slot = skeletonData.FindSlot(end);
-						if (slot == null) throw new Exception("Clipping end slot not found: " + end);
-						clip.EndSlot = slot;
-					}
-
-					ReadVertices(map, clip, GetInt(map, "vertexCount", 0) << 1);
-
-					//string color = GetString(map, "color", null);
-					// if (color != null) clip.color = color;
-					return clip;
-				}
-			}
-			return null;
-		}
-
-		private void ReadVertices (Dictionary<string, Object> map, VertexAttachment attachment, int verticesLength) {
-			attachment.WorldVerticesLength = verticesLength;
-			float[] vertices = GetFloatArray(map, "vertices", 1);
-			float scale = Scale;
-			if (verticesLength == vertices.Length) {
-				if (scale != 1) {
-					for (int i = 0; i < vertices.Length; i++) {
-						vertices[i] *= scale;
-					}
-				}
-				attachment.vertices = vertices;
-				return;
-			}
-			ExposedList<float> weights = new ExposedList<float>(verticesLength * 3 * 3);
-			ExposedList<int> bones = new ExposedList<int>(verticesLength * 3);
-			for (int i = 0, n = vertices.Length; i < n;) {
-				int boneCount = (int)vertices[i++];
-				bones.Add(boneCount);
-				for (int nn = i + boneCount * 4; i < nn; i += 4) {
-					bones.Add((int)vertices[i]);
-					weights.Add(vertices[i + 1] * this.Scale);
-					weights.Add(vertices[i + 2] * this.Scale);
-					weights.Add(vertices[i + 3]);
-				}
-			}
-			attachment.bones = bones.ToArray();
-			attachment.vertices = weights.ToArray();
-		}
-
-		private void ReadAnimation (Dictionary<string, Object> map, string name, SkeletonData skeletonData) {
-			var scale = this.Scale;
-			var timelines = new ExposedList<Timeline>();
-			float duration = 0;
-
-			// Slot timelines.
-			if (map.ContainsKey("slots")) {
-				foreach (KeyValuePair<string, Object> entry in (Dictionary<string, Object>)map["slots"]) {
-					string slotName = entry.Key;
-					int slotIndex = skeletonData.FindSlotIndex(slotName);
-					var timelineMap = (Dictionary<string, Object>)entry.Value;
-					foreach (KeyValuePair<string, Object> timelineEntry in timelineMap) {
-						var values = (List<Object>)timelineEntry.Value;
-						var timelineName = (string)timelineEntry.Key;
-						if (timelineName == "attachment") {
-							var timeline = new AttachmentTimeline(values.Count);
-							timeline.slotIndex = slotIndex;
-
-							int frameIndex = 0;
-							foreach (Dictionary<string, Object> valueMap in values) {
-								float time = (float)valueMap["time"];
-								timeline.SetFrame(frameIndex++, time, (string)valueMap["name"]);
-							}
-							timelines.Add(timeline);
-							duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
-
-						} else if (timelineName == "color") {
-							var timeline = new ColorTimeline(values.Count);
-							timeline.slotIndex = slotIndex;
-
-							int frameIndex = 0;
-							foreach (Dictionary<string, Object> valueMap in values) {
-								float time = (float)valueMap["time"];
-								string c = (string)valueMap["color"];
-								timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3));
-								ReadCurve(valueMap, timeline, frameIndex);
-								frameIndex++;
-							}
-							timelines.Add(timeline);
-							duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]);
-
-						} else if (timelineName == "twoColor") {
-							var timeline = new TwoColorTimeline(values.Count);
-							timeline.slotIndex = slotIndex;
-
-							int frameIndex = 0;
-							foreach (Dictionary<string, Object> valueMap in values) {
-								float time = (float)valueMap["time"];
-								string light = (string)valueMap["light"];
-								string dark = (string)valueMap["dark"];
-								timeline.SetFrame(frameIndex, time, ToColor(light, 0), ToColor(light, 1), ToColor(light, 2), ToColor(light, 3),
-									ToColor(dark, 0, 6), ToColor(dark, 1, 6), ToColor(dark, 2, 6));
-								ReadCurve(valueMap, timeline, frameIndex);
-								frameIndex++;
-							}
-							timelines.Add(timeline);
-							duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TwoColorTimeline.ENTRIES]);
-
-						} else
-							throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")");
-					}
-				}
-			}
-
-			// Bone timelines.
-			if (map.ContainsKey("bones")) {
-				foreach (KeyValuePair<string, Object> entry in (Dictionary<string, Object>)map["bones"]) {
-					string boneName = entry.Key;
-					int boneIndex = skeletonData.FindBoneIndex(boneName);
-					if (boneIndex == -1) throw new Exception("Bone not found: " + boneName);
-					var timelineMap = (Dictionary<string, Object>)entry.Value;
-					foreach (KeyValuePair<string, Object> timelineEntry in timelineMap) {
-						var values = (List<Object>)timelineEntry.Value;
-						var timelineName = (string)timelineEntry.Key;
-						if (timelineName == "rotate") {
-							var timeline = new RotateTimeline(values.Count);
-							timeline.boneIndex = boneIndex;
-
-							int frameIndex = 0;
-							foreach (Dictionary<string, Object> valueMap in values) {
-								timeline.SetFrame(frameIndex, (float)valueMap["time"], (float)valueMap["angle"]);
-								ReadCurve(valueMap, timeline, frameIndex);
-								frameIndex++;
-							}
-							timelines.Add(timeline);
-							duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * RotateTimeline.ENTRIES]);
-
-						} else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") {
-							TranslateTimeline timeline;
-							float timelineScale = 1;
-							if (timelineName == "scale")
-								timeline = new ScaleTimeline(values.Count);
-							else if (timelineName == "shear")
-								timeline = new ShearTimeline(values.Count);
-							else {
-								timeline = new TranslateTimeline(values.Count);
-								timelineScale = scale;
-							}
-							timeline.boneIndex = boneIndex;
-
-							int frameIndex = 0;
-							foreach (Dictionary<string, Object> valueMap in values) {
-								float time = (float)valueMap["time"];
-								float x = GetFloat(valueMap, "x", 0);
-								float y = GetFloat(valueMap, "y", 0);
-								timeline.SetFrame(frameIndex, time, x * timelineScale, y * timelineScale);
-								ReadCurve(valueMap, timeline, frameIndex);
-								frameIndex++;
-							}
-							timelines.Add(timeline);
-							duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TranslateTimeline.ENTRIES]);
-
-						} else
-							throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")");
-					}
-				}
-			}
-
-			// IK constraint timelines.
-			if (map.ContainsKey("ik")) {
-				foreach (KeyValuePair<string, Object> constraintMap in (Dictionary<string, Object>)map["ik"]) {
-					IkConstraintData constraint = skeletonData.FindIkConstraint(constraintMap.Key);
-					var values = (List<Object>)constraintMap.Value;
-					var timeline = new IkConstraintTimeline(values.Count);
-					timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint);
-					int frameIndex = 0;
-					foreach (Dictionary<string, Object> valueMap in values) {
-						timeline.SetFrame(
-							frameIndex,
-							(float)valueMap["time"],
-							GetFloat(valueMap, "mix", 1),
-							GetBoolean(valueMap, "bendPositive", true) ? 1 : -1,
-							GetBoolean(valueMap, "compress", true),
-							GetBoolean(valueMap, "stretch", false)
-						);
-						ReadCurve(valueMap, timeline, frameIndex);
-						frameIndex++;
-					}
-					timelines.Add(timeline);
-					duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * IkConstraintTimeline.ENTRIES]);
-				}
-			}
-
-			// Transform constraint timelines.
-			if (map.ContainsKey("transform")) {
-				foreach (KeyValuePair<string, Object> constraintMap in (Dictionary<string, Object>)map["transform"]) {
-					TransformConstraintData constraint = skeletonData.FindTransformConstraint(constraintMap.Key);
-					var values = (List<Object>)constraintMap.Value;
-					var timeline = new TransformConstraintTimeline(values.Count);
-					timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint);
-					int frameIndex = 0;
-					foreach (Dictionary<string, Object> valueMap in values) {
-						float time = (float)valueMap["time"];
-						float rotateMix = GetFloat(valueMap, "rotateMix", 1);
-						float translateMix = GetFloat(valueMap, "translateMix", 1);
-						float scaleMix = GetFloat(valueMap, "scaleMix", 1);
-						float shearMix = GetFloat(valueMap, "shearMix", 1);
-						timeline.SetFrame(frameIndex, time, rotateMix, translateMix, scaleMix, shearMix);
-						ReadCurve(valueMap, timeline, frameIndex);
-						frameIndex++;
-					}
-					timelines.Add(timeline);
-					duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TransformConstraintTimeline.ENTRIES]);
-				}
-			}
-
-			// Path constraint timelines.
-			if (map.ContainsKey("paths")) {
-				foreach (KeyValuePair<string, Object> constraintMap in (Dictionary<string, Object>)map["paths"]) {
-					int index = skeletonData.FindPathConstraintIndex(constraintMap.Key);
-					if (index == -1) throw new Exception("Path constraint not found: " + constraintMap.Key);
-					PathConstraintData data = skeletonData.pathConstraints.Items[index];
-					var timelineMap = (Dictionary<string, Object>)constraintMap.Value;
-					foreach (KeyValuePair<string, Object> timelineEntry in timelineMap) {
-						var values = (List<Object>)timelineEntry.Value;
-						var timelineName = (string)timelineEntry.Key;
-						if (timelineName == "position" || timelineName == "spacing") {
-							PathConstraintPositionTimeline timeline;
-							float timelineScale = 1;
-							if (timelineName == "spacing") {
-								timeline = new PathConstraintSpacingTimeline(values.Count);
-								if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale;
-							}
-							else {
-								timeline = new PathConstraintPositionTimeline(values.Count);
-								if (data.positionMode == PositionMode.Fixed) timelineScale = scale;
-							}
-							timeline.pathConstraintIndex = index;
-							int frameIndex = 0;
-							foreach (Dictionary<string, Object> valueMap in values) {
-								timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, timelineName, 0) * timelineScale);
-								ReadCurve(valueMap, timeline, frameIndex);
-								frameIndex++;
-							}
-							timelines.Add(timeline);
-							duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintPositionTimeline.ENTRIES]);
-						}
-						else if (timelineName == "mix") {
-							PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(values.Count);
-							timeline.pathConstraintIndex = index;
-							int frameIndex = 0;
-							foreach (Dictionary<string, Object> valueMap in values) {
-								timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, "rotateMix", 1), GetFloat(valueMap, "translateMix", 1));
-								ReadCurve(valueMap, timeline, frameIndex);
-								frameIndex++;
-							}
-							timelines.Add(timeline);
-							duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintMixTimeline.ENTRIES]);
-						}
-					}
-				}
-			}
-
-			// Deform timelines.
-			if (map.ContainsKey("deform")) {
-				foreach (KeyValuePair<string, Object> deformMap in (Dictionary<string, Object>)map["deform"]) {
-					Skin skin = skeletonData.FindSkin(deformMap.Key);
-					foreach (KeyValuePair<string, Object> slotMap in (Dictionary<string, Object>)deformMap.Value) {
-						int slotIndex = skeletonData.FindSlotIndex(slotMap.Key);
-						if (slotIndex == -1) throw new Exception("Slot not found: " + slotMap.Key);
-						foreach (KeyValuePair<string, Object> timelineMap in (Dictionary<string, Object>)slotMap.Value) {
-							var values = (List<Object>)timelineMap.Value;
-							VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, timelineMap.Key);
-							if (attachment == null) throw new Exception("Deform attachment not found: " + timelineMap.Key);
-							bool weighted = attachment.bones != null;
-							float[] vertices = attachment.vertices;
-							int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length;
-
-							var timeline = new DeformTimeline(values.Count);
-							timeline.slotIndex = slotIndex;
-							timeline.attachment = attachment;
-							
-							int frameIndex = 0;
-							foreach (Dictionary<string, Object> valueMap in values) {
-								float[] deform;
-								if (!valueMap.ContainsKey("vertices")) {
-									deform = weighted ? new float[deformLength] : vertices;
-								} else {
-									deform = new float[deformLength];
-									int start = GetInt(valueMap, "offset", 0);
-									float[] verticesValue = GetFloatArray(valueMap, "vertices", 1);
-									Array.Copy(verticesValue, 0, deform, start, verticesValue.Length);
-									if (scale != 1) {
-										for (int i = start, n = i + verticesValue.Length; i < n; i++)
-											deform[i] *= scale;
-									}
-
-									if (!weighted) {
-										for (int i = 0; i < deformLength; i++)
-											deform[i] += vertices[i];
-									}
-								}
-
-								timeline.SetFrame(frameIndex, (float)valueMap["time"], deform);
-								ReadCurve(valueMap, timeline, frameIndex);
-								frameIndex++;
-							}
-							timelines.Add(timeline);
-							duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
-						}
-					}
-				}
-			}
-
-			// Draw order timeline.
-			if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) {
-				var values = (List<Object>)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"];
-				var timeline = new DrawOrderTimeline(values.Count);
-				int slotCount = skeletonData.slots.Count;
-				int frameIndex = 0;
-				foreach (Dictionary<string, Object> drawOrderMap in values) {
-					int[] drawOrder = null;
-					if (drawOrderMap.ContainsKey("offsets")) {
-						drawOrder = new int[slotCount];
-						for (int i = slotCount - 1; i >= 0; i--)
-							drawOrder[i] = -1;
-						var offsets = (List<Object>)drawOrderMap["offsets"];
-						int[] unchanged = new int[slotCount - offsets.Count];
-						int originalIndex = 0, unchangedIndex = 0;
-						foreach (Dictionary<string, Object> offsetMap in offsets) {
-							int slotIndex = skeletonData.FindSlotIndex((string)offsetMap["slot"]);
-							if (slotIndex == -1) throw new Exception("Slot not found: " + offsetMap["slot"]);
-							// Collect unchanged items.
-							while (originalIndex != slotIndex)
-								unchanged[unchangedIndex++] = originalIndex++;
-							// Set changed items.
-							int index = originalIndex + (int)(float)offsetMap["offset"];
-							drawOrder[index] = originalIndex++;
-						}
-						// Collect remaining unchanged items.
-						while (originalIndex < slotCount)
-							unchanged[unchangedIndex++] = originalIndex++;
-						// Fill in unchanged items.
-						for (int i = slotCount - 1; i >= 0; i--)
-							if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex];
-					}
-					timeline.SetFrame(frameIndex++, (float)drawOrderMap["time"], drawOrder);
-				}
-				timelines.Add(timeline);
-				duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
-			}
-
-			// Event timeline.
-			if (map.ContainsKey("events")) {
-				var eventsMap = (List<Object>)map["events"];
-				var timeline = new EventTimeline(eventsMap.Count);
-				int frameIndex = 0;
-				foreach (Dictionary<string, Object> eventMap in eventsMap) {
-					EventData eventData = skeletonData.FindEvent((string)eventMap["name"]);
-					if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]);
-					var e = new Event((float)eventMap["time"], eventData) {
-						intValue = GetInt(eventMap, "int", eventData.Int),
-						floatValue = GetFloat(eventMap, "float", eventData.Float),
-						stringValue = GetString(eventMap, "string", eventData.String)
-					};
-					if (e.data.AudioPath != null) {
-						e.volume = GetFloat(eventMap, "volume", eventData.Volume);
-						e.balance = GetFloat(eventMap, "balance", eventData.Balance);
-					}
-					timeline.SetFrame(frameIndex++, e);
-				}
-				timelines.Add(timeline);
-				duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
-			}
-
-			timelines.TrimExcess();
-			skeletonData.animations.Add(new Animation(name, timelines, duration));
-		}
-
-		static void ReadCurve (Dictionary<string, Object> valueMap, CurveTimeline timeline, int frameIndex) {
-			if (!valueMap.ContainsKey("curve"))
-				return;
-			Object curveObject = valueMap["curve"];
-			if (curveObject.Equals("stepped"))
-				timeline.SetStepped(frameIndex);
-			else {
-				var curve = curveObject as List<Object>;
-				if (curve != null)
-					timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]);
-			}
-		}
-
-		internal class LinkedMesh {
-			internal string parent, skin;
-			internal int slotIndex;
-			internal MeshAttachment mesh;
-
-			public LinkedMesh (MeshAttachment mesh, string skin, int slotIndex, string parent) {
-				this.mesh = mesh;
-				this.skin = skin;
-				this.slotIndex = slotIndex;
-				this.parent = parent;
-			}
-		}
-
-		static float[] GetFloatArray(Dictionary<string, Object> map, string name, float scale) {
-			var list = (List<Object>)map[name];
-			var values = new float[list.Count];
-			if (scale == 1) {
-				for (int i = 0, n = list.Count; i < n; i++)
-					values[i] = (float)list[i];
-			} else {
-				for (int i = 0, n = list.Count; i < n; i++)
-					values[i] = (float)list[i] * scale;
-			}
-			return values;
-		}
-
-		static int[] GetIntArray(Dictionary<string, Object> map, string name) {
-			var list = (List<Object>)map[name];
-			var values = new int[list.Count];
-			for (int i = 0, n = list.Count; i < n; i++)
-				values[i] = (int)(float)list[i];
-			return values;
-		}
-
-		static float GetFloat(Dictionary<string, Object> map, string name, float defaultValue) {
-			if (!map.ContainsKey(name))
-				return defaultValue;
-			return (float)map[name];
-		}
-
-		static int GetInt(Dictionary<string, Object> map, string name, int defaultValue) {
-			if (!map.ContainsKey(name))
-				return defaultValue;
-			return (int)(float)map[name];
-		}
-
-		static bool GetBoolean(Dictionary<string, Object> map, string name, bool defaultValue) {
-			if (!map.ContainsKey(name))
-				return defaultValue;
-			return (bool)map[name];
-		}
-
-		static string GetString(Dictionary<string, Object> map, string name, string defaultValue) {
-			if (!map.ContainsKey(name))
-				return defaultValue;
-			return (string)map[name];
-		}
-
-		static float ToColor(string hexString, int colorIndex, int expectedLength = 8) {
-			if (hexString.Length != expectedLength)
-				throw new ArgumentException("Color hexidecimal length must be " + expectedLength + ", recieved: " + hexString, "hexString");
-			return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255;
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+#if (UNITY_5 || UNITY_5_3_OR_NEWER || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1)
+#define IS_UNITY
+#endif
+
+using System;
+using System.IO;
+using System.Collections.Generic;
+
+#if WINDOWS_STOREAPP
+using System.Threading.Tasks;
+using Windows.Storage;
+#endif
+
+namespace Spine {
+	public class SkeletonJson {
+		public float Scale { get; set; }
+
+		private AttachmentLoader attachmentLoader;
+		private List<LinkedMesh> linkedMeshes = new List<LinkedMesh>();
+
+		public SkeletonJson (params Atlas[] atlasArray)
+			: this(new AtlasAttachmentLoader(atlasArray)) {
+		}
+
+		public SkeletonJson (AttachmentLoader attachmentLoader) {
+			if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null.");
+			this.attachmentLoader = attachmentLoader;
+			Scale = 1;
+		}
+
+		#if !IS_UNITY && WINDOWS_STOREAPP
+		private async Task<SkeletonData> ReadFile(string path) {
+			var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
+			var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false);
+			using (var reader = new StreamReader(await file.OpenStreamForReadAsync().ConfigureAwait(false))) {
+				SkeletonData skeletonData = ReadSkeletonData(reader);
+				skeletonData.Name = Path.GetFileNameWithoutExtension(path);
+				return skeletonData;
+			}
+		}
+
+		public SkeletonData ReadSkeletonData (string path) {
+			return this.ReadFile(path).Result;
+		}
+		#else
+		public SkeletonData ReadSkeletonData (string path) {
+		#if WINDOWS_PHONE
+			using (var reader = new StreamReader(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) {
+		#else
+			using (var reader = new StreamReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))) {
+		#endif
+				SkeletonData skeletonData = ReadSkeletonData(reader);
+				skeletonData.name = Path.GetFileNameWithoutExtension(path);
+				return skeletonData;
+			}
+		}
+		#endif
+
+		public SkeletonData ReadSkeletonData (TextReader reader) {
+			if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null.");
+
+			float scale = this.Scale;
+			var skeletonData = new SkeletonData();
+
+			var root = Json.Deserialize(reader) as Dictionary<string, Object>;
+			if (root == null) throw new Exception("Invalid JSON.");
+
+			// Skeleton.
+			if (root.ContainsKey("skeleton")) {
+				var skeletonMap = (Dictionary<string, Object>)root["skeleton"];
+				skeletonData.hash = (string)skeletonMap["hash"];
+				skeletonData.version = (string)skeletonMap["spine"];
+				skeletonData.width = GetFloat(skeletonMap, "width", 0);
+				skeletonData.height = GetFloat(skeletonMap, "height", 0);
+				skeletonData.fps = GetFloat(skeletonMap, "fps", 0);
+				skeletonData.imagesPath = GetString(skeletonMap, "images", null);
+				skeletonData.audioPath = GetString(skeletonMap, "audio", null);
+			}
+
+			// Bones.
+			foreach (Dictionary<string, Object> boneMap in (List<Object>)root["bones"]) {
+				BoneData parent = null;
+				if (boneMap.ContainsKey("parent")) {
+					parent = skeletonData.FindBone((string)boneMap["parent"]);
+					if (parent == null)
+						throw new Exception("Parent bone not found: " + boneMap["parent"]);
+				}
+				var data = new BoneData(skeletonData.Bones.Count, (string)boneMap["name"], parent);
+				data.length = GetFloat(boneMap, "length", 0) * scale;
+				data.x = GetFloat(boneMap, "x", 0) * scale;
+				data.y = GetFloat(boneMap, "y", 0) * scale;
+				data.rotation = GetFloat(boneMap, "rotation", 0);
+				data.scaleX = GetFloat(boneMap, "scaleX", 1);
+				data.scaleY = GetFloat(boneMap, "scaleY", 1);
+				data.shearX = GetFloat(boneMap, "shearX", 0);
+				data.shearY = GetFloat(boneMap, "shearY", 0);
+
+				string tm = GetString(boneMap, "transform", TransformMode.Normal.ToString());
+				data.transformMode = (TransformMode)Enum.Parse(typeof(TransformMode), tm, true);
+
+				skeletonData.bones.Add(data);
+			}
+
+			// Slots.
+			if (root.ContainsKey("slots")) {
+				foreach (Dictionary<string, Object> slotMap in (List<Object>)root["slots"]) {
+					var slotName = (string)slotMap["name"];
+					var boneName = (string)slotMap["bone"];
+					BoneData boneData = skeletonData.FindBone(boneName);
+					if (boneData == null) throw new Exception("Slot bone not found: " + boneName);
+					var data = new SlotData(skeletonData.Slots.Count, slotName, boneData);
+
+					if (slotMap.ContainsKey("color")) {
+						string color = (string)slotMap["color"];
+						data.r = ToColor(color, 0);
+						data.g = ToColor(color, 1);
+						data.b = ToColor(color, 2);
+						data.a = ToColor(color, 3);
+					}
+
+					if (slotMap.ContainsKey("dark")) {
+						var color2 = (string)slotMap["dark"];
+						data.r2 = ToColor(color2, 0, 6); // expectedLength = 6. ie. "RRGGBB"
+						data.g2 = ToColor(color2, 1, 6);
+						data.b2 = ToColor(color2, 2, 6);
+						data.hasSecondColor = true;
+					}
+						
+					data.attachmentName = GetString(slotMap, "attachment", null);
+					if (slotMap.ContainsKey("blend"))
+						data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (string)slotMap["blend"], true);
+					else
+						data.blendMode = BlendMode.Normal;
+					skeletonData.slots.Add(data);
+				}
+			}
+
+			// IK constraints.
+			if (root.ContainsKey("ik")) {
+				foreach (Dictionary<string, Object> constraintMap in (List<Object>)root["ik"]) {
+					IkConstraintData data = new IkConstraintData((string)constraintMap["name"]);
+					data.order = GetInt(constraintMap, "order", 0);
+
+					foreach (string boneName in (List<Object>)constraintMap["bones"]) {
+						BoneData bone = skeletonData.FindBone(boneName);
+						if (bone == null) throw new Exception("IK constraint bone not found: " + boneName);
+						data.bones.Add(bone);
+					}
+
+					string targetName = (string)constraintMap["target"];
+					data.target = skeletonData.FindBone(targetName);
+					if (data.target == null) throw new Exception("Target bone not found: " + targetName);
+					data.mix = GetFloat(constraintMap, "mix", 1);
+					data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1;
+					data.compress = GetBoolean(constraintMap, "compress", false);
+					data.stretch = GetBoolean(constraintMap, "stretch", false);
+					data.uniform = GetBoolean(constraintMap, "uniform", false);
+
+					skeletonData.ikConstraints.Add(data);
+				}
+			}
+
+			// Transform constraints.
+			if (root.ContainsKey("transform")) {
+				foreach (Dictionary<string, Object> constraintMap in (List<Object>)root["transform"]) {
+					TransformConstraintData data = new TransformConstraintData((string)constraintMap["name"]);
+					data.order = GetInt(constraintMap, "order", 0);
+
+					foreach (string boneName in (List<Object>)constraintMap["bones"]) {
+						BoneData bone = skeletonData.FindBone(boneName);
+						if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName);
+						data.bones.Add(bone);
+					}
+
+					string targetName = (string)constraintMap["target"];
+					data.target = skeletonData.FindBone(targetName);
+					if (data.target == null) throw new Exception("Target bone not found: " + targetName);
+
+					data.local = GetBoolean(constraintMap, "local", false);
+					data.relative = GetBoolean(constraintMap, "relative", false);
+
+					data.offsetRotation = GetFloat(constraintMap, "rotation", 0);
+					data.offsetX = GetFloat(constraintMap, "x", 0) * scale;
+					data.offsetY = GetFloat(constraintMap, "y", 0) * scale;
+					data.offsetScaleX = GetFloat(constraintMap, "scaleX", 0);
+					data.offsetScaleY = GetFloat(constraintMap, "scaleY", 0);
+					data.offsetShearY = GetFloat(constraintMap, "shearY", 0);
+
+					data.rotateMix = GetFloat(constraintMap, "rotateMix", 1);
+					data.translateMix = GetFloat(constraintMap, "translateMix", 1);
+					data.scaleMix = GetFloat(constraintMap, "scaleMix", 1);
+					data.shearMix = GetFloat(constraintMap, "shearMix", 1);
+
+					skeletonData.transformConstraints.Add(data);
+				}
+			}
+
+			// Path constraints.
+			if(root.ContainsKey("path")) {
+				foreach (Dictionary<string, Object> constraintMap in (List<Object>)root["path"]) {
+					PathConstraintData data = new PathConstraintData((string)constraintMap["name"]);
+					data.order = GetInt(constraintMap, "order", 0);
+
+					foreach (string boneName in (List<Object>)constraintMap["bones"]) {
+						BoneData bone = skeletonData.FindBone(boneName);
+						if (bone == null) throw new Exception("Path bone not found: " + boneName);
+						data.bones.Add(bone);
+					}
+
+					string targetName = (string)constraintMap["target"];
+					data.target = skeletonData.FindSlot(targetName);
+					if (data.target == null) throw new Exception("Target slot not found: " + targetName);
+
+					data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true);
+					data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true);
+					data.rotateMode = (RotateMode)Enum.Parse(typeof(RotateMode), GetString(constraintMap, "rotateMode", "tangent"), true);
+					data.offsetRotation = GetFloat(constraintMap, "rotation", 0);
+					data.position = GetFloat(constraintMap, "position", 0);
+					if (data.positionMode == PositionMode.Fixed) data.position *= scale;
+					data.spacing = GetFloat(constraintMap, "spacing", 0);
+					if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale;
+					data.rotateMix = GetFloat(constraintMap, "rotateMix", 1);
+					data.translateMix = GetFloat(constraintMap, "translateMix", 1);
+
+					skeletonData.pathConstraints.Add(data);
+				}
+			}
+
+			// Skins.
+			if (root.ContainsKey("skins")) {
+					foreach (KeyValuePair<string, Object> skinMap in (Dictionary<string, Object>)root["skins"]) {
+					var skin = new Skin(skinMap.Key);
+					foreach (KeyValuePair<string, Object> slotEntry in (Dictionary<string, Object>)skinMap.Value) {
+						int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key);
+						foreach (KeyValuePair<string, Object> entry in ((Dictionary<string, Object>)slotEntry.Value)) {
+							try {
+								Attachment attachment = ReadAttachment((Dictionary<string, Object>)entry.Value, skin, slotIndex, entry.Key, skeletonData);
+								if (attachment != null) skin.AddAttachment(slotIndex, entry.Key, attachment);
+							} catch (Exception e) {
+								throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e);
+							}
+						} 
+					}
+					skeletonData.skins.Add(skin);
+					if (skin.name == "default") skeletonData.defaultSkin = skin;
+				}
+			}
+
+			// Linked meshes.
+			for (int i = 0, n = linkedMeshes.Count; i < n; i++) {
+				LinkedMesh linkedMesh = linkedMeshes[i];
+				Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin);
+				if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin);
+				Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent);
+				if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent);
+				linkedMesh.mesh.ParentMesh = (MeshAttachment)parent;
+				linkedMesh.mesh.UpdateUVs();
+			}
+			linkedMeshes.Clear();
+
+			// Events.
+			if (root.ContainsKey("events")) {
+				foreach (KeyValuePair<string, Object> entry in (Dictionary<string, Object>)root["events"]) {
+					var entryMap = (Dictionary<string, Object>)entry.Value;
+					var data = new EventData(entry.Key);
+					data.Int = GetInt(entryMap, "int", 0);
+					data.Float = GetFloat(entryMap, "float", 0);
+					data.String = GetString(entryMap, "string", string.Empty);
+					data.AudioPath = GetString(entryMap, "audio", null);
+					if (data.AudioPath != null) {
+						data.Volume = GetFloat(entryMap, "volume", 1);
+						data.Balance = GetFloat(entryMap, "balance", 0);
+					}
+					skeletonData.events.Add(data);
+				}
+			}
+
+			// Animations.
+			if (root.ContainsKey("animations")) {
+				foreach (KeyValuePair<string, Object> entry in (Dictionary<string, Object>)root["animations"]) {
+					try {
+						ReadAnimation((Dictionary<string, Object>)entry.Value, entry.Key, skeletonData);
+					} catch (Exception e) {
+						throw new Exception("Error reading animation: " + entry.Key, e);
+					}
+				}   
+			}
+
+			skeletonData.bones.TrimExcess();
+			skeletonData.slots.TrimExcess();
+			skeletonData.skins.TrimExcess();
+			skeletonData.events.TrimExcess();
+			skeletonData.animations.TrimExcess();
+			skeletonData.ikConstraints.TrimExcess();
+			return skeletonData;
+		}
+
+		private Attachment ReadAttachment (Dictionary<string, Object> map, Skin skin, int slotIndex, string name, SkeletonData skeletonData) {
+			float scale = this.Scale;
+			name = GetString(map, "name", name);
+
+			var typeName = GetString(map, "type", "region");
+			if (typeName == "skinnedmesh") typeName = "weightedmesh";
+			if (typeName == "weightedmesh") typeName = "mesh";
+			if (typeName == "weightedlinkedmesh") typeName = "linkedmesh";
+			var type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, true);
+
+			string path = GetString(map, "path", name);
+
+			switch (type) {
+			case AttachmentType.Region:
+				RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path);
+				if (region == null) return null;
+				region.Path = path;
+				region.x = GetFloat(map, "x", 0) * scale;
+				region.y = GetFloat(map, "y", 0) * scale;
+				region.scaleX = GetFloat(map, "scaleX", 1);
+				region.scaleY = GetFloat(map, "scaleY", 1);
+				region.rotation = GetFloat(map, "rotation", 0);
+				region.width = GetFloat(map, "width", 32) * scale;
+				region.height = GetFloat(map, "height", 32) * scale;
+
+				if (map.ContainsKey("color")) {
+					var color = (string)map["color"];
+					region.r = ToColor(color, 0);
+					region.g = ToColor(color, 1);
+					region.b = ToColor(color, 2);
+					region.a = ToColor(color, 3);
+				}
+
+				region.UpdateOffset();
+				return region;
+			case AttachmentType.Boundingbox:
+				BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name);
+				if (box == null) return null;
+				ReadVertices(map, box, GetInt(map, "vertexCount", 0) << 1);
+				return box;
+			case AttachmentType.Mesh:
+			case AttachmentType.Linkedmesh: {
+					MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path);
+					if (mesh == null) return null;
+					mesh.Path = path;
+
+					if (map.ContainsKey("color")) {
+						var color = (string)map["color"];
+						mesh.r = ToColor(color, 0);
+						mesh.g = ToColor(color, 1);
+						mesh.b = ToColor(color, 2);
+						mesh.a = ToColor(color, 3);
+					}
+
+					mesh.Width = GetFloat(map, "width", 0) * scale;
+					mesh.Height = GetFloat(map, "height", 0) * scale;
+
+					string parent = GetString(map, "parent", null);
+					if (parent != null) {
+						mesh.InheritDeform = GetBoolean(map, "deform", true);
+						linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent));
+						return mesh;
+					}
+
+					float[] uvs = GetFloatArray(map, "uvs", 1);
+					ReadVertices(map, mesh, uvs.Length);
+					mesh.triangles = GetIntArray(map, "triangles");
+					mesh.regionUVs = uvs;
+					mesh.UpdateUVs();
+
+					if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) * 2;
+					if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges");
+					return mesh;
+				}
+			case AttachmentType.Path: {
+					PathAttachment pathAttachment = attachmentLoader.NewPathAttachment(skin, name);
+					if (pathAttachment == null) return null;
+					pathAttachment.closed = GetBoolean(map, "closed", false);
+					pathAttachment.constantSpeed = GetBoolean(map, "constantSpeed", true);
+
+					int vertexCount = GetInt(map, "vertexCount", 0);
+					ReadVertices(map, pathAttachment, vertexCount << 1);
+
+					// potential BOZO see Java impl
+					pathAttachment.lengths = GetFloatArray(map, "lengths", scale);
+					return pathAttachment;
+				}
+			case AttachmentType.Point: {
+					PointAttachment point = attachmentLoader.NewPointAttachment(skin, name);
+					if (point == null) return null;
+					point.x = GetFloat(map, "x", 0) * scale;
+					point.y = GetFloat(map, "y", 0) * scale;
+					point.rotation = GetFloat(map, "rotation", 0);
+
+					//string color = GetString(map, "color", null);
+					//if (color != null) point.color = color;
+					return point;
+				}
+			case AttachmentType.Clipping: {
+					ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name);
+					if (clip == null) return null;
+
+					string end = GetString(map, "end", null);
+					if (end != null) {
+						SlotData slot = skeletonData.FindSlot(end);
+						if (slot == null) throw new Exception("Clipping end slot not found: " + end);
+						clip.EndSlot = slot;
+					}
+
+					ReadVertices(map, clip, GetInt(map, "vertexCount", 0) << 1);
+
+					//string color = GetString(map, "color", null);
+					// if (color != null) clip.color = color;
+					return clip;
+				}
+			}
+			return null;
+		}
+
+		private void ReadVertices (Dictionary<string, Object> map, VertexAttachment attachment, int verticesLength) {
+			attachment.WorldVerticesLength = verticesLength;
+			float[] vertices = GetFloatArray(map, "vertices", 1);
+			float scale = Scale;
+			if (verticesLength == vertices.Length) {
+				if (scale != 1) {
+					for (int i = 0; i < vertices.Length; i++) {
+						vertices[i] *= scale;
+					}
+				}
+				attachment.vertices = vertices;
+				return;
+			}
+			ExposedList<float> weights = new ExposedList<float>(verticesLength * 3 * 3);
+			ExposedList<int> bones = new ExposedList<int>(verticesLength * 3);
+			for (int i = 0, n = vertices.Length; i < n;) {
+				int boneCount = (int)vertices[i++];
+				bones.Add(boneCount);
+				for (int nn = i + boneCount * 4; i < nn; i += 4) {
+					bones.Add((int)vertices[i]);
+					weights.Add(vertices[i + 1] * this.Scale);
+					weights.Add(vertices[i + 2] * this.Scale);
+					weights.Add(vertices[i + 3]);
+				}
+			}
+			attachment.bones = bones.ToArray();
+			attachment.vertices = weights.ToArray();
+		}
+
+		private void ReadAnimation (Dictionary<string, Object> map, string name, SkeletonData skeletonData) {
+			var scale = this.Scale;
+			var timelines = new ExposedList<Timeline>();
+			float duration = 0;
+
+			// Slot timelines.
+			if (map.ContainsKey("slots")) {
+				foreach (KeyValuePair<string, Object> entry in (Dictionary<string, Object>)map["slots"]) {
+					string slotName = entry.Key;
+					int slotIndex = skeletonData.FindSlotIndex(slotName);
+					var timelineMap = (Dictionary<string, Object>)entry.Value;
+					foreach (KeyValuePair<string, Object> timelineEntry in timelineMap) {
+						var values = (List<Object>)timelineEntry.Value;
+						var timelineName = (string)timelineEntry.Key;
+						if (timelineName == "attachment") {
+							var timeline = new AttachmentTimeline(values.Count);
+							timeline.slotIndex = slotIndex;
+
+							int frameIndex = 0;
+							foreach (Dictionary<string, Object> valueMap in values) {
+								float time = (float)valueMap["time"];
+								timeline.SetFrame(frameIndex++, time, (string)valueMap["name"]);
+							}
+							timelines.Add(timeline);
+							duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
+
+						} else if (timelineName == "color") {
+							var timeline = new ColorTimeline(values.Count);
+							timeline.slotIndex = slotIndex;
+
+							int frameIndex = 0;
+							foreach (Dictionary<string, Object> valueMap in values) {
+								float time = (float)valueMap["time"];
+								string c = (string)valueMap["color"];
+								timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3));
+								ReadCurve(valueMap, timeline, frameIndex);
+								frameIndex++;
+							}
+							timelines.Add(timeline);
+							duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]);
+
+						} else if (timelineName == "twoColor") {
+							var timeline = new TwoColorTimeline(values.Count);
+							timeline.slotIndex = slotIndex;
+
+							int frameIndex = 0;
+							foreach (Dictionary<string, Object> valueMap in values) {
+								float time = (float)valueMap["time"];
+								string light = (string)valueMap["light"];
+								string dark = (string)valueMap["dark"];
+								timeline.SetFrame(frameIndex, time, ToColor(light, 0), ToColor(light, 1), ToColor(light, 2), ToColor(light, 3),
+									ToColor(dark, 0, 6), ToColor(dark, 1, 6), ToColor(dark, 2, 6));
+								ReadCurve(valueMap, timeline, frameIndex);
+								frameIndex++;
+							}
+							timelines.Add(timeline);
+							duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TwoColorTimeline.ENTRIES]);
+
+						} else
+							throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")");
+					}
+				}
+			}
+
+			// Bone timelines.
+			if (map.ContainsKey("bones")) {
+				foreach (KeyValuePair<string, Object> entry in (Dictionary<string, Object>)map["bones"]) {
+					string boneName = entry.Key;
+					int boneIndex = skeletonData.FindBoneIndex(boneName);
+					if (boneIndex == -1) throw new Exception("Bone not found: " + boneName);
+					var timelineMap = (Dictionary<string, Object>)entry.Value;
+					foreach (KeyValuePair<string, Object> timelineEntry in timelineMap) {
+						var values = (List<Object>)timelineEntry.Value;
+						var timelineName = (string)timelineEntry.Key;
+						if (timelineName == "rotate") {
+							var timeline = new RotateTimeline(values.Count);
+							timeline.boneIndex = boneIndex;
+
+							int frameIndex = 0;
+							foreach (Dictionary<string, Object> valueMap in values) {
+								timeline.SetFrame(frameIndex, (float)valueMap["time"], (float)valueMap["angle"]);
+								ReadCurve(valueMap, timeline, frameIndex);
+								frameIndex++;
+							}
+							timelines.Add(timeline);
+							duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * RotateTimeline.ENTRIES]);
+
+						} else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") {
+							TranslateTimeline timeline;
+							float timelineScale = 1;
+							if (timelineName == "scale")
+								timeline = new ScaleTimeline(values.Count);
+							else if (timelineName == "shear")
+								timeline = new ShearTimeline(values.Count);
+							else {
+								timeline = new TranslateTimeline(values.Count);
+								timelineScale = scale;
+							}
+							timeline.boneIndex = boneIndex;
+
+							int frameIndex = 0;
+							foreach (Dictionary<string, Object> valueMap in values) {
+								float time = (float)valueMap["time"];
+								float x = GetFloat(valueMap, "x", 0);
+								float y = GetFloat(valueMap, "y", 0);
+								timeline.SetFrame(frameIndex, time, x * timelineScale, y * timelineScale);
+								ReadCurve(valueMap, timeline, frameIndex);
+								frameIndex++;
+							}
+							timelines.Add(timeline);
+							duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TranslateTimeline.ENTRIES]);
+
+						} else
+							throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")");
+					}
+				}
+			}
+
+			// IK constraint timelines.
+			if (map.ContainsKey("ik")) {
+				foreach (KeyValuePair<string, Object> constraintMap in (Dictionary<string, Object>)map["ik"]) {
+					IkConstraintData constraint = skeletonData.FindIkConstraint(constraintMap.Key);
+					var values = (List<Object>)constraintMap.Value;
+					var timeline = new IkConstraintTimeline(values.Count);
+					timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint);
+					int frameIndex = 0;
+					foreach (Dictionary<string, Object> valueMap in values) {
+						timeline.SetFrame(
+							frameIndex,
+							(float)valueMap["time"],
+							GetFloat(valueMap, "mix", 1),
+							GetBoolean(valueMap, "bendPositive", true) ? 1 : -1,
+							GetBoolean(valueMap, "compress", true),
+							GetBoolean(valueMap, "stretch", false)
+						);
+						ReadCurve(valueMap, timeline, frameIndex);
+						frameIndex++;
+					}
+					timelines.Add(timeline);
+					duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * IkConstraintTimeline.ENTRIES]);
+				}
+			}
+
+			// Transform constraint timelines.
+			if (map.ContainsKey("transform")) {
+				foreach (KeyValuePair<string, Object> constraintMap in (Dictionary<string, Object>)map["transform"]) {
+					TransformConstraintData constraint = skeletonData.FindTransformConstraint(constraintMap.Key);
+					var values = (List<Object>)constraintMap.Value;
+					var timeline = new TransformConstraintTimeline(values.Count);
+					timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint);
+					int frameIndex = 0;
+					foreach (Dictionary<string, Object> valueMap in values) {
+						float time = (float)valueMap["time"];
+						float rotateMix = GetFloat(valueMap, "rotateMix", 1);
+						float translateMix = GetFloat(valueMap, "translateMix", 1);
+						float scaleMix = GetFloat(valueMap, "scaleMix", 1);
+						float shearMix = GetFloat(valueMap, "shearMix", 1);
+						timeline.SetFrame(frameIndex, time, rotateMix, translateMix, scaleMix, shearMix);
+						ReadCurve(valueMap, timeline, frameIndex);
+						frameIndex++;
+					}
+					timelines.Add(timeline);
+					duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TransformConstraintTimeline.ENTRIES]);
+				}
+			}
+
+			// Path constraint timelines.
+			if (map.ContainsKey("paths")) {
+				foreach (KeyValuePair<string, Object> constraintMap in (Dictionary<string, Object>)map["paths"]) {
+					int index = skeletonData.FindPathConstraintIndex(constraintMap.Key);
+					if (index == -1) throw new Exception("Path constraint not found: " + constraintMap.Key);
+					PathConstraintData data = skeletonData.pathConstraints.Items[index];
+					var timelineMap = (Dictionary<string, Object>)constraintMap.Value;
+					foreach (KeyValuePair<string, Object> timelineEntry in timelineMap) {
+						var values = (List<Object>)timelineEntry.Value;
+						var timelineName = (string)timelineEntry.Key;
+						if (timelineName == "position" || timelineName == "spacing") {
+							PathConstraintPositionTimeline timeline;
+							float timelineScale = 1;
+							if (timelineName == "spacing") {
+								timeline = new PathConstraintSpacingTimeline(values.Count);
+								if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale;
+							}
+							else {
+								timeline = new PathConstraintPositionTimeline(values.Count);
+								if (data.positionMode == PositionMode.Fixed) timelineScale = scale;
+							}
+							timeline.pathConstraintIndex = index;
+							int frameIndex = 0;
+							foreach (Dictionary<string, Object> valueMap in values) {
+								timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, timelineName, 0) * timelineScale);
+								ReadCurve(valueMap, timeline, frameIndex);
+								frameIndex++;
+							}
+							timelines.Add(timeline);
+							duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintPositionTimeline.ENTRIES]);
+						}
+						else if (timelineName == "mix") {
+							PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(values.Count);
+							timeline.pathConstraintIndex = index;
+							int frameIndex = 0;
+							foreach (Dictionary<string, Object> valueMap in values) {
+								timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, "rotateMix", 1), GetFloat(valueMap, "translateMix", 1));
+								ReadCurve(valueMap, timeline, frameIndex);
+								frameIndex++;
+							}
+							timelines.Add(timeline);
+							duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintMixTimeline.ENTRIES]);
+						}
+					}
+				}
+			}
+
+			// Deform timelines.
+			if (map.ContainsKey("deform")) {
+				foreach (KeyValuePair<string, Object> deformMap in (Dictionary<string, Object>)map["deform"]) {
+					Skin skin = skeletonData.FindSkin(deformMap.Key);
+					foreach (KeyValuePair<string, Object> slotMap in (Dictionary<string, Object>)deformMap.Value) {
+						int slotIndex = skeletonData.FindSlotIndex(slotMap.Key);
+						if (slotIndex == -1) throw new Exception("Slot not found: " + slotMap.Key);
+						foreach (KeyValuePair<string, Object> timelineMap in (Dictionary<string, Object>)slotMap.Value) {
+							var values = (List<Object>)timelineMap.Value;
+							VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, timelineMap.Key);
+							if (attachment == null) throw new Exception("Deform attachment not found: " + timelineMap.Key);
+							bool weighted = attachment.bones != null;
+							float[] vertices = attachment.vertices;
+							int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length;
+
+							var timeline = new DeformTimeline(values.Count);
+							timeline.slotIndex = slotIndex;
+							timeline.attachment = attachment;
+							
+							int frameIndex = 0;
+							foreach (Dictionary<string, Object> valueMap in values) {
+								float[] deform;
+								if (!valueMap.ContainsKey("vertices")) {
+									deform = weighted ? new float[deformLength] : vertices;
+								} else {
+									deform = new float[deformLength];
+									int start = GetInt(valueMap, "offset", 0);
+									float[] verticesValue = GetFloatArray(valueMap, "vertices", 1);
+									Array.Copy(verticesValue, 0, deform, start, verticesValue.Length);
+									if (scale != 1) {
+										for (int i = start, n = i + verticesValue.Length; i < n; i++)
+											deform[i] *= scale;
+									}
+
+									if (!weighted) {
+										for (int i = 0; i < deformLength; i++)
+											deform[i] += vertices[i];
+									}
+								}
+
+								timeline.SetFrame(frameIndex, (float)valueMap["time"], deform);
+								ReadCurve(valueMap, timeline, frameIndex);
+								frameIndex++;
+							}
+							timelines.Add(timeline);
+							duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
+						}
+					}
+				}
+			}
+
+			// Draw order timeline.
+			if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) {
+				var values = (List<Object>)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"];
+				var timeline = new DrawOrderTimeline(values.Count);
+				int slotCount = skeletonData.slots.Count;
+				int frameIndex = 0;
+				foreach (Dictionary<string, Object> drawOrderMap in values) {
+					int[] drawOrder = null;
+					if (drawOrderMap.ContainsKey("offsets")) {
+						drawOrder = new int[slotCount];
+						for (int i = slotCount - 1; i >= 0; i--)
+							drawOrder[i] = -1;
+						var offsets = (List<Object>)drawOrderMap["offsets"];
+						int[] unchanged = new int[slotCount - offsets.Count];
+						int originalIndex = 0, unchangedIndex = 0;
+						foreach (Dictionary<string, Object> offsetMap in offsets) {
+							int slotIndex = skeletonData.FindSlotIndex((string)offsetMap["slot"]);
+							if (slotIndex == -1) throw new Exception("Slot not found: " + offsetMap["slot"]);
+							// Collect unchanged items.
+							while (originalIndex != slotIndex)
+								unchanged[unchangedIndex++] = originalIndex++;
+							// Set changed items.
+							int index = originalIndex + (int)(float)offsetMap["offset"];
+							drawOrder[index] = originalIndex++;
+						}
+						// Collect remaining unchanged items.
+						while (originalIndex < slotCount)
+							unchanged[unchangedIndex++] = originalIndex++;
+						// Fill in unchanged items.
+						for (int i = slotCount - 1; i >= 0; i--)
+							if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex];
+					}
+					timeline.SetFrame(frameIndex++, (float)drawOrderMap["time"], drawOrder);
+				}
+				timelines.Add(timeline);
+				duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
+			}
+
+			// Event timeline.
+			if (map.ContainsKey("events")) {
+				var eventsMap = (List<Object>)map["events"];
+				var timeline = new EventTimeline(eventsMap.Count);
+				int frameIndex = 0;
+				foreach (Dictionary<string, Object> eventMap in eventsMap) {
+					EventData eventData = skeletonData.FindEvent((string)eventMap["name"]);
+					if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]);
+					var e = new Event((float)eventMap["time"], eventData) {
+						intValue = GetInt(eventMap, "int", eventData.Int),
+						floatValue = GetFloat(eventMap, "float", eventData.Float),
+						stringValue = GetString(eventMap, "string", eventData.String)
+					};
+					if (e.data.AudioPath != null) {
+						e.volume = GetFloat(eventMap, "volume", eventData.Volume);
+						e.balance = GetFloat(eventMap, "balance", eventData.Balance);
+					}
+					timeline.SetFrame(frameIndex++, e);
+				}
+				timelines.Add(timeline);
+				duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
+			}
+
+			timelines.TrimExcess();
+			skeletonData.animations.Add(new Animation(name, timelines, duration));
+		}
+
+		static void ReadCurve (Dictionary<string, Object> valueMap, CurveTimeline timeline, int frameIndex) {
+			if (!valueMap.ContainsKey("curve"))
+				return;
+			Object curveObject = valueMap["curve"];
+			if (curveObject.Equals("stepped"))
+				timeline.SetStepped(frameIndex);
+			else {
+				var curve = curveObject as List<Object>;
+				if (curve != null)
+					timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]);
+			}
+		}
+
+		internal class LinkedMesh {
+			internal string parent, skin;
+			internal int slotIndex;
+			internal MeshAttachment mesh;
+
+			public LinkedMesh (MeshAttachment mesh, string skin, int slotIndex, string parent) {
+				this.mesh = mesh;
+				this.skin = skin;
+				this.slotIndex = slotIndex;
+				this.parent = parent;
+			}
+		}
+
+		static float[] GetFloatArray(Dictionary<string, Object> map, string name, float scale) {
+			var list = (List<Object>)map[name];
+			var values = new float[list.Count];
+			if (scale == 1) {
+				for (int i = 0, n = list.Count; i < n; i++)
+					values[i] = (float)list[i];
+			} else {
+				for (int i = 0, n = list.Count; i < n; i++)
+					values[i] = (float)list[i] * scale;
+			}
+			return values;
+		}
+
+		static int[] GetIntArray(Dictionary<string, Object> map, string name) {
+			var list = (List<Object>)map[name];
+			var values = new int[list.Count];
+			for (int i = 0, n = list.Count; i < n; i++)
+				values[i] = (int)(float)list[i];
+			return values;
+		}
+
+		static float GetFloat(Dictionary<string, Object> map, string name, float defaultValue) {
+			if (!map.ContainsKey(name))
+				return defaultValue;
+			return (float)map[name];
+		}
+
+		static int GetInt(Dictionary<string, Object> map, string name, int defaultValue) {
+			if (!map.ContainsKey(name))
+				return defaultValue;
+			return (int)(float)map[name];
+		}
+
+		static bool GetBoolean(Dictionary<string, Object> map, string name, bool defaultValue) {
+			if (!map.ContainsKey(name))
+				return defaultValue;
+			return (bool)map[name];
+		}
+
+		static string GetString(Dictionary<string, Object> map, string name, string defaultValue) {
+			if (!map.ContainsKey(name))
+				return defaultValue;
+			return (string)map[name];
+		}
+
+		static float ToColor(string hexString, int colorIndex, int expectedLength = 8) {
+			if (hexString.Length != expectedLength)
+				throw new ArgumentException("Color hexidecimal length must be " + expectedLength + ", recieved: " + hexString, "hexString");
+			return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255;
+		}
+	}
+}

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

@@ -1,131 +1,131 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-using System;
-using System.Collections.Generic;
-
-namespace Spine {
-	/// <summary>Stores attachments by slot index and attachment name.
-	/// <para>See SkeletonData <see cref="Spine.SkeletonData.DefaultSkin"/>, Skeleton <see cref="Spine.Skeleton.Skin"/>, and 
-	/// <a href="http://esotericsoftware.com/spine-runtime-skins">Runtime skins</a> in the Spine Runtimes Guide.</para>
-	/// </summary>
-	public class Skin {
-		internal string name;
-		private Dictionary<AttachmentKeyTuple, Attachment> attachments =
-			new Dictionary<AttachmentKeyTuple, Attachment>(AttachmentKeyTupleComparer.Instance);
-
-		public string Name { get { return name; } }
-		public Dictionary<AttachmentKeyTuple, Attachment> Attachments { get { return attachments; } }
-
-		public Skin (string name) {
-			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
-			this.name = name;
-		}
-
-		/// <summary>Adds an attachment to the skin for the specified slot index and name. If the name already exists for the slot, the previous value is replaced.</summary>
-		public void AddAttachment (int slotIndex, string name, Attachment attachment) {
-			if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null.");
-			attachments[new AttachmentKeyTuple(slotIndex, name)] = attachment;
-		}
-
-		/// <summary>Returns the attachment for the specified slot index and name, or null.</summary>
-		/// <returns>May be null.</returns>
-		public Attachment GetAttachment (int slotIndex, string name) {
-			Attachment attachment;
-			attachments.TryGetValue(new AttachmentKeyTuple(slotIndex, name), out attachment);
-			return attachment;
-		}
-
-		/// <summary> Removes the attachment in the skin for the specified slot index and name, if any.</summary>
-		public void RemoveAttachment (int slotIndex, string name) {
-			if (slotIndex < 0) throw new ArgumentOutOfRangeException("slotIndex", "slotIndex must be >= 0");
-			attachments.Remove(new AttachmentKeyTuple(slotIndex, name));
-		}
-
-		/// <summary>Finds the skin keys for a given slot. The results are added to the passed List(names).</summary>
-		/// <param name="slotIndex">The target slotIndex. To find the slot index, use <see cref="Spine.Skeleton.FindSlotIndex"/> or <see cref="Spine.SkeletonData.FindSlotIndex"/>
-		/// <param name="names">Found skin key names will be added to this list.</param>
-		public void FindNamesForSlot (int slotIndex, List<string> names) {
-			if (names == null) throw new ArgumentNullException("names", "names cannot be null.");
-			foreach (AttachmentKeyTuple key in attachments.Keys)
-				if (key.slotIndex == slotIndex) names.Add(key.name);
-		}
-
-		/// <summary>Finds the attachments for a given slot. The results are added to the passed List(Attachment).</summary>
-		/// <param name="slotIndex">The target slotIndex. To find the slot index, use <see cref="Spine.Skeleton.FindSlotIndex"/> or <see cref="Spine.SkeletonData.FindSlotIndex"/>
-		/// <param name="attachments">Found Attachments will be added to this list.</param>
-		public void FindAttachmentsForSlot (int slotIndex, List<Attachment> attachments) {
-			if (attachments == null) throw new ArgumentNullException("attachments", "attachments cannot be null.");
-			foreach (KeyValuePair<AttachmentKeyTuple, Attachment> entry in this.attachments)
-				if (entry.Key.slotIndex == slotIndex) attachments.Add(entry.Value);
-		}
-
-		override public string ToString () {
-			return name;
-		}
-
-		/// <summary>Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached.</summary>
-		internal void AttachAll (Skeleton skeleton, Skin oldSkin) {
-			foreach (KeyValuePair<AttachmentKeyTuple, Attachment> entry in oldSkin.attachments) {
-				int slotIndex = entry.Key.slotIndex;
-				Slot slot = skeleton.slots.Items[slotIndex];
-				if (slot.Attachment == entry.Value) {
-					Attachment attachment = GetAttachment(slotIndex, entry.Key.name);
-					if (attachment != null) slot.Attachment = attachment;
-				}
-			}
-		}
-
-		public struct AttachmentKeyTuple {
-			public readonly int slotIndex;
-			public readonly string name;
-			internal readonly int nameHashCode;
-
-			public AttachmentKeyTuple (int slotIndex, string name) {
-				this.slotIndex = slotIndex;
-				this.name = name;
-				nameHashCode = this.name.GetHashCode();
-			}
-		}
-
-		// Avoids boxing in the dictionary.
-		class AttachmentKeyTupleComparer : IEqualityComparer<AttachmentKeyTuple> {
-			internal static readonly AttachmentKeyTupleComparer Instance = new AttachmentKeyTupleComparer();
-
-			bool IEqualityComparer<AttachmentKeyTuple>.Equals (AttachmentKeyTuple o1, AttachmentKeyTuple o2) {
-				return o1.slotIndex == o2.slotIndex && o1.nameHashCode == o2.nameHashCode && string.Equals(o1.name, o2.name, StringComparison.Ordinal);
-			}
-
-			int IEqualityComparer<AttachmentKeyTuple>.GetHashCode (AttachmentKeyTuple o) {
-				return o.slotIndex;
-			}
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+using System.Collections.Generic;
+
+namespace Spine {
+	/// <summary>Stores attachments by slot index and attachment name.
+	/// <para>See SkeletonData <see cref="Spine.SkeletonData.DefaultSkin"/>, Skeleton <see cref="Spine.Skeleton.Skin"/>, and 
+	/// <a href="http://esotericsoftware.com/spine-runtime-skins">Runtime skins</a> in the Spine Runtimes Guide.</para>
+	/// </summary>
+	public class Skin {
+		internal string name;
+		private Dictionary<AttachmentKeyTuple, Attachment> attachments =
+			new Dictionary<AttachmentKeyTuple, Attachment>(AttachmentKeyTupleComparer.Instance);
+
+		public string Name { get { return name; } }
+		public Dictionary<AttachmentKeyTuple, Attachment> Attachments { get { return attachments; } }
+
+		public Skin (string name) {
+			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
+			this.name = name;
+		}
+
+		/// <summary>Adds an attachment to the skin for the specified slot index and name. If the name already exists for the slot, the previous value is replaced.</summary>
+		public void AddAttachment (int slotIndex, string name, Attachment attachment) {
+			if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null.");
+			attachments[new AttachmentKeyTuple(slotIndex, name)] = attachment;
+		}
+
+		/// <summary>Returns the attachment for the specified slot index and name, or null.</summary>
+		/// <returns>May be null.</returns>
+		public Attachment GetAttachment (int slotIndex, string name) {
+			Attachment attachment;
+			attachments.TryGetValue(new AttachmentKeyTuple(slotIndex, name), out attachment);
+			return attachment;
+		}
+
+		/// <summary> Removes the attachment in the skin for the specified slot index and name, if any.</summary>
+		public void RemoveAttachment (int slotIndex, string name) {
+			if (slotIndex < 0) throw new ArgumentOutOfRangeException("slotIndex", "slotIndex must be >= 0");
+			attachments.Remove(new AttachmentKeyTuple(slotIndex, name));
+		}
+
+		/// <summary>Finds the skin keys for a given slot. The results are added to the passed List(names).</summary>
+		/// <param name="slotIndex">The target slotIndex. To find the slot index, use <see cref="Spine.Skeleton.FindSlotIndex"/> or <see cref="Spine.SkeletonData.FindSlotIndex"/>
+		/// <param name="names">Found skin key names will be added to this list.</param>
+		public void FindNamesForSlot (int slotIndex, List<string> names) {
+			if (names == null) throw new ArgumentNullException("names", "names cannot be null.");
+			foreach (AttachmentKeyTuple key in attachments.Keys)
+				if (key.slotIndex == slotIndex) names.Add(key.name);
+		}
+
+		/// <summary>Finds the attachments for a given slot. The results are added to the passed List(Attachment).</summary>
+		/// <param name="slotIndex">The target slotIndex. To find the slot index, use <see cref="Spine.Skeleton.FindSlotIndex"/> or <see cref="Spine.SkeletonData.FindSlotIndex"/>
+		/// <param name="attachments">Found Attachments will be added to this list.</param>
+		public void FindAttachmentsForSlot (int slotIndex, List<Attachment> attachments) {
+			if (attachments == null) throw new ArgumentNullException("attachments", "attachments cannot be null.");
+			foreach (KeyValuePair<AttachmentKeyTuple, Attachment> entry in this.attachments)
+				if (entry.Key.slotIndex == slotIndex) attachments.Add(entry.Value);
+		}
+
+		override public string ToString () {
+			return name;
+		}
+
+		/// <summary>Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached.</summary>
+		internal void AttachAll (Skeleton skeleton, Skin oldSkin) {
+			foreach (KeyValuePair<AttachmentKeyTuple, Attachment> entry in oldSkin.attachments) {
+				int slotIndex = entry.Key.slotIndex;
+				Slot slot = skeleton.slots.Items[slotIndex];
+				if (slot.Attachment == entry.Value) {
+					Attachment attachment = GetAttachment(slotIndex, entry.Key.name);
+					if (attachment != null) slot.Attachment = attachment;
+				}
+			}
+		}
+
+		public struct AttachmentKeyTuple {
+			public readonly int slotIndex;
+			public readonly string name;
+			internal readonly int nameHashCode;
+
+			public AttachmentKeyTuple (int slotIndex, string name) {
+				this.slotIndex = slotIndex;
+				this.name = name;
+				nameHashCode = this.name.GetHashCode();
+			}
+		}
+
+		// Avoids boxing in the dictionary.
+		class AttachmentKeyTupleComparer : IEqualityComparer<AttachmentKeyTuple> {
+			internal static readonly AttachmentKeyTupleComparer Instance = new AttachmentKeyTupleComparer();
+
+			bool IEqualityComparer<AttachmentKeyTuple>.Equals (AttachmentKeyTuple o1, AttachmentKeyTuple o2) {
+				return o1.slotIndex == o2.slotIndex && o1.nameHashCode == o2.nameHashCode && string.Equals(o1.name, o2.name, StringComparison.Ordinal);
+			}
+
+			int IEqualityComparer<AttachmentKeyTuple>.GetHashCode (AttachmentKeyTuple o) {
+				return o.slotIndex;
+			}
+		}
+	}
+}

+ 100 - 100
spine-csharp/src/Slot.cs

@@ -1,100 +1,100 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-using System;
-
-namespace Spine {
-	public class Slot {
-		internal SlotData data;
-		internal Bone bone;
-		internal float r, g, b, a;
-		internal float r2, g2, b2;
-		internal bool hasSecondColor;
-		internal Attachment attachment;
-		internal float attachmentTime;
-		internal ExposedList<float> attachmentVertices = new ExposedList<float>();
-
-		public SlotData Data { get { return data; } }
-		public Bone Bone { get { return bone; } }
-		public Skeleton Skeleton { get { return bone.skeleton; } }
-		public float R { get { return r; } set { r = value; } }
-		public float G { get { return g; } set { g = value; } }
-		public float B { get { return b; } set { b = value; } }
-		public float A { get { return a; } set { a = value; } }
-
-		public float R2 { get { return r2; } set { r2 = value; } }
-		public float G2 { get { return g2; } set { g2 = value; } }
-		public float B2 { get { return b2; } set { b2 = value; } }
-		public bool HasSecondColor { get { return data.hasSecondColor; } set { data.hasSecondColor = value; } }
-
-		/// <summary>May be null.</summary>
-		public Attachment Attachment {
-			get { return attachment; }
-			set {
-				if (attachment == value) return;
-				attachment = value;
-				attachmentTime = bone.skeleton.time;
-				attachmentVertices.Clear(false);
-			}
-		}
-
-		public float AttachmentTime {
-			get { return bone.skeleton.time - attachmentTime; }
-			set { attachmentTime = bone.skeleton.time - value; }
-		}
-
-		public ExposedList<float> AttachmentVertices { get { return attachmentVertices; } set { attachmentVertices = value; } }
-
-		public Slot (SlotData data, Bone bone) {
-			if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
-			if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null.");
-			this.data = data;
-			this.bone = bone;
-			SetToSetupPose();
-		}
-
-		public void SetToSetupPose () {
-			r = data.r;
-			g = data.g;
-			b = data.b;
-			a = data.a;
-			if (data.attachmentName == null)
-				Attachment = null;
-			else {
-				attachment = null;
-				Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName);
-			}
-		}
-
-		override public string ToString () {
-			return data.name;
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+	public class Slot {
+		internal SlotData data;
+		internal Bone bone;
+		internal float r, g, b, a;
+		internal float r2, g2, b2;
+		internal bool hasSecondColor;
+		internal Attachment attachment;
+		internal float attachmentTime;
+		internal ExposedList<float> attachmentVertices = new ExposedList<float>();
+
+		public SlotData Data { get { return data; } }
+		public Bone Bone { get { return bone; } }
+		public Skeleton Skeleton { get { return bone.skeleton; } }
+		public float R { get { return r; } set { r = value; } }
+		public float G { get { return g; } set { g = value; } }
+		public float B { get { return b; } set { b = value; } }
+		public float A { get { return a; } set { a = value; } }
+
+		public float R2 { get { return r2; } set { r2 = value; } }
+		public float G2 { get { return g2; } set { g2 = value; } }
+		public float B2 { get { return b2; } set { b2 = value; } }
+		public bool HasSecondColor { get { return data.hasSecondColor; } set { data.hasSecondColor = value; } }
+
+		/// <summary>May be null.</summary>
+		public Attachment Attachment {
+			get { return attachment; }
+			set {
+				if (attachment == value) return;
+				attachment = value;
+				attachmentTime = bone.skeleton.time;
+				attachmentVertices.Clear(false);
+			}
+		}
+
+		public float AttachmentTime {
+			get { return bone.skeleton.time - attachmentTime; }
+			set { attachmentTime = bone.skeleton.time - value; }
+		}
+
+		public ExposedList<float> AttachmentVertices { get { return attachmentVertices; } set { attachmentVertices = value; } }
+
+		public Slot (SlotData data, Bone bone) {
+			if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
+			if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null.");
+			this.data = data;
+			this.bone = bone;
+			SetToSetupPose();
+		}
+
+		public void SetToSetupPose () {
+			r = data.r;
+			g = data.g;
+			b = data.b;
+			a = data.a;
+			if (data.attachmentName == null)
+				Attachment = null;
+			else {
+				attachment = null;
+				Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName);
+			}
+		}
+
+		override public string ToString () {
+			return data.name;
+		}
+	}
+}

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

@@ -1,74 +1,74 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-using System;
-
-namespace Spine {
-	public class SlotData {
-		internal int index;
-		internal string name;
-		internal BoneData boneData;
-		internal float r = 1, g = 1, b = 1, a = 1;
-		internal float r2 = 0, g2 = 0, b2 = 0;
-		internal bool hasSecondColor = false;
-		internal string attachmentName;
-		internal BlendMode blendMode;
-
-		public int Index { get { return index; } }
-		public string Name { get { return name; } }
-		public BoneData BoneData { get { return boneData; } }
-		public float R { get { return r; } set { r = value; } }
-		public float G { get { return g; } set { g = value; } }
-		public float B { get { return b; } set { b = value; } }
-		public float A { get { return a; } set { a = value; } }
-
-		public float R2 { get { return r2; } set { r2 = value; } }
-		public float G2 { get { return g2; } set { g2 = value; } }
-		public float B2 { get { return b2; } set { b2 = value; } }
-		public bool HasSecondColor { get { return hasSecondColor; } set { hasSecondColor = value; } }
-
-		/// <summary>May be null.</summary>
-		public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } }
-		public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } }
-
-		public SlotData (int index, String name, BoneData boneData) {
-			if (index < 0) throw new ArgumentException ("index must be >= 0.", "index");
-			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
-			if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null.");
-			this.index = index;
-			this.name = name;
-			this.boneData = boneData;
-		}
-
-		override public string ToString () {
-			return name;
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+	public class SlotData {
+		internal int index;
+		internal string name;
+		internal BoneData boneData;
+		internal float r = 1, g = 1, b = 1, a = 1;
+		internal float r2 = 0, g2 = 0, b2 = 0;
+		internal bool hasSecondColor = false;
+		internal string attachmentName;
+		internal BlendMode blendMode;
+
+		public int Index { get { return index; } }
+		public string Name { get { return name; } }
+		public BoneData BoneData { get { return boneData; } }
+		public float R { get { return r; } set { r = value; } }
+		public float G { get { return g; } set { g = value; } }
+		public float B { get { return b; } set { b = value; } }
+		public float A { get { return a; } set { a = value; } }
+
+		public float R2 { get { return r2; } set { r2 = value; } }
+		public float G2 { get { return g2; } set { g2 = value; } }
+		public float B2 { get { return b2; } set { b2 = value; } }
+		public bool HasSecondColor { get { return hasSecondColor; } set { hasSecondColor = value; } }
+
+		/// <summary>May be null.</summary>
+		public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } }
+		public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } }
+
+		public SlotData (int index, String name, BoneData boneData) {
+			if (index < 0) throw new ArgumentException ("index must be >= 0.", "index");
+			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
+			if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null.");
+			this.index = index;
+			this.name = name;
+			this.boneData = boneData;
+		}
+
+		override public string ToString () {
+			return name;
+		}
+	}
+}

+ 284 - 284
spine-csharp/src/TransformConstraint.cs

@@ -1,284 +1,284 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-using System;
-
-namespace Spine {
-	public class TransformConstraint : IConstraint {
-		internal TransformConstraintData data;
-		internal ExposedList<Bone> bones;
-		internal Bone target;
-		internal float rotateMix, translateMix, scaleMix, shearMix;
-
-		public TransformConstraintData Data { get { return data; } }
-		public int Order { get { return data.order; } }
-		public ExposedList<Bone> Bones { get { return bones; } }
-		public Bone Target { get { return target; } set { target = value; } }
-		public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
-		public float TranslateMix { get { return translateMix; } set { translateMix = value; } }
-		public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } }
-		public float ShearMix { get { return shearMix; } set { shearMix = value; } }
-
-		public TransformConstraint (TransformConstraintData 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;
-			rotateMix = data.rotateMix;
-			translateMix = data.translateMix;
-			scaleMix = data.scaleMix;
-			shearMix = data.shearMix;
-
-			bones = new ExposedList<Bone>();
-			foreach (BoneData boneData in data.bones)
-				bones.Add (skeleton.FindBone (boneData.name));
-
-			target = skeleton.FindBone(data.target.name);
-		}
-
-		public void Apply () {
-			Update();
-		}
-
-		public void Update () {
-			if (data.local) {
-				if (data.relative)
-					ApplyRelativeLocal();
-				else
-					ApplyAbsoluteLocal();
-			} else {
-				if (data.relative)
-					ApplyRelativeWorld();
-				else
-					ApplyAbsoluteWorld();
-			}
-		}
-
-		void ApplyAbsoluteWorld () {
-			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;
-			float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad;
-			float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect;
-			var bones = this.bones;
-			for (int i = 0, n = bones.Count; i < n; i++) {
-				Bone bone = bones.Items[i];
-				bool modified = false;
-
-				if (rotateMix != 0) {
-					float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
-					float r = MathUtils.Atan2(tc, ta) - MathUtils.Atan2(c, a) + offsetRotation;
-					if (r > MathUtils.PI)
-						r -= MathUtils.PI2;
-					else if (r < -MathUtils.PI) r += MathUtils.PI2;
-					r *= rotateMix;
-					float cos = MathUtils.Cos(r), sin = MathUtils.Sin(r);
-					bone.a = cos * a - sin * c;
-					bone.b = cos * b - sin * d;
-					bone.c = sin * a + cos * c;
-					bone.d = sin * b + cos * d;
-					modified = true;
-				}
-
-				if (translateMix != 0) {
-					float tx, ty; //Vector2 temp = this.temp;
-					target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY));
-					bone.worldX += (tx - bone.worldX) * translateMix;
-					bone.worldY += (ty - bone.worldY) * translateMix;
-					modified = true;
-				}
-
-				if (scaleMix > 0) {
-					float s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c);
-					//float ts = (float)Math.sqrt(ta * ta + tc * tc);
-					if (s > 0.00001f) s = (s + ((float)Math.Sqrt(ta * ta + tc * tc) - s + data.offsetScaleX) * scaleMix) / s;
-					bone.a *= s;
-					bone.c *= s;
-					s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d);
-					//ts = (float)Math.Sqrt(tb * tb + td * td);
-					if (s > 0.00001f) s = (s + ((float)Math.Sqrt(tb * tb + td * td) - s + data.offsetScaleY) * scaleMix) / s;
-					bone.b *= s;
-					bone.d *= s;
-					modified = true;
-				}
-
-				if (shearMix > 0) {
-					float b = bone.b, d = bone.d;
-					float by = MathUtils.Atan2(d, b);
-					float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta) - (by - MathUtils.Atan2(bone.c, bone.a));
-					if (r > MathUtils.PI)
-						r -= MathUtils.PI2;
-					else if (r < -MathUtils.PI) r += MathUtils.PI2;
-					r = by + (r + offsetShearY) * shearMix;
-					float s = (float)Math.Sqrt(b * b + d * d);
-					bone.b = MathUtils.Cos(r) * s;
-					bone.d = MathUtils.Sin(r) * s;
-					modified = true;
-				}
-
-				if (modified) bone.appliedValid = false;
-			}
-		}
-
-		void ApplyRelativeWorld () {
-			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;
-			float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad;
-			float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect;
-			var bones = this.bones;
-			for (int i = 0, n = bones.Count; i < n; i++) {
-				Bone bone = bones.Items[i];
-				bool modified = false;
-
-				if (rotateMix != 0) {
-					float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
-					float r = MathUtils.Atan2(tc, ta) + offsetRotation;
-					if (r > MathUtils.PI)
-						r -= MathUtils.PI2;
-					else if (r < -MathUtils.PI) r += MathUtils.PI2;
-					r *= rotateMix;
-					float cos = MathUtils.Cos(r), sin = MathUtils.Sin(r);
-					bone.a = cos * a - sin * c;
-					bone.b = cos * b - sin * d;
-					bone.c = sin * a + cos * c;
-					bone.d = sin * b + cos * d;
-					modified = true;
-				}
-
-				if (translateMix != 0) {
-					float tx, ty; //Vector2 temp = this.temp;
-					target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY));
-					bone.worldX += tx * translateMix;
-					bone.worldY += ty * translateMix;
-					modified = true;
-				}
-
-				if (scaleMix > 0) {
-					float s = ((float)Math.Sqrt(ta * ta + tc * tc) - 1 + data.offsetScaleX) * scaleMix + 1;
-					bone.a *= s;
-					bone.c *= s;
-					s = ((float)Math.Sqrt(tb * tb + td * td) - 1 + data.offsetScaleY) * scaleMix + 1;
-					bone.b *= s;
-					bone.d *= s;
-					modified = true;
-				}
-
-				if (shearMix > 0) {
-					float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta);
-					if (r > MathUtils.PI)
-						r -= MathUtils.PI2;
-					else if (r < -MathUtils.PI) r += MathUtils.PI2;
-					float b = bone.b, d = bone.d;
-					r = MathUtils.Atan2(d, b) + (r - MathUtils.PI / 2 + offsetShearY) * shearMix;
-					float s = (float)Math.Sqrt(b * b + d * d);
-					bone.b = MathUtils.Cos(r) * s;
-					bone.d = MathUtils.Sin(r) * s;
-					modified = true;
-				}
-
-				if (modified) bone.appliedValid = false;
-			}
-		}
-
-		void ApplyAbsoluteLocal () {
-			float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix;
-			Bone target = this.target;
-			if (!target.appliedValid) target.UpdateAppliedTransform();
-			var bonesItems = this.bones.Items;
-			for (int i = 0, n = this.bones.Count; i < n; i++) {
-				Bone bone = bonesItems[i];
-				if (!bone.appliedValid) bone.UpdateAppliedTransform();
-
-				float rotation = bone.arotation;
-				if (rotateMix != 0) {
-					float r = target.arotation - rotation + data.offsetRotation;
-					r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360;
-					rotation += r * rotateMix;
-				}
-
-				float x = bone.ax, y = bone.ay;
-				if (translateMix != 0) {
-					x += (target.ax - x + data.offsetX) * translateMix;
-					y += (target.ay - y + data.offsetY) * translateMix;
-				}
-
-				float scaleX = bone.ascaleX, scaleY = bone.ascaleY;
-				if (scaleMix != 0) {
-					if (scaleX > 0.00001f) scaleX = (scaleX + (target.ascaleX - scaleX + data.offsetScaleX) * scaleMix) / scaleX;
-					if (scaleY > 0.00001f) scaleY = (scaleY + (target.ascaleY - scaleY + data.offsetScaleY) * scaleMix) / scaleY;
-				}
-
-				float shearY = bone.ashearY;
-				if (shearMix != 0) {
-					float r = target.ashearY - shearY + data.offsetShearY;
-					r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360;
-					bone.shearY += r * shearMix;
-				}
-
-				bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY);
-			}
-		}
-
-		void ApplyRelativeLocal () {
-			float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix;
-			Bone target = this.target;
-			if (!target.appliedValid) target.UpdateAppliedTransform();
-			var bonesItems = this.bones.Items;
-			for (int i = 0, n = this.bones.Count; i < n; i++) {
-				Bone bone = bonesItems[i];
-				if (!bone.appliedValid) bone.UpdateAppliedTransform();
-
-				float rotation = bone.arotation;
-				if (rotateMix != 0) rotation += (target.arotation + data.offsetRotation) * rotateMix;
-
-				float x = bone.ax, y = bone.ay;
-				if (translateMix != 0) {
-					x += (target.ax + data.offsetX) * translateMix;
-					y += (target.ay + data.offsetY) * translateMix;
-				}
-
-				float scaleX = bone.ascaleX, scaleY = bone.ascaleY;
-				if (scaleMix != 0) {
-					if (scaleX > 0.00001f) scaleX *= ((target.ascaleX - 1 + data.offsetScaleX) * scaleMix) + 1;
-					if (scaleY > 0.00001f) scaleY *= ((target.ascaleY - 1 + data.offsetScaleY) * scaleMix) + 1;
-				}
-
-				float shearY = bone.ashearY;
-				if (shearMix != 0) shearY += (target.ashearY + data.offsetShearY) * shearMix;
-
-				bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY);
-			}
-		}
-
-		override public string ToString () {
-			return data.name;
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+	public class TransformConstraint : IConstraint {
+		internal TransformConstraintData data;
+		internal ExposedList<Bone> bones;
+		internal Bone target;
+		internal float rotateMix, translateMix, scaleMix, shearMix;
+
+		public TransformConstraintData Data { get { return data; } }
+		public int Order { get { return data.order; } }
+		public ExposedList<Bone> Bones { get { return bones; } }
+		public Bone Target { get { return target; } set { target = value; } }
+		public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
+		public float TranslateMix { get { return translateMix; } set { translateMix = value; } }
+		public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } }
+		public float ShearMix { get { return shearMix; } set { shearMix = value; } }
+
+		public TransformConstraint (TransformConstraintData 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;
+			rotateMix = data.rotateMix;
+			translateMix = data.translateMix;
+			scaleMix = data.scaleMix;
+			shearMix = data.shearMix;
+
+			bones = new ExposedList<Bone>();
+			foreach (BoneData boneData in data.bones)
+				bones.Add (skeleton.FindBone (boneData.name));
+
+			target = skeleton.FindBone(data.target.name);
+		}
+
+		public void Apply () {
+			Update();
+		}
+
+		public void Update () {
+			if (data.local) {
+				if (data.relative)
+					ApplyRelativeLocal();
+				else
+					ApplyAbsoluteLocal();
+			} else {
+				if (data.relative)
+					ApplyRelativeWorld();
+				else
+					ApplyAbsoluteWorld();
+			}
+		}
+
+		void ApplyAbsoluteWorld () {
+			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;
+			float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad;
+			float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect;
+			var bones = this.bones;
+			for (int i = 0, n = bones.Count; i < n; i++) {
+				Bone bone = bones.Items[i];
+				bool modified = false;
+
+				if (rotateMix != 0) {
+					float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
+					float r = MathUtils.Atan2(tc, ta) - MathUtils.Atan2(c, a) + offsetRotation;
+					if (r > MathUtils.PI)
+						r -= MathUtils.PI2;
+					else if (r < -MathUtils.PI) r += MathUtils.PI2;
+					r *= rotateMix;
+					float cos = MathUtils.Cos(r), sin = MathUtils.Sin(r);
+					bone.a = cos * a - sin * c;
+					bone.b = cos * b - sin * d;
+					bone.c = sin * a + cos * c;
+					bone.d = sin * b + cos * d;
+					modified = true;
+				}
+
+				if (translateMix != 0) {
+					float tx, ty; //Vector2 temp = this.temp;
+					target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY));
+					bone.worldX += (tx - bone.worldX) * translateMix;
+					bone.worldY += (ty - bone.worldY) * translateMix;
+					modified = true;
+				}
+
+				if (scaleMix > 0) {
+					float s = (float)Math.Sqrt(bone.a * bone.a + bone.c * bone.c);
+					//float ts = (float)Math.sqrt(ta * ta + tc * tc);
+					if (s > 0.00001f) s = (s + ((float)Math.Sqrt(ta * ta + tc * tc) - s + data.offsetScaleX) * scaleMix) / s;
+					bone.a *= s;
+					bone.c *= s;
+					s = (float)Math.Sqrt(bone.b * bone.b + bone.d * bone.d);
+					//ts = (float)Math.Sqrt(tb * tb + td * td);
+					if (s > 0.00001f) s = (s + ((float)Math.Sqrt(tb * tb + td * td) - s + data.offsetScaleY) * scaleMix) / s;
+					bone.b *= s;
+					bone.d *= s;
+					modified = true;
+				}
+
+				if (shearMix > 0) {
+					float b = bone.b, d = bone.d;
+					float by = MathUtils.Atan2(d, b);
+					float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta) - (by - MathUtils.Atan2(bone.c, bone.a));
+					if (r > MathUtils.PI)
+						r -= MathUtils.PI2;
+					else if (r < -MathUtils.PI) r += MathUtils.PI2;
+					r = by + (r + offsetShearY) * shearMix;
+					float s = (float)Math.Sqrt(b * b + d * d);
+					bone.b = MathUtils.Cos(r) * s;
+					bone.d = MathUtils.Sin(r) * s;
+					modified = true;
+				}
+
+				if (modified) bone.appliedValid = false;
+			}
+		}
+
+		void ApplyRelativeWorld () {
+			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;
+			float degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad;
+			float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect;
+			var bones = this.bones;
+			for (int i = 0, n = bones.Count; i < n; i++) {
+				Bone bone = bones.Items[i];
+				bool modified = false;
+
+				if (rotateMix != 0) {
+					float a = bone.a, b = bone.b, c = bone.c, d = bone.d;
+					float r = MathUtils.Atan2(tc, ta) + offsetRotation;
+					if (r > MathUtils.PI)
+						r -= MathUtils.PI2;
+					else if (r < -MathUtils.PI) r += MathUtils.PI2;
+					r *= rotateMix;
+					float cos = MathUtils.Cos(r), sin = MathUtils.Sin(r);
+					bone.a = cos * a - sin * c;
+					bone.b = cos * b - sin * d;
+					bone.c = sin * a + cos * c;
+					bone.d = sin * b + cos * d;
+					modified = true;
+				}
+
+				if (translateMix != 0) {
+					float tx, ty; //Vector2 temp = this.temp;
+					target.LocalToWorld(data.offsetX, data.offsetY, out tx, out ty); //target.localToWorld(temp.set(data.offsetX, data.offsetY));
+					bone.worldX += tx * translateMix;
+					bone.worldY += ty * translateMix;
+					modified = true;
+				}
+
+				if (scaleMix > 0) {
+					float s = ((float)Math.Sqrt(ta * ta + tc * tc) - 1 + data.offsetScaleX) * scaleMix + 1;
+					bone.a *= s;
+					bone.c *= s;
+					s = ((float)Math.Sqrt(tb * tb + td * td) - 1 + data.offsetScaleY) * scaleMix + 1;
+					bone.b *= s;
+					bone.d *= s;
+					modified = true;
+				}
+
+				if (shearMix > 0) {
+					float r = MathUtils.Atan2(td, tb) - MathUtils.Atan2(tc, ta);
+					if (r > MathUtils.PI)
+						r -= MathUtils.PI2;
+					else if (r < -MathUtils.PI) r += MathUtils.PI2;
+					float b = bone.b, d = bone.d;
+					r = MathUtils.Atan2(d, b) + (r - MathUtils.PI / 2 + offsetShearY) * shearMix;
+					float s = (float)Math.Sqrt(b * b + d * d);
+					bone.b = MathUtils.Cos(r) * s;
+					bone.d = MathUtils.Sin(r) * s;
+					modified = true;
+				}
+
+				if (modified) bone.appliedValid = false;
+			}
+		}
+
+		void ApplyAbsoluteLocal () {
+			float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix;
+			Bone target = this.target;
+			if (!target.appliedValid) target.UpdateAppliedTransform();
+			var bonesItems = this.bones.Items;
+			for (int i = 0, n = this.bones.Count; i < n; i++) {
+				Bone bone = bonesItems[i];
+				if (!bone.appliedValid) bone.UpdateAppliedTransform();
+
+				float rotation = bone.arotation;
+				if (rotateMix != 0) {
+					float r = target.arotation - rotation + data.offsetRotation;
+					r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360;
+					rotation += r * rotateMix;
+				}
+
+				float x = bone.ax, y = bone.ay;
+				if (translateMix != 0) {
+					x += (target.ax - x + data.offsetX) * translateMix;
+					y += (target.ay - y + data.offsetY) * translateMix;
+				}
+
+				float scaleX = bone.ascaleX, scaleY = bone.ascaleY;
+				if (scaleMix != 0) {
+					if (scaleX > 0.00001f) scaleX = (scaleX + (target.ascaleX - scaleX + data.offsetScaleX) * scaleMix) / scaleX;
+					if (scaleY > 0.00001f) scaleY = (scaleY + (target.ascaleY - scaleY + data.offsetScaleY) * scaleMix) / scaleY;
+				}
+
+				float shearY = bone.ashearY;
+				if (shearMix != 0) {
+					float r = target.ashearY - shearY + data.offsetShearY;
+					r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360;
+					bone.shearY += r * shearMix;
+				}
+
+				bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY);
+			}
+		}
+
+		void ApplyRelativeLocal () {
+			float rotateMix = this.rotateMix, translateMix = this.translateMix, scaleMix = this.scaleMix, shearMix = this.shearMix;
+			Bone target = this.target;
+			if (!target.appliedValid) target.UpdateAppliedTransform();
+			var bonesItems = this.bones.Items;
+			for (int i = 0, n = this.bones.Count; i < n; i++) {
+				Bone bone = bonesItems[i];
+				if (!bone.appliedValid) bone.UpdateAppliedTransform();
+
+				float rotation = bone.arotation;
+				if (rotateMix != 0) rotation += (target.arotation + data.offsetRotation) * rotateMix;
+
+				float x = bone.ax, y = bone.ay;
+				if (translateMix != 0) {
+					x += (target.ax + data.offsetX) * translateMix;
+					y += (target.ay + data.offsetY) * translateMix;
+				}
+
+				float scaleX = bone.ascaleX, scaleY = bone.ascaleY;
+				if (scaleMix != 0) {
+					if (scaleX > 0.00001f) scaleX *= ((target.ascaleX - 1 + data.offsetScaleX) * scaleMix) + 1;
+					if (scaleY > 0.00001f) scaleY *= ((target.ascaleY - 1 + data.offsetScaleY) * scaleMix) + 1;
+				}
+
+				float shearY = bone.ashearY;
+				if (shearMix != 0) shearY += (target.ashearY + data.offsetShearY) * shearMix;
+
+				bone.UpdateWorldTransform(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY);
+			}
+		}
+
+		override public string ToString () {
+			return data.name;
+		}
+	}
+}

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

@@ -1,71 +1,71 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes or (b) remove,
- * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
- * 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, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
- * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *****************************************************************************/
-
-using System;
-
-namespace Spine {
-	public class TransformConstraintData {
-		internal string name;
-		internal int order;
-		internal ExposedList<BoneData> bones = new ExposedList<BoneData>();
-		internal BoneData target;
-		internal float rotateMix, translateMix, scaleMix, shearMix;
-		internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY;
-		internal bool relative, local;
-
-		public string Name { get { return name; } }
-		public int Order { get { return order; } set { order = value; } }
-		public ExposedList<BoneData> Bones { get { return bones; } }
-		public BoneData Target { get { return target; } set { target = value; } }
-		public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
-		public float TranslateMix { get { return translateMix; } set { translateMix = value; } }
-		public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } }
-		public float ShearMix { get { return shearMix; } set { shearMix = value; } }
-
-		public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } }
-		public float OffsetX { get { return offsetX; } set { offsetX = value; } }
-		public float OffsetY { get { return offsetY; } set { offsetY = value; } }
-		public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } }
-		public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } }
-		public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } }
-
-		public bool Relative { get { return relative; } set { relative = value; } }
-		public bool Local { get { return local; } set { local = value; } }
-
-		public TransformConstraintData (string name) {
-			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
-			this.name = name;
-		}
-
-		override public string ToString () {
-			return name;
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+	public class TransformConstraintData {
+		internal string name;
+		internal int order;
+		internal ExposedList<BoneData> bones = new ExposedList<BoneData>();
+		internal BoneData target;
+		internal float rotateMix, translateMix, scaleMix, shearMix;
+		internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY;
+		internal bool relative, local;
+
+		public string Name { get { return name; } }
+		public int Order { get { return order; } set { order = value; } }
+		public ExposedList<BoneData> Bones { get { return bones; } }
+		public BoneData Target { get { return target; } set { target = value; } }
+		public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
+		public float TranslateMix { get { return translateMix; } set { translateMix = value; } }
+		public float ScaleMix { get { return scaleMix; } set { scaleMix = value; } }
+		public float ShearMix { get { return shearMix; } set { shearMix = value; } }
+
+		public float OffsetRotation { get { return offsetRotation; } set { offsetRotation = value; } }
+		public float OffsetX { get { return offsetX; } set { offsetX = value; } }
+		public float OffsetY { get { return offsetY; } set { offsetY = value; } }
+		public float OffsetScaleX { get { return offsetScaleX; } set { offsetScaleX = value; } }
+		public float OffsetScaleY { get { return offsetScaleY; } set { offsetScaleY = value; } }
+		public float OffsetShearY { get { return offsetShearY; } set { offsetShearY = value; } }
+
+		public bool Relative { get { return relative; } set { relative = value; } }
+		public bool Local { get { return local; } set { local = value; } }
+
+		public TransformConstraintData (string name) {
+			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
+			this.name = name;
+		}
+
+		override public string ToString () {
+			return name;
+		}
+	}
+}