瀏覽代碼

Merge remote-tracking branch 'origin/3.6-beta' into 3.6-beta

NathanSweet 8 年之前
父節點
當前提交
277f96d50f

二進制
spine-as3/spine-as3-example/lib/spine-as3.swc


+ 80 - 136
spine-as3/spine-as3/src/spine/animation/AnimationState.as

@@ -36,6 +36,9 @@ package spine.animation {
 	import flash.utils.Dictionary;
 
 	public class AnimationState {
+		public static var SUBSEQUENT : int = 0;
+		public static var FIRST : int = 1;
+		public static var DIP : int = 2;
 		internal static var emptyAnimation : Animation = new Animation("<empty>", new Vector.<Timeline>(), 0);
 		public var data : AnimationStateData;
 		public var tracks : Vector.<TrackEntry> = new Vector.<TrackEntry>();
@@ -48,8 +51,8 @@ package spine.animation {
 		public var onEvent : Listeners = new Listeners();
 		internal var queue : EventQueue;
 		internal var propertyIDs : Dictionary = new Dictionary();
-		internal var animationsChanged : Boolean;
-		public var multipleMixing : Boolean = false;
+		internal var mixingTo : Vector.<TrackEntry> = new Vector.<TrackEntry>();
+		internal var animationsChanged : Boolean;			
 		public var timeScale : Number = 1;
 		internal var trackEntryPool : Pool;
 
@@ -104,7 +107,15 @@ package spine.animation {
 						continue;
 					}
 				}
-				updateMixingFrom(current, delta);
+				if (current.mixingFrom != null && updateMixingFrom(current, delta, 2)) {
+					// End mixing from entries once all have completed.
+					var from : TrackEntry = current.mixingFrom;
+					current.mixingFrom = null;
+					while (from != null) {
+						queue.end(from);
+						from = from.mixingFrom;
+					}
+				}
 
 				current.trackTime += currentDelta;
 			}
@@ -112,22 +123,26 @@ package spine.animation {
 			queue.drain();
 		}
 
-		private function updateMixingFrom(entry : TrackEntry, delta : Number) : void {
+		private function updateMixingFrom(entry : TrackEntry, delta : Number, animationCount : int) : Boolean {
 			var from : TrackEntry = entry.mixingFrom;
-			if (from == null) return;
+			if (from == null) return true;
 
-			updateMixingFrom(from, delta);
+			var finished : Boolean = updateMixingFrom(from, delta, animationCount + 1);
 
-			if (entry.mixTime >= entry.mixDuration && from.mixingFrom == null && entry.mixTime > 0) {
-				entry.mixingFrom = null;
-				queue.end(from);
-				return;
+			// Require mixTime > 0 to ensure the mixing from entry was applied at least once.
+			if (entry.mixTime > 0 && (entry.mixTime >= entry.mixDuration || entry.timeScale == 0)) {
+				if (animationCount > 6 && from.mixingFrom == null) { // Limit the mixing from linked list.
+					entry.mixingFrom = null;
+					queue.end(from);
+				}
+				return finished;
 			}
-
+	
 			from.animationLast = from.nextAnimationLast;
 			from.trackLast = from.nextTrackLast;
 			from.trackTime += delta * from.timeScale;
 			entry.mixTime += delta * entry.timeScale;
+			return false;
 		}
 
 		public function apply(skeleton : Skeleton) : void {
@@ -156,17 +171,18 @@ package spine.animation {
 					for (ii = 0; ii < timelineCount; ii++)
 						Timeline(timelines[ii]).apply(skeleton, animationLast, animationTime, events, 1, true, false);
 				} else {
+					var timelineData : Vector.<int> = current.timelineData;
+					
 					var firstFrame : Boolean = current.timelinesRotation.length == 0;
 					if (firstFrame) current.timelinesRotation.length = timelineCount << 1;
 					var timelinesRotation : Vector.<Number> = current.timelinesRotation;
-
-					var timelinesFirst : Vector.<Boolean> = current.timelinesFirst;
+					
 					for (ii = 0; ii < timelineCount; ii++) {
 						var timeline : Timeline = timelines[ii];
 						if (timeline is RotateTimeline) {
-							applyRotateTimeline(timeline, skeleton, animationTime, mix, timelinesFirst[ii], timelinesRotation, ii << 1, firstFrame);
+							applyRotateTimeline(timeline, skeleton, animationTime, mix, timelineData[ii] > 0, timelinesRotation, ii << 1, firstFrame);
 						} else
-							timeline.apply(skeleton, animationLast, animationTime, events, mix, timelinesFirst[ii], false);
+							timeline.apply(skeleton, animationLast, animationTime, events, mix, timelineData[ii] > 0, false);
 					}
 				}
 				queueEvents(current, animationTime);
@@ -178,15 +194,15 @@ package spine.animation {
 			queue.drain();
 		}
 
-		private function applyMixingFrom(entry : TrackEntry, skeleton : Skeleton) : Number {
-			var from : TrackEntry = entry.mixingFrom;
+		private function applyMixingFrom(to : TrackEntry, skeleton : Skeleton) : Number {
+			var from : TrackEntry = to.mixingFrom;
 			if (from.mixingFrom != null) applyMixingFrom(from, skeleton);
 
 			var mix : Number = 0;
-			if (entry.mixDuration == 0) // Single frame mix to undo mixingFrom changes.
+			if (to.mixDuration == 0) // Single frame mix to undo mixingFrom changes.
 				mix = 1;
 			else {
-				mix = entry.mixTime / entry.mixDuration;
+				mix = to.mixTime / to.mixDuration;
 				if (mix > 1) mix = 1;
 			}
 
@@ -195,35 +211,51 @@ package spine.animation {
 			var animationLast : Number = from.animationLast, animationTime : Number = from.getAnimationTime();
 			var timelineCount : int = from.animation.timelines.length;
 			var timelines : Vector.<Timeline> = from.animation.timelines;
-			var timelinesFirst : Vector.<Boolean> = from.timelinesFirst;
-			var timelinesLast : Vector.<Boolean> = multipleMixing ? null : from.timelinesLast;
-			var alphaBase : Number = from.alpha * entry.mixAlpha;
-			var alphaMix : Number = alphaBase * (1 - mix);
+			var timelineData : Vector.<int> = from.timelineData;
+			var timelineDipMix : Vector.<TrackEntry> = from.timelineDipMix;
 
 			var firstFrame : Boolean = from.timelinesRotation.length == 0;
 			if (firstFrame) from.timelinesRotation.length = timelineCount << 1;
 			var timelinesRotation : Vector.<Number> = from.timelinesRotation;
 
+			var first : Boolean = false;
+			var alphaDip : Number = from.alpha * to.interruptAlpha;
+			var alphaMix : Number = alphaDip * (1 - mix);
+			var alpha : Number = 0;
 			for (var i : int = 0; i < timelineCount; i++) {
 				var timeline : Timeline = timelines[i];
-				var setupPose : Boolean = timelinesFirst[i];
-				var alpha : Number = timelinesLast != null && setupPose && !timelinesLast[i] ? alphaBase : alphaMix;
+				switch (timelineData[i]) {
+				case SUBSEQUENT:
+					first = false;
+					alpha = alphaMix;
+					break;
+				case FIRST:
+					first = true;
+					alpha = alphaMix;
+					break;
+				default:
+					first = true;
+					alpha = alphaDip;
+					var dipMix : TrackEntry = timelineDipMix[i];
+					if (dipMix != null) alpha *= Math.max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
+					break;
+				}
 				if (timeline is RotateTimeline)
-					applyRotateTimeline(timeline, skeleton, animationTime, alpha, setupPose, timelinesRotation, i << 1, firstFrame);
+					applyRotateTimeline(timeline, skeleton, animationTime, alpha, first, timelinesRotation, i << 1, firstFrame);
 				else {
-					if (!setupPose) {
+					if (!first) {
 						if (!attachments && timeline is AttachmentTimeline) continue;
 						if (!drawOrder && timeline is DrawOrderTimeline) continue;
 					}
-					timeline.apply(skeleton, animationLast, animationTime, events, alpha, setupPose, true);
+					timeline.apply(skeleton, animationLast, animationTime, events, alpha, first, true);
 				}
 			}
-
-			if (entry.mixDuration > 0) queueEvents(from, animationTime);
+	
+			if (to.mixDuration > 0) queueEvents(from, animationTime);
 			this.events.length = 0;
 			from.nextAnimationLast = animationTime;
 			from.nextTrackLast = from.trackTime;
-
+	
 			return mix;
 		}
 
@@ -355,36 +387,19 @@ package spine.animation {
 		private function setCurrent(index : int, current : TrackEntry, interrupt : Boolean) : void {
 			var from : TrackEntry = expandToIndex(index);
 			tracks[index] = current;
-
+	
 			if (from != null) {
 				if (interrupt) queue.interrupt(from);
 				current.mixingFrom = from;
 				current.mixTime = 0;
-
-				var mixingFrom : TrackEntry = from.mixingFrom;
-				if (mixingFrom != null && from.mixDuration > 0) {
-					if (multipleMixing) {
-						current.mixAlpha *= Math.min(from.mixTime / from.mixDuration, 1);
-					} else {
-						// A mix was interrupted, mix from the closest animation.
-						if (from.mixTime / from.mixDuration < 0.5 && mixingFrom.animation != AnimationState.emptyAnimation) {
-							current.mixingFrom = mixingFrom;
-							mixingFrom.mixingFrom = from;
-							mixingFrom.mixTime = from.mixDuration - from.mixTime;
-							mixingFrom.mixDuration = from.mixDuration;
-							from.mixingFrom = null;
-							from = mixingFrom;
-						}
-						
-						from.mixAlpha = 0;
-						from.mixTime = 0;
-						from.mixDuration = 0;					
-					}
-				}
-
-				from.timelinesRotation.length = 0;
+	
+				// Store the interrupted mix percentage.
+				if (from.mixingFrom != null && from.mixDuration > 0)
+					current.interruptAlpha *= Math.min(1, from.mixTime / from.mixDuration);
+	
+				from.timelinesRotation.length = 0; // Reset rotation for mixing out, in case entry was mixed in.
 			}
-
+	
 			queue.start(current);
 		}
 
@@ -506,7 +521,7 @@ package spine.animation {
 			entry.timeScale = 1;
 
 			entry.alpha = 1;
-			entry.mixAlpha = 1;
+			entry.interruptAlpha = 1;
 			entry.mixTime = 0;
 			entry.mixDuration = last == null ? 0 : data.getMix(last.animation, animation);
 			return entry;
@@ -524,89 +539,18 @@ package spine.animation {
 		private function _animationsChanged() : void {
 			animationsChanged = false;
 
-			var propertyIDs : Dictionary = this.propertyIDs = new Dictionary();
-
-			// Compute timelinesFirst from lowest to highest track entries.
-			var i : int = 0, n : int = tracks.length;
-			for (var key : String in propertyIDs) {
-				delete propertyIDs[key];
-			}
-			var entry : TrackEntry;
-			for (; i < n; i++) { // Find first non-null entry.
-				entry = tracks[i];
-				if (entry == null) continue;
-				setTimelinesFirst(entry);
-				i++;
-				break;
-			}
-			for (; i < n; i++) { // Rest of entries.
-				entry = tracks[i];
-				if (entry != null) checkTimelinesFirst(entry);
-			}
-			
-			if (multipleMixing) return;
-
-			// Set timelinesLast for mixingFrom entries, from highest track to lowest that has mixingFrom.
-			propertyIDs = this.propertyIDs = new Dictionary();
-			var lowestMixingFrom : int = n;
-			for (i = 0; i < n; i++) { // Find lowest track with a mixingFrom entry.
-				entry = tracks[i];
-				if (entry == null || entry.mixingFrom == null) continue;
-				lowestMixingFrom = i;
-				break;
-			}
-			for (i = n - 1; i >= lowestMixingFrom; i--) { // Find first non-null entry.
-				entry = tracks[i];
-				if (entry == null) continue;
-	
-				// Store properties for non-mixingFrom entry but don't set timelinesLast, which is only used for mixingFrom entries.
-				var timelines : Vector.<Timeline> = entry.animation.timelines;
-				for (var ii : int = 0, nn : int = entry.animation.timelines.length; ii < nn; ii++)					
-					propertyIDs[timelines[ii].getPropertyId().toString()] = true;				
-	
-				entry = entry.mixingFrom;
-				while (entry != null) {
-					checkTimelinesUsage(entry, entry.timelinesLast);
-					entry = entry.mixingFrom;
+			var propertyIDs : Dictionary = this.propertyIDs = new Dictionary();					
+			var mixingTo : Vector.<TrackEntry> = this.mixingTo;
+			var lastEntry : TrackEntry = null;
+			for (var i : int = 0, n : int = tracks.length; i < n; i++) {
+				var entry : TrackEntry = tracks[i];
+				if (entry != null) {
+					entry.setTimelineData(lastEntry, mixingTo, propertyIDs);
+					lastEntry = entry;
 				}
 			}
 		}
 
-		private function setTimelinesFirst(entry : TrackEntry) : void {
-			if (entry.mixingFrom != null) {
-				setTimelinesFirst(entry.mixingFrom);
-				checkTimelinesUsage(entry, entry.timelinesFirst);
-				return;
-			}
-			var propertyIDs : Dictionary = this.propertyIDs;
-			var timelines : Vector.<Timeline> = entry.animation.timelines;
-			var n : int = timelines.length;
-			var usage : Vector.<Boolean> = entry.timelinesFirst;
-			usage.length = n;
-			for (var i : int = 0; i < n; i++) {				
-				propertyIDs[timelines[i].getPropertyId().toString()] = true;
-				usage[i] = true;
-			}
-		}
-
-		private function checkTimelinesFirst(entry : TrackEntry) : void {
-			if (entry.mixingFrom != null) checkTimelinesFirst(entry.mixingFrom);
-			checkTimelinesUsage(entry, entry.timelinesFirst);
-		}
-
-		private function checkTimelinesUsage(entry : TrackEntry, usageArray : Vector.<Boolean>) : void {
-			var propertyIDs : Dictionary = this.propertyIDs;
-			var timelines : Vector.<Timeline> = entry.animation.timelines;
-			var n : int = timelines.length;
-			var usage : Vector.<Boolean> = usageArray;
-			usageArray.length = n;
-			for (var i : int = 0; i < n; i++) {
-				var id : String = timelines[i].getPropertyId().toString();
-				usage[i] = !propertyIDs.hasOwnProperty(id);
-				propertyIDs[id] = true;
-			}
-		}
-
 		public function getCurrent(trackIndex : int) : TrackEntry {
 			if (trackIndex >= tracks.length) return null;
 			return tracks[trackIndex];

+ 51 - 5
spine-as3/spine-as3/src/spine/animation/TrackEntry.as

@@ -29,6 +29,7 @@
  *****************************************************************************/
 
 package spine.animation {
+	import flash.utils.Dictionary;
 	import spine.Poolable;
 
 	public class TrackEntry implements Poolable {
@@ -45,9 +46,9 @@ package spine.animation {
 		public var eventThreshold : Number, attachmentThreshold : Number, drawOrderThreshold : Number;
 		public var animationStart : Number, animationEnd : Number, animationLast : Number, nextAnimationLast : Number;
 		public var delay : Number, trackTime : Number, trackLast : Number, nextTrackLast : Number, trackEnd : Number, timeScale : Number;
-		public var alpha : Number, mixTime : Number, mixDuration : Number, mixAlpha : Number;
-		public var timelinesFirst : Vector.<Boolean> = new Vector.<Boolean>();
-		public var timelinesLast : Vector.<Boolean> = new Vector.<Boolean>();
+		public var alpha : Number, mixTime : Number, mixDuration : Number, interruptAlpha : Number;
+		public var timelineData : Vector.<int> = new Vector.<int>();
+		public var timelineDipMix : Vector.<TrackEntry> = new Vector.<TrackEntry>();
 		public var timelinesRotation : Vector.<Number> = new Vector.<Number>();
 
 		public function TrackEntry() {
@@ -72,10 +73,55 @@ package spine.animation {
 			onDispose.listeners.length = 0;
 			onComplete.listeners.length = 0;
 			onEvent.listeners.length = 0;
-			timelinesFirst.length = 0;
-			timelinesLast.length = 0;
+			timelineData.length = 0;
+			timelineDipMix.length = 0;
 			timelinesRotation.length = 0;
 		}
+		
+		public function setTimelineData (to: TrackEntry, mixingToArray : Vector.<TrackEntry>, propertyIDs : Dictionary) : TrackEntry {
+			if (to != null) mixingToArray.push(to);
+			var lastEntry : TrackEntry = mixingFrom != null ? mixingFrom.setTimelineData(this, mixingToArray, propertyIDs) : this;
+			if (to != null) mixingToArray.pop();
+
+			var mixingTo : Vector.<TrackEntry> = mixingToArray;
+			var mixingToLast : int = mixingToArray.length - 1;
+			var timelines : Vector.<Timeline> = animation.timelines;
+			var timelinesCount : int = animation.timelines.length;
+			var timelineData : Vector.<int> = this.timelineData;
+			timelineData.length = timelinesCount;
+			var timelineDipMix : Vector.<TrackEntry> = this.timelineDipMix;
+			timelineDipMix.length = timelinesCount;
+
+			outer:
+			for (var i : int = 0; i < timelinesCount; i++) {
+				var intId : int = timelines[i].getPropertyId();
+				var id : String = intId.toString();
+				if (!(propertyIDs[id] == false)) {
+					propertyIDs[id] = true;
+					timelineData[i] = AnimationState.SUBSEQUENT;
+				} else if (to == null || !to.hasTimeline(intId))
+					timelineData[i] = AnimationState.FIRST;
+				else {
+					timelineData[i] = AnimationState.DIP;
+					for (var ii : int = mixingToLast; ii >= 0; ii--) {
+						var entry : TrackEntry = mixingTo[ii];
+						if (!entry.hasTimeline(intId)) {
+							if (entry.mixDuration > 0) timelineDipMix[i] = entry;
+							continue outer;
+						}
+					}
+					timelineDipMix[i] = null;
+				}
+			}
+			return lastEntry;
+		}
+
+		private function hasTimeline (id : int) : Boolean {
+			var timelines : Vector.<Timeline> = animation.timelines;
+			for (var i : int = 0, n : int = animation.timelines.length; i < n; i++)
+				if (timelines[i].getPropertyId() == id) return true;
+			return false;
+		}
 
 		public function resetRotationDirection() : void {
 			timelinesRotation.length = 0;

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

@@ -37,11 +37,11 @@ namespace Spine {
 		internal float duration;
 		internal String name;
 
-		public String Name { get { return 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) {
+		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;

+ 131 - 163
spine-csharp/src/AnimationState.cs

@@ -34,6 +34,7 @@ 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, DIP = 2;
 
 		private AnimationStateData data;
 		private readonly ExposedList<TrackEntry> tracks = new ExposedList<TrackEntry>();
@@ -41,24 +42,8 @@ namespace Spine {
 		private readonly ExposedList<Event> events = new ExposedList<Event>();
 		private readonly EventQueue queue;
 
+		private readonly ExposedList<TrackEntry> mixingTo = new ExposedList<TrackEntry>();
 		private bool animationsChanged;
-		private bool multipleMixing;
-		/// <summary>
-		/// <para>When false, only two animations can be mixed at once. Interrupting a mix by setting a new animation will choose from the 
-		/// two old animations the one that is closest to being fully mixed in and the other is discarded. Discarding an animation in 
-		/// this way may cause keyed values to jump.</para>
-		/// <para>When true, any number of animations may be mixed at once without causing keyed values to jump. Mixing is done by mixing out 
-		/// one or more animations while mixing in the newest one. When animations key the same value, this may cause "dipping", where 
-		/// the value moves toward the setup pose as the old animation mixes out, then back to the keyed value as the new animation 
-		/// mixes in.</para>
-		/// Defaults to false.</summary>
-		public bool MultipleMixing {
-			get { return multipleMixing; }
-			set {
-				multipleMixing = value;
-				animationsChanged = true;
-			}
-		}
 
 		private float timeScale = 1;
 
@@ -122,16 +107,23 @@ namespace Spine {
 						}
 						continue;
 					}
-				} else {
+				} 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.
-					if (current.trackLast >= current.trackEnd && current.mixingFrom == null) {
-						tracksItems[i] = null;
-						queue.End(current);
-						DisposeNext(current);
-						continue;
+					tracksItems[i] = null;
+
+					queue.End(current);
+					DisposeNext(current);
+					continue;
+				}
+				if (current.mixingFrom != null && UpdateMixingFrom(current, delta, 2)) {
+					// End mixing from entries once all have completed.
+					var from = current.mixingFrom;
+					current.mixingFrom = null;
+					while (from != null) {
+						queue.End(from);
+						from = from.mixingFrom;
 					}
 				}
-				UpdateMixingFrom(current, delta);
 
 				current.trackTime += currentDelta;
 			}
@@ -139,22 +131,27 @@ namespace Spine {
 			queue.Drain();
 		}
 
-		private void UpdateMixingFrom (TrackEntry entry, float delta) {
+		/// <summary>Returns true when all mixing from entries are complete.</summary>
+		private bool UpdateMixingFrom (TrackEntry entry, float delta, int animationCount) {
 			TrackEntry from = entry.mixingFrom;
-			if (from == null) return;
+			if (from == null) return true;
 
-			UpdateMixingFrom(from, delta);
+			bool finished = UpdateMixingFrom(from, delta, animationCount + 1);
 
-			if (entry.mixTime >= entry.mixDuration && from.mixingFrom == null && entry.mixTime > 0) {
-				entry.mixingFrom = null;
-				queue.End(from);
-				return;
+			// Require mixTime > 0 to ensure the mixing from entry was applied at least once.
+			if (entry.mixTime > 0 && (entry.mixTime >= entry.mixDuration || entry.timeScale == 0)) {
+				if (animationCount > 6 && from.mixingFrom == null) { // Limit the mixing from linked list.
+					entry.mixingFrom = null;
+					queue.End(from);
+				}
+				return finished;
 			}
 
 			from.animationLast = from.nextAnimationLast;
 			from.trackLast = from.nextTrackLast;
 			from.trackTime += delta * from.timeScale;
 			entry.mixTime += delta * entry.timeScale;
+			return false;
 		}
 
 
@@ -188,19 +185,20 @@ namespace Spine {
 					for (int ii = 0; ii < timelineCount; ii++)
 						timelinesItems[ii].Apply(skeleton, animationLast, animationTime, events, 1, true, false);
 				} else {
+					var timelineData = current.timelineData.Items;
+
 					bool firstFrame = current.timelinesRotation.Count == 0;
 					if (firstFrame) current.timelinesRotation.EnsureCapacity(timelines.Count << 1);
 					var timelinesRotation = current.timelinesRotation.Items;
 
-					var timelinesFirstItems = current.timelinesFirst.Items;
 					for (int ii = 0; ii < timelineCount; ii++) {
 						Timeline timeline = timelinesItems[ii];
 						var rotateTimeline = timeline as RotateTimeline;
 						if (rotateTimeline != null) {
-							ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, timelinesFirstItems[ii], timelinesRotation, ii << 1,
+							ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, timelineData[ii] > 0, timelinesRotation, ii << 1,
 								firstFrame);
 						} else {
-							timeline.Apply(skeleton, animationLast, animationTime, events, mix, timelinesFirstItems[ii], false);
+							timeline.Apply(skeleton, animationLast, animationTime, events, mix, timelineData[ii] > 0, false);
 						}
 					}
 				}
@@ -213,15 +211,15 @@ namespace Spine {
 			queue.Drain();
 		}
 
-		private float ApplyMixingFrom (TrackEntry entry, Skeleton skeleton) {
-			TrackEntry from = entry.mixingFrom;
+		private float ApplyMixingFrom (TrackEntry to, Skeleton skeleton) {
+			TrackEntry from = to.mixingFrom;
 			if (from.mixingFrom != null) ApplyMixingFrom(from, skeleton);
 
 			float mix;
-			if (entry.mixDuration == 0) // Single frame mix to undo mixingFrom changes.
+			if (to.mixDuration == 0) // Single frame mix to undo mixingFrom changes.
 				mix = 1;
 			else {
-				mix = entry.mixTime / entry.mixDuration;
+				mix = to.mixTime / to.mixDuration;
 				if (mix > 1) mix = 1;
 			}
 
@@ -229,36 +227,50 @@ namespace Spine {
 			bool attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold;
 			float animationLast = from.animationLast, animationTime = from.AnimationTime;
 			var timelines = from.animation.timelines;
-			var timelinesItems = timelines.Items;
 			int timelineCount = timelines.Count;
-			var timelinesFirst = from.timelinesFirst;
-			var timelinesFirstItems = timelinesFirst.Items;
-			var timelinesLastItems = multipleMixing ? null : from.timelinesLast.Items;
-			float alphaBase = from.alpha * entry.mixAlpha;
-			float alphaMix = alphaBase * (1 - mix);
+			var timelinesItems = timelines.Items;
+			var timelineData = from.timelineData.Items;
+			var timelineDipMix = from.timelineDipMix.Items;
 
-			bool firstFrame = entry.timelinesRotation.Count == 0;
-			if (firstFrame) entry.timelinesRotation.EnsureCapacity(timelines.Count << 1);
-			var timelinesRotation = entry.timelinesRotation.Items;
+			bool firstFrame = from.timelinesRotation.Count == 0;
+			if (firstFrame) from.timelinesRotation.Resize(timelines.Count << 1); // from.timelinesRotation.setSize
+			var timelinesRotation = from.timelinesRotation.Items;
 
+			bool first;
+			float alphaDip = from.alpha * to.interruptAlpha, alphaMix = alphaDip * (1 - mix), alpha;
 			for (int i = 0; i < timelineCount; i++) {
 				Timeline timeline = timelinesItems[i];
-				bool setupPose = timelinesFirstItems[i];
-				float alpha = timelinesLastItems != null && setupPose && !timelinesLastItems[i] ? alphaBase : alphaMix;
+				switch (timelineData[i]) {
+				case SUBSEQUENT:
+					first = false;
+					alpha = alphaMix;
+					break;
+				case FIRST:
+					first = true;
+					alpha = alphaMix;
+					break;
+				default:
+					first = true;
+					alpha = alphaDip;
+					var dipMix = timelineDipMix[i];
+					if (dipMix != null) alpha *= Math.Max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
+					break;
+				}
+
 				var rotateTimeline = timeline as RotateTimeline;
 				if (rotateTimeline != null) {
-					ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, alpha, setupPose, timelinesRotation, i << 1, firstFrame);
+					ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, alpha, first, timelinesRotation, i << 1, firstFrame);
 				} else {
-					if (!setupPose) {
+					if (!first) {
 						if (!attachments && timeline is AttachmentTimeline) continue;
 						if (!drawOrder && timeline is DrawOrderTimeline) continue;
 					}
-					timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, alpha, setupPose, true);
+					timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, alpha, first, true);
 				}
 			}
 
-			if (entry.mixDuration > 0 ) QueueEvents(from, animationTime);
-			events.Clear(false);
+			if (to.mixDuration > 0) QueueEvents(from, animationTime);
+			this.events.Clear(false);
 			from.nextAnimationLast = animationTime;
 			from.nextTrackLast = from.trackTime;
 
@@ -411,29 +423,9 @@ namespace Spine {
 				current.mixingFrom = from;
 				current.mixTime = 0;
 
-				//from.timelinesRotation.Clear();
-				var mixingFrom = from.mixingFrom;
-
-				if (mixingFrom != null && from.mixDuration > 0) {
-					if (multipleMixing) {
-						// The interrupted mix will mix out from its current percentage to zero.
-						current.mixAlpha *= Math.Min(from.mixTime / from.mixDuration, 1);
-					} else {
-						// A mix was interrupted, mix from the closest animation.
-						if (from.mixTime / from.mixDuration < 0.5f && mixingFrom.animation != AnimationState.EmptyAnimation) {
-							current.mixingFrom = mixingFrom;
-							mixingFrom.mixingFrom = from;
-							mixingFrom.mixTime = from.mixDuration - from.mixTime;
-							mixingFrom.mixDuration = from.mixDuration;
-							from.mixingFrom = null;
-							from = mixingFrom;
-						}
-
-						from.mixAlpha = 0;
-						from.mixTime = 0;
-						from.mixDuration = 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.
 			}
@@ -595,7 +587,7 @@ namespace Spine {
 			entry.timeScale = 1;
 
 			entry.alpha = 1;
-			entry.mixAlpha = 1;
+			entry.interruptAlpha = 1;
 			entry.mixTime = 0;
 			entry.mixDuration = (last == null) ? 0 : data.GetMix(last.animation, animation);
 			return entry;
@@ -614,88 +606,20 @@ namespace Spine {
 			animationsChanged = false;
 
 			var propertyIDs = this.propertyIDs;
-
-			// Set timelinesFirst for all entries, from lowest track to highest.
-			int i = 0, n = tracks.Count;
-			propertyIDs.Clear();
-			for (; i < n; i++) { // Find first non-null entry.
-				TrackEntry entry = tracks.Items[i];
-				if (entry == null) continue;
-				SetTimelinesFirst(entry);
-				i++;
-				break;
-			}
-			for (; i < n; i++) { // Rest of entries.
-				TrackEntry entry = tracks.Items[i];
-				if (entry != null) CheckTimelinesFirst(entry);
-			}
-
-			if (multipleMixing) return;
-
-			// Set timelinesLast for mixingFrom entries, from highest track to lowest that has mixingFrom.
 			propertyIDs.Clear();
-			int lowestMixingFrom = n;
-			for (i = 0; i < n; i++) { // Find lowest track with a mixingFrom entry.
-				TrackEntry entry = tracks.Items[i];
-				if (entry == null || entry.mixingFrom == null) continue;
-				lowestMixingFrom = i;
-				break;
-			}
-			for (i = n - 1; i >= lowestMixingFrom; i--) { // Find first non-null entry.
-				TrackEntry entry = tracks.Items[i];
-				if (entry == null) continue;
-
-				// Store properties for non-mixingFrom entry but don't set timelinesLast, which is only used for mixingFrom entries.
-				var timelines = entry.animation.timelines;
-				var timelinesItems = timelines.Items;
-				for (int ii = 0, nn = timelines.Count; ii < nn; ii++)
-					propertyIDs.Add(timelinesItems[ii].PropertyId);
+			var mixingTo = this.mixingTo;
 
-				entry = entry.mixingFrom;
-				while (entry != null) {
-					CheckTimelinesUsage(entry, entry.timelinesLast);
-					entry = entry.mixingFrom;
+			TrackEntry lastEntry = null;
+			var tracksItems = tracks.Items;
+			for (int i = 0, n = tracks.Count; i < n; i++) {
+				var entry = tracksItems[i];
+				if (entry != null) {
+					entry.SetTimelineData(lastEntry, mixingTo, propertyIDs);
+					lastEntry = entry;
 				}
 			}
 		}
 
-		/// <summary>From last to first mixingFrom entries, sets timelinesFirst to true on last, calls checkTimelineUsage on rest.</summary>
-		private void SetTimelinesFirst (TrackEntry entry) {
-			if (entry.mixingFrom != null) {
-				SetTimelinesFirst(entry.mixingFrom);
-				CheckTimelinesUsage(entry, entry.timelinesFirst);
-				return;
-			}
-			var propertyIDs = this.propertyIDs;
-			var timelines = entry.animation.timelines;
-			int n = timelines.Count;
-			entry.timelinesFirst.EnsureCapacity(n); // entry.timelinesFirst.setSize(n);
-			var usage = entry.timelinesFirst.Items;
-			var timelinesItems = timelines.Items;
-			for (int i = 0; i < n; i++) {
-				propertyIDs.Add(timelinesItems[i].PropertyId);
-				usage[i] = true;
-			}
-		}
-
-		/// <summary>From last to first mixingFrom entries, calls checkTimelineUsage.</summary>
-		private void CheckTimelinesFirst (TrackEntry entry) {
-			if (entry.mixingFrom != null) CheckTimelinesFirst(entry.mixingFrom);
-			CheckTimelinesUsage(entry, entry.timelinesFirst);
-		}
-
-		private void CheckTimelinesUsage (TrackEntry entry, ExposedList<bool> usageArray) {
-			var propertyIDs = this.propertyIDs;
-			var timelines = entry.animation.timelines;
-			int n = timelines.Count;
-			//var usageArray = entry.timelinesFirst;
-			usageArray.EnsureCapacity(n);
-			var usage = usageArray.Items;
-			var timelinesItems = timelines.Items;
-			for (int i = 0; i < n; i++)
-				usage[i] = propertyIDs.Add(timelinesItems[i].PropertyId);
-		}
-
 		/// <returns>The track entry for the animation currently playing on the track, or null if no animation is currently playing.</returns>
 		public TrackEntry GetCurrent (int trackIndex) {
 			return (trackIndex >= tracks.Count) ? null : tracks.Items[trackIndex];
@@ -731,9 +655,9 @@ namespace Spine {
 		internal float eventThreshold, attachmentThreshold, drawOrderThreshold;
 		internal float animationStart, animationEnd, animationLast, nextAnimationLast;
 		internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f;
-		internal float alpha, mixTime, mixDuration, mixAlpha;
-		internal readonly ExposedList<bool> timelinesFirst = new ExposedList<bool>();
-		internal readonly ExposedList<bool> timelinesLast = new ExposedList<bool>();
+		internal float alpha, mixTime, mixDuration, interruptAlpha;
+		internal readonly ExposedList<int> timelineData = new ExposedList<int>();
+		internal readonly ExposedList<TrackEntry> timelineDipMix = new ExposedList<TrackEntry>();
 		internal readonly ExposedList<float> timelinesRotation = new ExposedList<float>();
 
 		// IPoolable.Reset()
@@ -741,8 +665,8 @@ namespace Spine {
 			next = null;
 			mixingFrom = null;
 			animation = null;
-			timelinesFirst.Clear();
-			timelinesLast.Clear();
+			timelineData.Clear();
+			timelineDipMix.Clear();
 			timelinesRotation.Clear();
 
 			Start = null;
@@ -753,6 +677,49 @@ namespace Spine {
 			Event = null;
 		}
 
+		/// <param name="to">May be null.</param>
+		internal TrackEntry SetTimelineData (TrackEntry to, ExposedList<TrackEntry> mixingToArray, HashSet<int> propertyIDs) {
+			if (to != null) mixingToArray.Add(to);
+			var lastEntry = mixingFrom != null ? mixingFrom.SetTimelineData(this, mixingToArray, propertyIDs) : this;
+			if (to != null) mixingToArray.RemoveAt(mixingToArray.Count - 1); // mixingToArray.pop();
+
+			var mixingTo = mixingToArray.Items;
+			int mixingToLast = mixingToArray.Count - 1;
+			var timelines = animation.timelines.Items;
+			int timelinesCount = animation.timelines.Count;
+			var timelineDataItems = this.timelineData.Resize(timelinesCount).Items; // timelineData.setSize(timelinesCount);
+			var timelineDipMixItems = this.timelineDipMix.Resize(timelinesCount).Items; //timelineDipMix.setSize(timelinesCount);
+
+			// outer:
+			for (int i = 0; i < timelinesCount; i++) {
+				int id = timelines[i].PropertyId;
+				if (!propertyIDs.Add(id)) {
+					timelineDataItems[i] = AnimationState.SUBSEQUENT;
+				} else if (to == null || !to.HasTimeline(id)) {
+					timelineDataItems[i] = AnimationState.FIRST;
+				} else {
+					timelineDataItems[i] = AnimationState.DIP;
+					for (int ii = mixingToLast; ii >= 0; ii--) {
+						var entry = mixingTo[ii];
+						if (!entry.HasTimeline(id)) {
+							timelineDipMixItems[i] = entry;
+							goto outer; // continue outer;
+						}
+					}
+					timelineDipMixItems[i] = null;
+				}
+				outer: {}
+			}
+			return lastEntry;
+		}
+
+		bool HasTimeline (int id) {
+			var timelines = animation.timelines.Items;
+			for (int i = 0, n = animation.timelines.Count; i < n; i++)
+				if (timelines[i].PropertyId == id) return true;
+			return false;
+		}
+
 		/// <summary>The index of the track where this entry is either current or queued.</summary>
 		public int TrackIndex { get { return trackIndex; } }
 
@@ -870,17 +837,18 @@ namespace Spine {
 
 		/// <summary>
 		/// Seconds from 0 to the mix duration when mixing from the previous animation to this animation. May be slightly more than 
-		/// <see cref="TrackEntry.MixDuration"/>.</summary>
+		/// <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 must be set before <see cref="AnimationState.Update(float)"/> is next called.
+		/// 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> <seealso cref="Delay"/> is set using the mix duration from the <see cref=" AnimationStateData"/>
+		/// <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"/>
 		/// </para>
 		/// 
 		/// </summary>
@@ -888,7 +856,7 @@ namespace Spine {
 
 		/// <summary>
 		/// The track entry for the previous animation when mixing from the previous animation to this animation, or null if no 
-		/// mixing is currently occuring.</summary>
+		/// mixing is currently occuring. When mixing from multiple animations, MixingFrom makes up a linked list.</summary>
 		public TrackEntry MixingFrom { get { return mixingFrom; } }
 
 		public event AnimationState.TrackEntryDelegate Start, Interrupt, End, Dispose, Complete;

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

@@ -49,7 +49,7 @@ namespace Spine {
 			this.name = name;
 		}
 
-		/// <summary>Adds an attachment to the skin for the specified slot index and name.</summary>
+		/// <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;

二進制
spine-starling/spine-starling-example/lib/spine-as3.swc


二進制
spine-starling/spine-starling/lib/spine-as3.swc


+ 3 - 2
spine-ts/build/spine-all.js

@@ -1215,7 +1215,7 @@ var spine;
 						first = true;
 						alpha = alphaDip;
 						var dipMix = timelineDipMix[i];
-						if (dipMix != null && dipMix.mixDuration > 0)
+						if (dipMix != null)
 							alpha *= Math.max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
 						break;
 				}
@@ -1569,7 +1569,8 @@ var spine;
 					for (var ii = mixingToLast; ii >= 0; ii--) {
 						var entry = mixingTo[ii];
 						if (!entry.hasTimeline(id)) {
-							timelineDipMix[i] = entry;
+							if (entry.mixDuration > 0)
+								timelineDipMix[i] = entry;
 							continue outer;
 						}
 					}

文件差異過大導致無法顯示
+ 0 - 0
spine-ts/build/spine-all.js.map


+ 3 - 2
spine-ts/build/spine-canvas.js

@@ -1215,7 +1215,7 @@ var spine;
 						first = true;
 						alpha = alphaDip;
 						var dipMix = timelineDipMix[i];
-						if (dipMix != null && dipMix.mixDuration > 0)
+						if (dipMix != null)
 							alpha *= Math.max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
 						break;
 				}
@@ -1569,7 +1569,8 @@ var spine;
 					for (var ii = mixingToLast; ii >= 0; ii--) {
 						var entry = mixingTo[ii];
 						if (!entry.hasTimeline(id)) {
-							timelineDipMix[i] = entry;
+							if (entry.mixDuration > 0)
+								timelineDipMix[i] = entry;
 							continue outer;
 						}
 					}

文件差異過大導致無法顯示
+ 0 - 0
spine-ts/build/spine-canvas.js.map


+ 3 - 2
spine-ts/build/spine-core.js

@@ -1215,7 +1215,7 @@ var spine;
 						first = true;
 						alpha = alphaDip;
 						var dipMix = timelineDipMix[i];
-						if (dipMix != null && dipMix.mixDuration > 0)
+						if (dipMix != null)
 							alpha *= Math.max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
 						break;
 				}
@@ -1569,7 +1569,8 @@ var spine;
 					for (var ii = mixingToLast; ii >= 0; ii--) {
 						var entry = mixingTo[ii];
 						if (!entry.hasTimeline(id)) {
-							timelineDipMix[i] = entry;
+							if (entry.mixDuration > 0)
+								timelineDipMix[i] = entry;
 							continue outer;
 						}
 					}

文件差異過大導致無法顯示
+ 0 - 0
spine-ts/build/spine-core.js.map


+ 3 - 2
spine-ts/build/spine-threejs.js

@@ -1215,7 +1215,7 @@ var spine;
 						first = true;
 						alpha = alphaDip;
 						var dipMix = timelineDipMix[i];
-						if (dipMix != null && dipMix.mixDuration > 0)
+						if (dipMix != null)
 							alpha *= Math.max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
 						break;
 				}
@@ -1569,7 +1569,8 @@ var spine;
 					for (var ii = mixingToLast; ii >= 0; ii--) {
 						var entry = mixingTo[ii];
 						if (!entry.hasTimeline(id)) {
-							timelineDipMix[i] = entry;
+							if (entry.mixDuration > 0)
+								timelineDipMix[i] = entry;
 							continue outer;
 						}
 					}

文件差異過大導致無法顯示
+ 0 - 0
spine-ts/build/spine-threejs.js.map


+ 3 - 2
spine-ts/build/spine-webgl.js

@@ -1215,7 +1215,7 @@ var spine;
 						first = true;
 						alpha = alphaDip;
 						var dipMix = timelineDipMix[i];
-						if (dipMix != null && dipMix.mixDuration > 0)
+						if (dipMix != null)
 							alpha *= Math.max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
 						break;
 				}
@@ -1569,7 +1569,8 @@ var spine;
 					for (var ii = mixingToLast; ii >= 0; ii--) {
 						var entry = mixingTo[ii];
 						if (!entry.hasTimeline(id)) {
-							timelineDipMix[i] = entry;
+							if (entry.mixDuration > 0)
+								timelineDipMix[i] = entry;
 							continue outer;
 						}
 					}

文件差異過大導致無法顯示
+ 0 - 0
spine-ts/build/spine-webgl.js.map


+ 3 - 2
spine-ts/build/spine-widget.js

@@ -1215,7 +1215,7 @@ var spine;
 						first = true;
 						alpha = alphaDip;
 						var dipMix = timelineDipMix[i];
-						if (dipMix != null && dipMix.mixDuration > 0)
+						if (dipMix != null)
 							alpha *= Math.max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
 						break;
 				}
@@ -1569,7 +1569,8 @@ var spine;
 					for (var ii = mixingToLast; ii >= 0; ii--) {
 						var entry = mixingTo[ii];
 						if (!entry.hasTimeline(id)) {
-							timelineDipMix[i] = entry;
+							if (entry.mixDuration > 0)
+								timelineDipMix[i] = entry;
 							continue outer;
 						}
 					}

文件差異過大導致無法顯示
+ 0 - 0
spine-ts/build/spine-widget.js.map


+ 2 - 2
spine-ts/core/src/AnimationState.ts

@@ -220,7 +220,7 @@ module spine {
 					first = true;
 					alpha = alphaDip;
 					let dipMix = timelineDipMix[i];
-					if (dipMix != null && dipMix.mixDuration > 0) alpha *= Math.max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
+					if (dipMix != null) alpha *= Math.max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
 					break;
 				}
 				if (timeline instanceof RotateTimeline)
@@ -613,7 +613,7 @@ module spine {
 					for (var ii = mixingToLast; ii >= 0; ii--) {
 						let entry = mixingTo[ii];
 						if (!entry.hasTimeline(id)) {
-							timelineDipMix[i] = entry;
+							if (entry.mixDuration > 0) timelineDipMix[i] = entry;
 							continue outer;
 						}
 					}

部分文件因文件數量過多而無法顯示