瀏覽代碼

[csharp] Ported 3.6-beta changes.

pharan 8 年之前
父節點
當前提交
3736a5c3dc

+ 132 - 1
spine-csharp/src/Animation.cs

@@ -126,7 +126,8 @@ namespace Spine {
 		Attachment, Color, Deform, //
 		Event, DrawOrder, //
 		IkConstraint, TransformConstraint, //
-		PathConstraintPosition, PathConstraintSpacing, PathConstraintMix
+		PathConstraintPosition, PathConstraintSpacing, PathConstraintMix, //
+		TwoColor
 	}
 
 	/// <summary>Base class for frames that use an interpolation bezier curve.</summary>
@@ -546,6 +547,136 @@ namespace Spine {
 		}
 	}
 
+	public class TwoColorTimeline : CurveTimeline {
+		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 float[] frames; // time, r, g, b, a, r2, g2, b2, ...
+		public float[] Frames { get { return frames; } }
+
+		internal int slotIndex;
+		public int SlotIndex {
+			get { return slotIndex; }
+			set {
+				if (value < 0) throw new ArgumentOutOfRangeException("index must be >= 0.");
+				slotIndex = value;
+			}
+		}
+
+		public TwoColorTimeline (int frameCount) :
+			base(frameCount) {
+			frames = new float[frameCount * ENTRIES];
+		}
+
+		override public int PropertyId {
+			get { return ((int)TimelineType.TwoColor << 24) + slotIndex; }
+		}
+
+		/// <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, bool setupPose, bool mixingOut) {
+			Slot slot = skeleton.slots.Items[slotIndex];
+			float[] frames = this.frames;
+			if (time < frames[0]) { // Time is before first frame.
+				if (setupPose) {
+//					slot.color.set(slot.data.color);
+//					slot.darkColor.set(slot.data.darkColor);
+					var slotData = slot.data;
+					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;
+			}
+
+			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 (setupPose) {
+					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 {
 		internal int slotIndex;
 		internal float[] frames;

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

@@ -805,6 +805,11 @@ namespace Spine {
 		/// <see cref="AnimationStateData"/> based on the animation before this animation (if any).
 		/// 
 		/// The mix duration must be set before <see cref="AnimationState.Update(float)"/> is next called.
+		/// <para>
+		/// When using <seealso cref="AnimationState.AddAnimation(int, Animation, bool, float)"/> with a 
+		/// <code>delay</code> <seealso cref="Delay"/> is set using the mix duration from the <see cref=" AnimationStateData"/>
+		/// </para>
+		/// 
 		/// </summary>
 		public float MixDuration { get { return mixDuration; } set { mixDuration = value; } }
 

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

@@ -39,7 +39,7 @@ namespace Spine {
 			this.atlasArray = atlasArray;
 		}
 
-		public RegionAttachment NewRegionAttachment (Skin skin, String name, String path) {
+		public RegionAttachment NewRegionAttachment (Skin skin, string name, string path) {
 			AtlasRegion region = FindRegion(path);
 			if (region == null) throw new Exception("Region not found in atlas: " + path + " (region attachment: " + name + ")");
 			RegionAttachment attachment = new RegionAttachment(name);
@@ -54,7 +54,7 @@ namespace Spine {
 			return attachment;
 		}
 
-		public MeshAttachment NewMeshAttachment (Skin skin, String name, String path) {
+		public MeshAttachment NewMeshAttachment (Skin skin, string name, string path) {
 			AtlasRegion region = FindRegion(path);
 			if (region == null) throw new Exception("Region not found in atlas: " + path + " (mesh attachment: " + name + ")");
 			MeshAttachment attachment = new MeshAttachment(name);
@@ -73,12 +73,16 @@ namespace Spine {
 			return attachment;
 		}			
 
-		public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, String name) {
+		public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name) {
 			return new BoundingBoxAttachment(name);
 		}
 
-		public PathAttachment NewPathAttachment (Skin skin, String name) {
-			return new PathAttachment (name);
+		public PathAttachment NewPathAttachment (Skin skin, string name) {
+			return new PathAttachment(name);
+		}
+
+		public PointAttachment NewPointAttachment (Skin skin, string name) {
+			return new PointAttachment(name);
 		}
 
 		public AtlasRegion FindRegion (string name) {

+ 6 - 4
spine-csharp/src/Attachments/AttachmentLoader.cs

@@ -33,15 +33,17 @@ using System;
 namespace Spine {
 	public interface AttachmentLoader {
 		/// <return>May be null to not load any attachment.</return>
-		RegionAttachment NewRegionAttachment (Skin skin, String name, String path);
+		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);
+		MeshAttachment NewMeshAttachment (Skin skin, String name, string path);
 
 		/// <return>May be null to not load any attachment.</return>
-		BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, String name);
+		BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name);
 
 		/// <returns>May be null to not load any attachment</returns>
-		PathAttachment NewPathAttachment (Skin skin, String name);
+		PathAttachment NewPathAttachment (Skin skin, string name);
+
+		PointAttachment NewPointAttachment (Skin skin, string name);
 	}
 }

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

@@ -30,6 +30,6 @@
 
 namespace Spine {
 	public enum AttachmentType {
-		Region, Boundingbox, Mesh, Linkedmesh, Path
+		Region, Boundingbox, Mesh, Linkedmesh, Path, Point
 	}
 }

+ 1 - 0
spine-csharp/src/Attachments/MeshAttachment.cs

@@ -43,6 +43,7 @@ namespace Spine {
 
 		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; } }
 

+ 61 - 0
spine-csharp/src/Attachments/PointAttachment.cs

@@ -0,0 +1,61 @@
+/******************************************************************************
+ * 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 {
+	/// <summary>
+	/// An attachment which is a single point and a rotation. This can be used to spawn projectiles, particles, etc. A bone can be
+	/// used in similar ways, but a PointAttachment is slightly less expensive to compute and can be hidden, shown, and placed in a
+	/// skin.
+	/// <p>
+	/// See <a href="http://esotericsoftware.com/spine-point-attachments">Point Attachments</a> in the Spine User Guide.
+	/// </summary>
+	public class PointAttachment : Attachment {
+		internal float x, y, rotation;
+		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 PointAttachment (string name)
+			: base(name) {
+		}
+
+		public void ComputeWorldPosition (Bone bone, float x, float y, out float ox, out float oy) {
+			bone.LocalToWorld(x, y, out ox, out oy);
+		}
+
+		public float ComputeWorldRotation (Bone bone) {
+			float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation);
+			float ix = cos * bone.a + sin * bone.b;
+			float iy = cos * bone.c + sin * bone.d;
+			return MathUtils.Atan2(iy, ix) * MathUtils.RadDeg;
+		}
+	}
+}
+

+ 5 - 4
spine-csharp/src/Attachments/VertexAttachment.cs

@@ -55,7 +55,8 @@ namespace Spine {
 		/// <param name="count">The number of world vertex values to output. Must be less than or equal to <see cref="WorldVerticesLength"/> - start.</param>
 		/// <param name="worldVertices">The output world vertices. Must have a length greater than or equal to <paramref name="offset"/> + <paramref name="count"/>.</param>
 		/// <param name="offset">The <paramref name="worldVertices"/> index to begin writing values.</param>
-		public void ComputeWorldVertices (Slot slot, int start, int count, float[] worldVertices, int offset) {
+		/// <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;
 			Skeleton skeleton = slot.Skeleton;
 			var deformArray = slot.attachmentVertices;
@@ -66,7 +67,7 @@ namespace Spine {
 				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 += 2) {
+				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;
@@ -81,7 +82,7 @@ namespace Spine {
 			}
 			Bone[] skeletonBones = skeleton.Bones.Items;
 			if (deformArray.Count == 0) {
-				for (int w = offset, b = skip * 3; w < count; w += 2) {
+				for (int w = offset, b = skip * 3; w < count; w += stride) {
 					float wx = 0, wy = 0;
 					int n = bones[v++];
 					n += v;
@@ -96,7 +97,7 @@ namespace Spine {
 				}
 			} else {
 				float[] deform = deformArray.Items;
-				for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += 2) {
+				for (int w = offset, b = skip * 3, f = skip << 1; w < count; w += stride) {
 					float wx = 0, wy = 0;
 					int n = bones[v++];
 					n += v;

+ 47 - 28
spine-csharp/src/Bone.cs

@@ -31,6 +31,14 @@
 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;
 
@@ -55,6 +63,7 @@ namespace Spine {
 		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; } }
 		public float Y { get { return y; } set { y = value; } }
 		public float Rotation { get { return rotation; } set { rotation = value; } }
@@ -240,34 +249,6 @@ namespace Spine {
 			shearY = data.shearY;
 		}
 
-		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 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;
-		}
-
 		/// <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)..
@@ -328,6 +309,44 @@ namespace Spine {
 			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;
+		}
+
+		public float LocalToWorldRotation (float localRotation) {
+			float sin = MathUtils.SinDeg(localRotation), cos = MathUtils.CosDeg(localRotation);
+			return MathUtils.Atan2(cos * c + sin * d, cos * a + sin * b) * MathUtils.RadDeg;
+		}
+
+		public void RotateWorld (float degrees) {
+			float a = this.a, b = this.b, c = this.c, d = this.d;
+			float cos = MathUtils.CosDeg(degrees), sin = MathUtils.SinDeg(degrees);
+			this.a = cos * a - sin * c;
+			this.b = cos * b - sin * d;
+			this.c = sin * a + cos * c;
+			this.d = sin * b + cos * d;
+			appliedValid = false;
+		}
+
 		override public String ToString () {
 			return data.name;
 		}

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

@@ -91,10 +91,10 @@ namespace Spine {
 				if (scale) lengths = this.lengths.Resize(boneCount);
 				for (int i = 0, n = spacesCount - 1; i < n;) {
 					Bone bone = bones[i];
-					float length = bone.data.length, x = length * bone.a, y = length * bone.c;
-					length = (float)Math.Sqrt(x * x + y * y);
-					if (scale) lengths.Items[i] = length;
-					spaces.Items[++i] = lengthSpacing ? Math.Max(0, length + spacing) : spacing;
+					float setupLength = bone.data.length, x = setupLength * bone.a, y = setupLength * bone.c;
+					float length = (float)Math.Sqrt(x * x + y * y);
+					if (scale) lengths.Items[i] = setupLength;
+					spaces.Items[++i] = (lengthSpacing ? Math.Max(0, setupLength + spacing) : spacing) * length / setupLength;
 				}
 			} else {
 				for (int i = 1; i < spacesCount; i++)

+ 70 - 12
spine-csharp/src/Skeleton.cs

@@ -194,15 +194,15 @@ namespace Spine {
 
 			var constrained = constraint.bones;
 			int boneCount = constrained.Count;
-			for (int ii = 0; ii < boneCount; ii++)
-				SortBone(constrained.Items[ii]);
+			for (int i = 0; i < boneCount; i++)
+				SortBone(constrained.Items[i]);
 
 			updateCache.Add(constraint);
 
-			for (int ii = 0; ii < boneCount; ii++)
-				SortReset(constrained.Items[ii].children);
-			for (int ii = 0; ii < boneCount; ii++)
-				constrained.Items[ii].sorted = true;
+			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) {
@@ -210,15 +210,25 @@ namespace Spine {
 
 			var constrained = constraint.bones;
 			int boneCount = constrained.Count;
-			for (int ii = 0; ii < boneCount; ii++)
-				SortBone(constrained.Items[ii]);
+//			for (int ii = 0; ii < boneCount; ii++)
+//				SortBone(constrained.Items[ii]);
+			if (constraint.data.local) {
+				for (int i = 0; i < boneCount; i++) {
+					Bone child = constrained.Items[constrained.Count - 1];
+					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 ii = 0; ii < boneCount; ii++)
-				SortReset(constrained.Items[ii].children);
-			for (int ii = 0; ii < boneCount; ii++)
-				constrained.Items[ii].sorted = true;
+			for (int i = 0; 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) {
@@ -475,5 +485,53 @@ namespace Spine {
 		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 drawOrder = this.drawOrder;
+			float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue;
+			for (int i = 0, n = drawOrder.Count; i < n; i++) {
+				Slot slot = drawOrder.Items[i];
+				int verticesLength = 0;
+				float[] vertices = null;
+				Attachment attachment = slot.attachment;
+				var regionAttachment = attachment as RegionAttachment;
+				if (regionAttachment != null) {
+					verticesLength = 8;
+					if (temp.Length < 8) temp = new float[8];
+					regionAttachment.ComputeWorldVertices(slot.bone, temp);
+				} else {
+					var meshAttachment = attachment as MeshAttachment;
+					if (meshAttachment != null) {
+						MeshAttachment mesh = meshAttachment;
+						verticesLength = mesh.WorldVerticesLength;
+						if (temp.Length < verticesLength) 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;
+		}
 	}
 }

+ 53 - 7
spine-csharp/src/SkeletonBinary.cs

@@ -50,6 +50,7 @@ namespace Spine {
 
 		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;
@@ -182,6 +183,15 @@ namespace Spine {
 				slotData.g = ((color & 0x00ff0000) >> 16) / 255f;
 				slotData.b = ((color & 0x0000ff00) >> 8) / 255f;
 				slotData.a = ((color & 0x000000ff)) / 255f;
+
+				int darkColor = ReadInt(input);
+				if (darkColor != -1) {
+					slotData.hasSecondColor = true;
+					slotData.r2 = ((darkColor & 0xff000000) >> 24) / 255f;
+					slotData.g2 = ((darkColor & 0x00ff0000) >> 16) / 255f;
+					slotData.b2 = ((darkColor & 0x0000ff00) >> 8) / 255f;
+				}
+
 				slotData.attachmentName = ReadString(input);
 				slotData.blendMode = (BlendMode)ReadVarint(input, true);
 				skeletonData.slots.Add(slotData);
@@ -425,7 +435,7 @@ namespace Spine {
 					float[] lengths = new float[vertexCount / 3];
 					for (int i = 0, n = lengths.Length; i < n; i++)
 						lengths[i] = ReadFloat(input) * scale;
-					if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0; // Avoid unused local warning.
+					if (nonessential) ReadInt(input); //int color = nonessential ? ReadInt(input) : 0;
 
 					PathAttachment path = attachmentLoader.NewPathAttachment(skin, name);
 					if (path == null) return null;
@@ -436,7 +446,21 @@ namespace Spine {
 					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;
+				}
 			}
 			return null;
 		}
@@ -499,6 +523,15 @@ namespace Spine {
 					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;
@@ -516,13 +549,26 @@ namespace Spine {
 							duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]);
 							break;
 						}
-					case SLOT_ATTACHMENT: {
-							AttachmentTimeline timeline = new AttachmentTimeline(frameCount);
+					case SLOT_TWO_COLOR: {
+							TwoColorTimeline timeline = new TwoColorTimeline(frameCount);
 							timeline.slotIndex = slotIndex;
-							for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
-								timeline.SetFrame(frameIndex, ReadFloat(input), ReadString(input));
+							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);
+								float r2 = ((color2 & 0xff000000) >> 24) / 255f;
+								float g2 = ((color2 & 0x00ff0000) >> 16) / 255f;
+								float b2 = ((color2 & 0x0000ff00) >> 8) / 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[frameCount - 1]);
+							duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TwoColorTimeline.ENTRIES]);
 							break;
 						}
 					}

+ 41 - 5
spine-csharp/src/SkeletonJson.cs

@@ -146,6 +146,14 @@ namespace Spine {
 						data.b = ToColor(color, 2);
 						data.a = ToColor(color, 3);
 					}
+
+					if (slotMap.ContainsKey("dark")) {
+						var color2 = (String)slotMap["dark"];
+						data.r2 = ToColor(color2, 0);
+						data.g2 = ToColor(color2, 1);
+						data.b2 = ToColor(color2, 2);
+						data.hasSecondColor = true;
+					}
 						
 					data.attachmentName = GetString(slotMap, "attachment", null);
 					if (slotMap.ContainsKey("blend"))
@@ -394,6 +402,17 @@ namespace Spine {
 					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;
+				}
 			}
 			return null;
 		}
@@ -441,7 +460,19 @@ namespace Spine {
 					foreach (KeyValuePair<String, Object> timelineEntry in timelineMap) {
 						var values = (List<Object>)timelineEntry.Value;
 						var timelineName = (String)timelineEntry.Key;
-						if (timelineName == "color") {
+						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;
 
@@ -456,17 +487,22 @@ namespace Spine {
 							timelines.Add(timeline);
 							duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]);
 
-						} else if (timelineName == "attachment") {
-							var timeline = new AttachmentTimeline(values.Count);
+						} 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"];
-								timeline.SetFrame(frameIndex++, time, (String)valueMap["name"]);
+								String c = (String)valueMap["light"];
+								String c2 = (String)valueMap["dark"];
+								timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3),
+									ToColor(c2, 0), ToColor(c2, 1), ToColor(c2, 2));
+								ReadCurve(valueMap, timeline, frameIndex);
+								frameIndex++;
 							}
 							timelines.Add(timeline);
-							duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
+							duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]);
 
 						} else
 							throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")");

+ 7 - 0
spine-csharp/src/Slot.cs

@@ -35,6 +35,8 @@ namespace Spine {
 		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>();
@@ -47,6 +49,11 @@ namespace Spine {
 		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 Attachment Attachment {
 			get { return attachment; }

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

@@ -33,19 +33,27 @@ using System;
 namespace Spine {
 	public class SlotData {
 		internal int index;
-		internal String name;
+		internal string name;
 		internal BoneData boneData;
 		internal float r = 1, g = 1, b = 1, a = 1;
-		internal String attachmentName;
+		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 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; } }

+ 153 - 11
spine-csharp/src/TransformConstraint.cs

@@ -67,15 +67,28 @@ namespace Spine {
 		}
 
 		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 degRadReflect = ta * td - tb * tc > 0 ? MathUtils.DegRad : -MathUtils.DegRad;
 			float offsetRotation = data.offsetRotation * degRadReflect, offsetShearY = data.offsetShearY * degRadReflect;
 			var bones = this.bones;
-			var bonesItems = bones.Items;
 			for (int i = 0, n = bones.Count; i < n; i++) {
-				Bone bone = bonesItems[i];
+				Bone bone = bones.Items[i];
 				bool modified = false;
 
 				if (rotateMix != 0) {
@@ -94,22 +107,20 @@ namespace Spine {
 				}
 
 				if (translateMix != 0) {
-					float tempx, tempy;
-					target.LocalToWorld(data.offsetX, data.offsetY, out tempx, out tempy);
-					bone.worldX += (tempx - bone.worldX) * translateMix;
-					bone.worldY += (tempy - bone.worldY) * translateMix;
+					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 + (ts - s + data.offsetScaleX) * scaleMix) / s;
+					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 + (ts - s + data.offsetScaleY) * scaleMix) / s;
+					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;
@@ -133,6 +144,137 @@ namespace Spine {
 			}
 		}
 
+		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 bones = this.bones;
+			for (int i = 0, n = bones.Count; i < n; i++) {
+				Bone bone = bones.Items[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 bones = this.bones;
+			for (int i = 0, n = bones.Count; i < n; i++) {
+				Bone bone = bones.Items[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;
 		}

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

@@ -38,6 +38,7 @@ namespace Spine {
 		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; } }
@@ -55,6 +56,9 @@ namespace Spine {
 		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;