浏览代码

[csharp] Animation and AnimationState 3.7

pharan 7 年之前
父节点
当前提交
b4c8deb283
共有 2 个文件被更改,包括 410 次插入224 次删除
  1. 318 161
      spine-csharp/src/Animation.cs
  2. 92 63
      spine-csharp/src/AnimationState.cs

+ 318 - 161
spine-csharp/src/Animation.cs

@@ -50,8 +50,8 @@ namespace Spine {
 		}
 
 		/// <summary>Applies all the animation's timelines to the specified skeleton.</summary>
-		/// <seealso cref="Timeline.Apply(Skeleton, float, float, ExposedList, float, MixPose, MixDirection)"/>
-		public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, ExposedList<Event> events, float alpha, MixPose pose, MixDirection direction) {
+		/// <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) {
@@ -113,28 +113,48 @@ namespace Spine {
 		/// 	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="pose">Controls how mixing is applied when alpha is than 1.</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, MixPose pose, MixDirection direction);
+		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, MixPose, MixDirection)"/>
-	public enum MixPose {
-		/// <summary> The timeline value is mixed with the setup pose (the current pose is not used).</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> The timeline value is mixed with the current pose. The setup pose is used as the timeline value before the first key,
-		/// except for timelines which perform instant transitions, such as DrawOrderTimeline or AttachmentTimeline.</summary>
-		Current,
-		/// <summary> The timeline value is mixed with the current pose. No change is made before the first key (the current pose is kept until the first key).</summary>
-		CurrentLayered
+
+		/// <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) or mixing in toward 1 (the timeline's pose).</summary>
-	/// <seealso cref="Timeline.Apply(Skeleton, float, float, ExposedList, float, MixPose, MixDirection)"/>
+	/// 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
@@ -162,7 +182,7 @@ namespace Spine {
 			curves = new float[(frameCount - 1) * BEZIER_SIZE];
 		}
 
-		abstract public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction);
+		abstract public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixBlend pose, MixDirection direction);
 
 		abstract public int PropertyId { get; }
 
@@ -258,31 +278,38 @@ namespace Spine {
 			frames[frameIndex + ROTATION] = degrees;
 		}
 
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction) {
+		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 (pose) {
-				case MixPose.Setup:
+				switch (blend) {
+				case MixBlend.Setup:
 					bone.rotation = bone.data.rotation;
 					return;
-				case MixPose.Current:
-					float rr = bone.data.rotation - bone.rotation;
-					rr -= (16384 - (int)(16384.499999999996 - rr / 360)) * 360;
-					bone.rotation += rr * alpha;
-					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.
-				if (pose == MixPose.Setup) {
-					bone.rotation = bone.data.rotation + frames[frames.Length + PREV_ROTATION] * alpha;
-				} else {
-					float rr = bone.data.rotation + frames[frames.Length + PREV_ROTATION] - bone.rotation;
-					rr -= (16384 - (int)(16384.499999999996 - rr / 360)) * 360; // Wrap within -180 and 180.
-					bone.rotation += rr * alpha;
+				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;
 			}
@@ -292,18 +319,23 @@ namespace Spine {
 			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 -= (16384 - (int)(16384.499999999996 - r / 360)) * 360;
-			r = prevRotation + r * percent;
-			if (pose == MixPose.Setup) {
-				r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360;
-				bone.rotation = bone.data.rotation + r * alpha;
-			} else {
-				r = bone.data.rotation + r - bone.rotation;
-				r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360;
-				bone.rotation += r * alpha;
+			{
+				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;
+				}
 			}
+			
 		}
 	}
 
@@ -335,17 +367,17 @@ namespace Spine {
 			frames[frameIndex + Y] = y;
 		}
 
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction) {
+		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 (pose) {
-				case MixPose.Setup:
+				switch (blend) {
+				case MixBlend.Setup:
 					bone.x = bone.data.x;
 					bone.y = bone.data.y;
 					return;
-				case MixPose.Current:
+				case MixBlend.First:
 					bone.x += (bone.data.x - bone.x) * alpha;
 					bone.y += (bone.data.y - bone.y) * alpha;
 					return;
@@ -369,12 +401,20 @@ namespace Spine {
 				x += (frames[frame + X] - x) * percent;
 				y += (frames[frame + Y] - y) * percent;
 			}
-			if (pose == MixPose.Setup) {
-				bone.x = bone.data.x + x * alpha;
-				bone.y = bone.data.y + y * alpha;
-			} else {
-				bone.x += (bone.data.x + x - bone.x) * alpha;
-				bone.y += (bone.data.y + y - bone.y) * alpha;
+			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;
 			}
 		}
 	}
@@ -388,17 +428,17 @@ namespace Spine {
 			: base(frameCount) {
 		}
 
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction) {
+		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 (pose) {
-				case MixPose.Setup:
+				switch (blend) {
+				case MixBlend.Setup:
 					bone.scaleX = bone.data.scaleX;
 					bone.scaleY = bone.data.scaleY;
 					return;
-				case MixPose.Current:
+				case MixBlend.First:
 					bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha;
 					bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha;
 					return;
@@ -423,27 +463,61 @@ namespace Spine {
 				y = (y + (frames[frame + Y] - y) * percent) * bone.data.scaleY;
 			}
 			if (alpha == 1) {
-				bone.scaleX = x;
-				bone.scaleY = y;
-			} else {
-				float bx, by;
-				if (pose == MixPose.Setup) {
-					bx = bone.data.scaleX;
-					by = bone.data.scaleY;
+				if (blend == MixBlend.Add) {
+					bone.scaleX += x - bone.data.scaleX;
+					bone.scaleY += y - bone.data.scaleY;
 				} else {
-					bx = bone.scaleX;
-					by = bone.scaleY;
+					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) {
-					x = (x >= 0 ? x : -x) * (bx >= 0 ? 1 : -1);
-					y = (y >= 0 ? y : -y) * (by >= 0 ? 1 : -1);
+					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 {
-					bx = (bx >= 0 ? bx : -bx) * (x >= 0 ? 1 : -1);
-					by = (by >= 0 ? by : -by) * (y >= 0 ? 1 : -1);
+					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;
+					}
 				}
-				bone.scaleX = bx + (x - bx) * alpha;
-				bone.scaleY = by + (y - by) * alpha;
 			}
 		}
 	}
@@ -457,16 +531,16 @@ namespace Spine {
 			: base(frameCount) {
 		}
 
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction) {
+		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 (pose) {
-				case MixPose.Setup:
+				switch (blend) {
+				case MixBlend.Setup:
 					bone.shearX = bone.data.shearX;
 					bone.shearY = bone.data.shearY;
 					return;
-				case MixPose.Current:
+				case MixBlend.First:
 					bone.shearX += (bone.data.shearX - bone.shearX) * alpha;
 					bone.shearY += (bone.data.shearY - bone.shearY) * alpha;
 					return;
@@ -490,12 +564,20 @@ namespace Spine {
 				x = x + (frames[frame + X] - x) * percent;
 				y = y + (frames[frame + Y] - y) * percent;
 			}
-			if (pose == MixPose.Setup) {
-				bone.shearX = bone.data.shearX + x * alpha;
-				bone.shearY = bone.data.shearY + y * alpha;
-			} else {
-				bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha;
-				bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha;
+			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;
 			}
 		}
 	}
@@ -530,19 +612,19 @@ namespace Spine {
 			frames[frameIndex + A] = a;
 		}
 
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction) {
+		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 (pose) {
-				case MixPose.Setup:
+				switch (blend) {
+				case MixBlend.Setup:
 					slot.r = slotData.r;
 					slot.g = slotData.g;
 					slot.b = slotData.b;
 					slot.a = slotData.a;
 					return;
-				case MixPose.Current:
+				case MixBlend.First:
 					slot.r += (slot.r - slotData.r) * alpha;
 					slot.g += (slot.g - slotData.g) * alpha;
 					slot.b += (slot.b - slotData.b) * alpha;
@@ -582,7 +664,7 @@ namespace Spine {
 				slot.a = a;
 			} else {
 				float br, bg, bb, ba;
-				if (pose == MixPose.Setup) {
+				if (blend == MixBlend.Setup) {
 					br = slot.data.r;
 					bg = slot.data.g;
 					bb = slot.data.b;
@@ -641,13 +723,13 @@ namespace Spine {
 			frames[frameIndex + B2] = b2;
 		}
 
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction) {
+		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 (pose) {
-				case MixPose.Setup:
+				switch (blend) {
+				case MixBlend.Setup:
 					//	slot.color.set(slot.data.color);
 					//	slot.darkColor.set(slot.data.darkColor);
 					slot.r = slotData.r;
@@ -658,7 +740,7 @@ namespace Spine {
 					slot.g2 = slotData.g2;
 					slot.b2 = slotData.b2;
 					return;
-				case MixPose.Current:
+				case MixBlend.First:
 					slot.r += (slot.r - slotData.r) * alpha;
 					slot.g += (slot.g - slotData.g) * alpha;
 					slot.b += (slot.b - slotData.b) * alpha;
@@ -713,7 +795,7 @@ namespace Spine {
 				slot.b2 = b2;
 			} else {
 				float br, bg, bb, ba, br2, bg2, bb2;
-				if (pose == MixPose.Setup) {
+				if (blend == MixBlend.Setup) {
 					br = slot.data.r;
 					bg = slot.data.g;
 					bb = slot.data.b;
@@ -767,10 +849,10 @@ namespace Spine {
 			attachmentNames[frameIndex] = attachmentName;
 		}
 
-		public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction) {
+		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 && pose == MixPose.Setup) {
+			if (direction == MixDirection.Out && blend == MixBlend.Setup) {
 				attachmentName = slot.data.attachmentName;
 				slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName);
 				return;
@@ -778,7 +860,7 @@ namespace Spine {
 
 			float[] frames = this.frames;
 			if (time < frames[0]) { // Time is before first frame.
-				if (pose == MixPose.Setup) {
+				if (blend == MixBlend.Setup || blend == MixBlend.First) {
 					attachmentName = slot.data.attachmentName;
 					slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName);
 				}
@@ -823,13 +905,13 @@ namespace Spine {
 			frameVertices[frameIndex] = vertices;
 		}
 
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction) {
+		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) alpha = 1;
+			if (verticesArray.Count == 0) blend = MixBlend.Setup;
 
 			float[][] frameVertices = this.frameVertices;
 			int vertexCount = frameVertices[0].Length;
@@ -838,11 +920,11 @@ namespace Spine {
 
 			if (time < frames[0]) {
 				
-				switch (pose) {
-				case MixPose.Setup:
+				switch (blend) {
+				case MixBlend.Setup:
 					verticesArray.Clear();
 					return;
-				case MixPose.Current:
+				case MixBlend.Replace:
 					if (alpha == 1) {
 						verticesArray.Clear();
 						return;
@@ -877,27 +959,60 @@ namespace Spine {
 			vertices = verticesArray.Items;
 
 			if (time >= frames[frames.Length - 1]) { // Time is after last frame.
+
 				float[] lastVertices = frameVertices[frames.Length - 1];
 				if (alpha == 1) {
-					// Vertex positions or deform offsets, no alpha.
-					Array.Copy(lastVertices, 0, vertices, 0, vertexCount);
-				} else if (pose == MixPose.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;
+					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 {
-						// Weighted deform offsets, with alpha.
-						for (int i = 0; i < vertexCount; i++)
-							vertices[i] = lastVertices[i] * alpha;
+						// Vertex positions or deform offsets, no alpha.
+						Array.Copy(lastVertices, 0, vertices, 0, vertexCount);
 					}
 				} else {
-					// Vertex positions or deform offsets, with alpha.
-					for (int i = 0; i < vertexCount; i++)
-						vertices[i] += (lastVertices[i] - vertices[i]) * alpha;
+					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;
 			}
@@ -910,31 +1025,73 @@ namespace Spine {
 			float percent = GetCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime));
 
 			if (alpha == 1) {
-				// Vertex positions or deform offsets, no alpha.
-				for (int i = 0; i < vertexCount; i++) {
-					float prev = prevVertices[i];
-					vertices[i] = prev + (nextVertices[i] - prev) * percent;
-				}
-			} else if (pose == MixPose.Setup) {
-				if (vertexAttachment.bones == null) {
-					// Unweighted vertex positions, with alpha.
-					var setupVertices = vertexAttachment.vertices;
-					for (int i = 0; i < vertexCount; i++) {
-						float prev = prevVertices[i], setup = setupVertices[i];
-						vertices[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha;
+				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 {
-					// Weighted deform offsets, with alpha.
+					// 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) * alpha;
+						vertices[i] = prev + (nextVertices[i] - prev) * percent;
 					}
 				}
 			} else {
-				// Vertex positions or deform offsets, with alpha.
-				for (int i = 0; i < vertexCount; i++) {
-					float prev = prevVertices[i];
-					vertices[i] += (prev + (nextVertices[i] - prev) * percent - vertices[i]) * alpha;
+				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;
+					}
 				}
 			}
 		}
@@ -964,13 +1121,13 @@ namespace Spine {
 		}
 
 		/// <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, MixPose pose, MixDirection direction) {
+		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, pose, direction);
+				Apply(skeleton, lastTime, int.MaxValue, firedEvents, alpha, blend, direction);
 				lastTime = -1f;
 			} else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame.
 				return;
@@ -1016,17 +1173,17 @@ namespace Spine {
 			drawOrders[frameIndex] = drawOrder;
 		}
 
-		public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction) {
+		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 && pose == MixPose.Setup) {
+			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 (pose == MixPose.Setup) Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count);
+				if (blend == MixBlend.Setup || blend == MixBlend.First) Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count);
 				return;
 			}
 
@@ -1078,16 +1235,16 @@ namespace Spine {
 			frames[frameIndex + BEND_DIRECTION] = bendDirection;
 		}
 
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction) {
+		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 (pose) {
-				case MixPose.Setup:
+				switch (blend) {
+				case MixBlend.Setup:
 					constraint.mix = constraint.data.mix;
 					constraint.bendDirection = constraint.data.bendDirection;
 					return;
-				case MixPose.Current:
+				case MixBlend.First:
 					constraint.mix += (constraint.data.mix - constraint.mix) * alpha;
 					constraint.bendDirection = constraint.data.bendDirection;
 					return;
@@ -1096,7 +1253,7 @@ namespace Spine {
 			}
 
 			if (time >= frames[frames.Length - ENTRIES]) { // Time is after last frame.
-				if (pose == MixPose.Setup) {
+				if (blend == MixBlend.Setup) {
 					constraint.mix = constraint.data.mix + (frames[frames.Length + PREV_MIX] - constraint.data.mix) * alpha;
 					constraint.bendDirection = direction == MixDirection.Out ? constraint.data.bendDirection
 						: (int)frames[frames.Length + PREV_BEND_DIRECTION];
@@ -1113,7 +1270,7 @@ namespace Spine {
 			float frameTime = frames[frame];
 			float percent = GetCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
 
-			if (pose == MixPose.Setup) {
+			if (blend == MixBlend.Setup) {
 				constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha;
 				constraint.bendDirection = direction == MixDirection.Out ? constraint.data.bendDirection : (int)frames[frame + PREV_BEND_DIRECTION];
 			} else {
@@ -1152,19 +1309,19 @@ namespace Spine {
 			frames[frameIndex + SHEAR] = shearMix;
 		}
 
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction) {
+		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 (pose) {
-				case MixPose.Setup:
+				switch (blend) {
+				case MixBlend.Setup:
 					constraint.rotateMix = data.rotateMix;
 					constraint.translateMix = data.translateMix;
 					constraint.scaleMix = data.scaleMix;
 					constraint.shearMix = data.shearMix;
 					return;
-				case MixPose.Current:
+				case MixBlend.First:
 					constraint.rotateMix += (data.rotateMix - constraint.rotateMix) * alpha;
 					constraint.translateMix += (data.translateMix - constraint.translateMix) * alpha;
 					constraint.scaleMix += (data.scaleMix - constraint.scaleMix) * alpha;
@@ -1197,7 +1354,7 @@ namespace Spine {
 				scale += (frames[frame + SCALE] - scale) * percent;
 				shear += (frames[frame + SHEAR] - shear) * percent;
 			}
-			if (pose == MixPose.Setup) {
+			if (blend == MixBlend.Setup) {
 				TransformConstraintData data = constraint.data;
 				constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha;
 				constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha;
@@ -1239,15 +1396,15 @@ namespace Spine {
 			frames[frameIndex + VALUE] = value;
 		}
 
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction) {
+		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 (pose) {
-				case MixPose.Setup:
+				switch (blend) {
+				case MixBlend.Setup:
 					constraint.position = constraint.data.position;
 					return;
-				case MixPose.Current:
+				case MixBlend.First:
 					constraint.position += (constraint.data.position - constraint.position) * alpha;
 					return;
 				}
@@ -1267,7 +1424,7 @@ namespace Spine {
 
 				position += (frames[frame + VALUE] - position) * percent;
 			}
-			if (pose == MixPose.Setup)
+			if (blend == MixBlend.Setup)
 				constraint.position = constraint.data.position + (position - constraint.data.position) * alpha;
 			else
 				constraint.position += (position - constraint.position) * alpha;
@@ -1283,15 +1440,15 @@ namespace Spine {
 			: base(frameCount) {
 		}
 
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction) {
+		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 (pose) {
-				case MixPose.Setup:
+				switch (blend) {
+				case MixBlend.Setup:
 					constraint.spacing = constraint.data.spacing;
 					return;
-				case MixPose.Current:
+				case MixBlend.First:
 					constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha;
 					return;
 				}
@@ -1312,7 +1469,7 @@ namespace Spine {
 				spacing += (frames[frame + VALUE] - spacing) * percent;
 			}
 
-			if (pose == MixPose.Setup)
+			if (blend == MixBlend.Setup)
 				constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha;
 			else
 				constraint.spacing += (spacing - constraint.spacing) * alpha;
@@ -1347,16 +1504,16 @@ namespace Spine {
 			frames[frameIndex + TRANSLATE] = translateMix;
 		}
 
-		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha, MixPose pose, MixDirection direction) {
+		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 (pose) {
-				case MixPose.Setup:
+				switch (blend) {
+				case MixBlend.Setup:
 					constraint.rotateMix = constraint.data.rotateMix;
 					constraint.translateMix = constraint.data.translateMix;
 					return;
-				case MixPose.Current:
+				case MixBlend.First:
 					constraint.rotateMix += (constraint.data.rotateMix - constraint.rotateMix) * alpha;
 					constraint.translateMix += (constraint.data.translateMix - constraint.translateMix) * alpha;
 					return;
@@ -1381,7 +1538,7 @@ namespace Spine {
 				translate += (frames[frame + TRANSLATE] - translate) * percent;
 			}
 
-			if (pose == MixPose.Setup) {
+			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 {

+ 92 - 63
spine-csharp/src/AnimationState.cs

@@ -54,13 +54,13 @@ namespace Spine {
 		public ExposedList<TrackEntry> Tracks { get { return tracks; } }
 		public float TimeScale { get { return timeScale; } set { timeScale = value; } }
 
-		public delegate void TrackEntryDelegate (TrackEntry trackEntry);
+		public delegate void TrackEntryDelegate(TrackEntry trackEntry);
 		public event TrackEntryDelegate Start, Interrupt, End, Dispose, Complete;
 
-		public delegate void TrackEntryEventDelegate (TrackEntry trackEntry, Event e);
+		public delegate void TrackEntryEventDelegate(TrackEntry trackEntry, Event e);
 		public event TrackEntryEventDelegate Event;
 
-		public AnimationState (AnimationStateData data) {
+		public AnimationState(AnimationStateData data) {
 			if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
 			this.data = data;
 			this.queue = new EventQueue(
@@ -171,12 +171,13 @@ namespace Spine {
 				TrackEntry current = tracksItems[i];
 				if (current == null || current.delay > 0) continue;
 				applied = true;
-				MixPose currentPose = i == 0 ? MixPose.Current : MixPose.CurrentLayered;
+
+				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, currentPose);
+					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.
 
@@ -185,9 +186,9 @@ namespace Spine {
 				int timelineCount = current.animation.timelines.Count;
 				var timelines = current.animation.timelines;
 				var timelinesItems = timelines.Items;
-				if (mix == 1) {
+				if (mix == 1 || blend == MixBlend.Add) {
 					for (int ii = 0; ii < timelineCount; ii++)
-						timelinesItems[ii].Apply(skeleton, animationLast, animationTime, events, 1, MixPose.Setup, MixDirection.In);
+						timelinesItems[ii].Apply(skeleton, animationLast, animationTime, events, mix, blend, MixDirection.In);
 				} else {
 					var timelineData = current.timelineData.Items;
 
@@ -197,12 +198,12 @@ namespace Spine {
 
 					for (int ii = 0; ii < timelineCount; ii++) {
 						Timeline timeline = timelinesItems[ii];
-						MixPose pose = timelineData[ii] >= AnimationState.First ? MixPose.Setup : currentPose;
+						MixBlend timelineBlend = timelineData[ii] >= AnimationState.Subsequent ? blend : MixBlend.Setup;
 						var rotateTimeline = timeline as RotateTimeline;
 						if (rotateTimeline != null)
-							ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, pose, timelinesRotation, ii << 1, firstFrame);
+							ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, ii << 1, firstFrame);
 						else
-							timeline.Apply(skeleton, animationLast, animationTime, events, mix, pose, MixDirection.In);
+							timeline.Apply(skeleton, animationLast, animationTime, events, mix, timelineBlend, MixDirection.In);
 					}
 				}
 				QueueEvents(current, animationTime);
@@ -215,17 +216,18 @@ namespace Spine {
 			return applied;
 		}
 
-		private float ApplyMixingFrom (TrackEntry to, Skeleton skeleton, MixPose currentPose) {
+		private float ApplyMixingFrom (TrackEntry to, Skeleton skeleton, MixBlend blend) {
 			TrackEntry from = to.mixingFrom;
-			if (from.mixingFrom != null) ApplyMixingFrom(from, skeleton, currentPose);
+			if (from.mixingFrom != null) ApplyMixingFrom(from, skeleton, blend);
 
 			float mix;
 			if (to.mixDuration == 0) { // Single frame mix to undo mixingFrom changes.
 				mix = 1;
-				currentPose = MixPose.Setup;
+				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;
@@ -234,45 +236,55 @@ namespace Spine {
 			var timelines = from.animation.timelines;
 			int timelineCount = timelines.Count;
 			var timelinesItems = timelines.Items;
-			var timelineData = from.timelineData.Items;
-			var timelineDipMix = from.timelineDipMix.Items;
-
-			bool firstFrame = from.timelinesRotation.Count == 0;
-			if (firstFrame) from.timelinesRotation.Resize(timelines.Count << 1); // from.timelinesRotation.setSize
-			var timelinesRotation = from.timelinesRotation.Items;
-
-			MixPose pose;
-			float alphaDip = from.alpha * to.interruptAlpha, alphaMix = alphaDip * (1 - mix), alpha;
-			from.totalAlpha = 0;
-			for (int i = 0; i < timelineCount; i++) {
-				Timeline timeline = timelinesItems[i];
-				switch (timelineData[i]) {
-				case Subsequent:
-					if (!attachments && timeline is AttachmentTimeline) continue;
-					if (!drawOrder && timeline is DrawOrderTimeline) continue;
-					pose = currentPose;
-					alpha = alphaMix;
-					break;
-				case First:
-					pose = MixPose.Setup;
-					alpha = alphaMix;
-					break;
-				case Dip:
-					pose = MixPose.Setup;
-					alpha = alphaDip;
-					break;
-				default:
-					pose = MixPose.Setup;
-					TrackEntry dipMix = timelineDipMix[i];
-					alpha = alphaDip * Math.Max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
-					break;
-				}
-				from.totalAlpha += alpha;
-				var rotateTimeline = timeline as RotateTimeline;
-				if (rotateTimeline != null) {
-					ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, alpha, pose, timelinesRotation, i << 1, firstFrame);
-				} else {
-					timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, alpha, pose, MixDirection.Out);
+			float alphaDip = from.alpha * to.interruptAlpha, alphaMix = alphaDip * (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 timelineData = from.timelineData.Items;
+				var timelineDipMix = from.timelineDipMix.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];
+					MixBlend timelineBlend;
+					float alpha;
+					switch (timelineData[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.Dip:
+							timelineBlend = MixBlend.Setup;
+							alpha = alphaDip;
+							break;
+						default:
+							timelineBlend = MixBlend.Setup;
+							TrackEntry dipMix = timelineDipMix[i];
+							alpha = alphaDip * Math.Max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
+							break;
+					}
+					from.totalAlpha += alpha;
+
+					var rotateTimeline = timeline as RotateTimeline;
+					if (rotateTimeline != null) {
+						ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, alpha, timelineBlend, timelinesRotation, i << 1, firstFrame);
+					} else {
+						timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, alpha, timelineBlend, MixDirection.Out);
+					}
 				}
 			}
 
@@ -284,7 +296,7 @@ namespace Spine {
 			return mix;
 		}
 
-		static private void ApplyRotateTimeline (RotateTimeline rotateTimeline, Skeleton skeleton, float time, float alpha, MixPose pose,
+		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;
@@ -297,7 +309,7 @@ namespace Spine {
 			Bone bone = skeleton.bones.Items[rotateTimeline.boneIndex];
 			float[] frames = rotateTimeline.frames;
 			if (time < frames[0]) {
-				if (pose == MixPose.Setup) bone.rotation = bone.data.rotation;
+				if (pose == MixBlend.Setup) bone.rotation = bone.data.rotation;
 				return;
 			}
 
@@ -319,7 +331,7 @@ namespace Spine {
 			}
 
 			// Mix between rotations using the direction of the shortest route on the first frame while detecting crosses.
-			float r1 = pose == MixPose.Setup ? bone.data.rotation : bone.rotation;
+			float r1 = pose == MixBlend.Setup ? bone.data.rotation : bone.rotation;
 			float total, diff = r2 - r1;
 			if (diff == 0) {
 				total = timelinesRotation[i];
@@ -490,8 +502,9 @@ namespace Spine {
 		/// <summary>Adds an animation to be played delay seconds after the current or last queued animation
 		/// for a track. If the track is empty, it is equivalent to calling <see cref="SetAnimation"/>.</summary>
 		/// <param name="delay">
-		/// Seconds to begin this animation after the start of the previous animation. May be &lt;= 0 to use the animation
-		/// duration of the previous track minus any mix duration plus the negative delay.
+		/// 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 plus the specified<code>delay</code>.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>
@@ -627,7 +640,7 @@ namespace Spine {
 			var tracksItems = tracks.Items;
 			for (int i = 0, n = tracks.Count; i < n; i++) {
 				var entry = tracksItems[i];
-				if (entry != null) entry.SetTimelineData(null, mixingTo, propertyIDs);
+				if (entry != null && (i == 0 || entry.mixBlend != MixBlend.Add)) entry.SetTimelineData(null, mixingTo, propertyIDs);
 			}
 		}
 
@@ -667,6 +680,7 @@ namespace Spine {
 		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> timelineData = new ExposedList<int>();
 		internal readonly ExposedList<TrackEntry> timelineDipMix = new ExposedList<TrackEntry>();
 		internal readonly ExposedList<float> timelinesRotation = new ExposedList<float>();
@@ -870,6 +884,17 @@ namespace Spine {
 		/// </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>
@@ -885,13 +910,17 @@ namespace Spine {
 		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.
-		///
-		/// Mixing involves finding a rotation between two others, which has two possible solutions: the short way or the long way around.
-		/// The two rotations likely change over time, so which direction is the short or long way also changes.
-		/// If the short way was always chosen, bones would flip to the other side when that direction became the long way.
-		/// TrackEntry chooses the short way the first time it is applied and remembers that direction.</summary>
+		/// </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();
 		}