Browse Source

Merge branch '3.7-beta' into 3.7-beta-cpp

badlogic 7 years ago
parent
commit
939cecdd0f
56 changed files with 1324 additions and 681 deletions
  1. BIN
      spine-as3/spine-as3-example/lib/spine-as3.swc
  2. 78 18
      spine-as3/spine-as3/src/spine/animation/AnimationState.as
  3. 8 57
      spine-as3/spine-as3/src/spine/animation/TrackEntry.as
  4. 12 20
      spine-csharp/src/AnimationState.cs
  5. 9 1
      spine-csharp/src/Atlas.cs
  6. 1 1
      spine-csharp/src/Attachments/Attachment.cs
  7. 20 6
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java
  8. BIN
      spine-starling/spine-starling-example/lib/spine-as3.swc
  9. 1 0
      spine-starling/spine-starling-example/src/spine/examples/SpineboyExample.as
  10. BIN
      spine-starling/spine-starling/lib/spine-as3.swc
  11. 8 7
      spine-ts/build/spine-all.d.ts
  12. 91 78
      spine-ts/build/spine-all.js
  13. 0 0
      spine-ts/build/spine-all.js.map
  14. 8 7
      spine-ts/build/spine-canvas.d.ts
  15. 78 65
      spine-ts/build/spine-canvas.js
  16. 0 0
      spine-ts/build/spine-canvas.js.map
  17. 8 7
      spine-ts/build/spine-core.d.ts
  18. 78 65
      spine-ts/build/spine-core.js
  19. 0 0
      spine-ts/build/spine-core.js.map
  20. 8 7
      spine-ts/build/spine-threejs.d.ts
  21. 78 65
      spine-ts/build/spine-threejs.js
  22. 0 0
      spine-ts/build/spine-threejs.js.map
  23. 8 7
      spine-ts/build/spine-webgl.d.ts
  24. 91 78
      spine-ts/build/spine-webgl.js
  25. 0 0
      spine-ts/build/spine-webgl.js.map
  26. 8 7
      spine-ts/build/spine-widget.d.ts
  27. 91 78
      spine-ts/build/spine-widget.js
  28. 0 0
      spine-ts/build/spine-widget.js.map
  29. 80 68
      spine-ts/core/src/AnimationState.ts
  30. 3 0
      spine-ts/webgl/tests/test-atlas-loading.html
  31. 113 0
      spine-ts/webgl/tests/test-rig.html
  32. 5 0
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonDataAssetInspector.cs
  33. 54 10
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineEditorUtilities.cs
  34. 120 0
      spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/BlendModeMaterialsAsset.cs
  35. 18 0
      spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/BlendModeMaterialsAsset.cs.meta
  36. 15 0
      spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SkeletonDataAsset.cs
  37. 40 0
      spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SkeletonDataModifierAsset.cs
  38. 12 0
      spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SkeletonDataModifierAsset.cs.meta
  39. 8 8
      spine-unity/Assets/Spine/Runtime/spine-unity/Modules/AttachmentTools/AttachmentTools.cs
  40. 9 0
      spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/BlendModes.meta
  41. BIN
      spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/BlendModes/Default BlendModeMaterials.asset
  42. 9 0
      spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/BlendModes/Default BlendModeMaterials.asset.meta
  43. BIN
      spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/BlendModes/SkeletonPMAAdditive.mat
  44. 9 0
      spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/BlendModes/SkeletonPMAAdditive.mat.meta
  45. 0 0
      spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/BlendModes/SkeletonPMAMultiply.mat
  46. 0 0
      spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/BlendModes/SkeletonPMAMultiply.mat.meta
  47. 0 0
      spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/BlendModes/SkeletonPMAScreen.mat
  48. 0 0
      spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/BlendModes/SkeletonPMAScreen.mat.meta
  49. 106 0
      spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/BlendModes/Spine-Skeleton-PMA-Additive.shader
  50. 9 0
      spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/BlendModes/Spine-Skeleton-PMA-Additive.shader.meta
  51. 2 4
      spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/BlendModes/Spine-Skeleton-PMA-Multiply.shader
  52. 0 0
      spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/BlendModes/Spine-Skeleton-PMA-Multiply.shader.meta
  53. 2 2
      spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/BlendModes/Spine-Skeleton-PMA-Screen.shader
  54. 0 0
      spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/BlendModes/Spine-Skeleton-PMA-Screen.shader.meta
  55. 24 7
      spine-unity/Assets/Spine/Runtime/spine-unity/SkeletonExtensions.cs
  56. 2 8
      spine-unity/Assets/Spine/Runtime/spine-unity/SkeletonUtility/SkeletonUtility.cs

BIN
spine-as3/spine-as3-example/lib/spine-as3.swc


+ 78 - 18
spine-as3/spine-as3/src/spine/animation/AnimationState.as

@@ -38,8 +38,8 @@ package spine.animation {
 	public class AnimationState {
 		public static var SUBSEQUENT : int = 0;
 		public static var FIRST : int = 1;
-		public static var DIP : int = 2;
-		public static var DIP_MIX : int = 3;
+		public static var HOLD : int = 2;
+		public static var HOLD_MIX : int = 3;
 		internal static var emptyAnimation : Animation = new Animation("<empty>", new Vector.<Timeline>(), 0);
 		public var data : AnimationStateData;
 		public var tracks : Vector.<TrackEntry> = new Vector.<TrackEntry>();
@@ -112,6 +112,7 @@ package spine.animation {
 					// End mixing from entries once all have completed.
 					var from : TrackEntry = current.mixingFrom;
 					current.mixingFrom = null;
+					if (from != null) from.mixingTo = null;
 					while (from != null) {
 						queue.end(from);
 						from = from.mixingFrom;
@@ -138,6 +139,7 @@ package spine.animation {
 				// Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame).
 				if (from.totalAlpha == 0 || to.mixDuration == 0) {
 					to.mixingFrom = from.mixingFrom;
+					if (from.mixingFrom != null) from.mixingFrom.mixingTo = to;
 					to.interruptAlpha = from.interruptAlpha;
 					queue.end(from);					
 				}
@@ -174,11 +176,11 @@ package spine.animation {
 				var timelineCount : int = current.animation.timelines.length;
 				var timelines : Vector.<Timeline> = current.animation.timelines;
 				var ii : int = 0;
-				if (mix == 1 || blend == MixBlend.add) {
+				if (i == 0 && (mix == 1 || blend == MixBlend.add)) {
 					for (ii = 0; ii < timelineCount; ii++)
 						Timeline(timelines[ii]).apply(skeleton, animationLast, animationTime, events, mix, blend, MixDirection.In);
 				} else {
-					var timelineData : Vector.<int> = current.timelineData;
+					var timelineMode : Vector.<int> = current.timelineMode;
 					
 					var firstFrame : Boolean = current.timelinesRotation.length == 0;
 					if (firstFrame) current.timelinesRotation.length = timelineCount << 1;
@@ -186,7 +188,7 @@ package spine.animation {
 					
 					for (ii = 0; ii < timelineCount; ii++) {
 						var timeline : Timeline = timelines[ii];
-						var timelineBlend : MixBlend = timelineData[ii] == AnimationState.SUBSEQUENT ? blend : MixBlend.setup;
+						var timelineBlend : MixBlend = timelineMode[ii] == AnimationState.SUBSEQUENT ? blend : MixBlend.setup;
 						if (timeline is RotateTimeline) {
 							applyRotateTimeline(timeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, ii << 1, firstFrame);
 						} else
@@ -222,15 +224,15 @@ 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 alphaDip : Number = from.alpha * to.interruptAlpha;
-			var alphaMix : Number = alphaDip * (1 - mix);
+			var alphaHold : Number = from.alpha * to.interruptAlpha;
+			var alphaMix : Number = alphaHold * (1 - mix);
 			var i : int = 0;
 			if (blend == MixBlend.add) {
 				for (i = 0; i < timelineCount; i++)
 					timelines[i].apply(skeleton, animationLast, animationTime, events, alphaMix, blend, MixDirection.Out);
 			} else {
-				var timelineData : Vector.<int> = from.timelineData;
-				var timelineDipMix : Vector.<TrackEntry> = from.timelineDipMix;
+				var timelineMode : Vector.<int> = from.timelineMode;
+				var timelineHoldMix : Vector.<TrackEntry> = from.timelineHoldMix;
 	
 				var firstFrame : Boolean = from.timelinesRotation.length == 0;
 				if (firstFrame) from.timelinesRotation.length = timelineCount << 1;
@@ -241,7 +243,7 @@ package spine.animation {
 					var timeline : Timeline = timelines[i];
 					var timelineBlend: MixBlend;
 					var alpha : Number = 0;
-					switch (timelineData[i]) {
+					switch (timelineMode[i]) {
 					case SUBSEQUENT:
 						if (!attachments && timeline is AttachmentTimeline) continue;
 						if (!drawOrder && timeline is DrawOrderTimeline) continue;
@@ -252,14 +254,14 @@ package spine.animation {
 						timelineBlend = MixBlend.setup;
 						alpha = alphaMix;
 						break;
-					case DIP:
+					case HOLD:
 						timelineBlend = MixBlend.setup;
-						alpha = alphaDip;
+						alpha = alphaHold;
 						break;
 					default:
 						timelineBlend = MixBlend.setup;						
-						var dipMix : TrackEntry = timelineDipMix[i];
-						alpha = alphaDip * Math.max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
+						var holdMix : TrackEntry = timelineHoldMix[i];
+						alpha = alphaHold * Math.max(0, 1 - holdMix.mixTime / holdMix.mixDuration);
 						break;
 					}
 					from.totalAlpha += alpha;
@@ -399,6 +401,7 @@ package spine.animation {
 				if (from == null) break;
 				queue.end(from);
 				entry.mixingFrom = null;
+				entry.mixingTo = null;
 				entry = from;
 			}
 
@@ -414,6 +417,7 @@ package spine.animation {
 			if (from != null) {
 				if (interrupt) queue.interrupt(from);
 				current.mixingFrom = from;
+				from.mixingTo = current;
 				current.mixTime = 0;
 	
 				// Store the interrupted mix percentage.
@@ -529,6 +533,7 @@ package spine.animation {
 			entry.trackIndex = trackIndex;
 			entry.animation = animation;
 			entry.loop = loop;
+			entry.holdPrevious = false;
 
 			entry.eventThreshold = 0;
 			entry.attachmentThreshold = 0;
@@ -565,13 +570,68 @@ package spine.animation {
 		private function _animationsChanged() : void {
 			animationsChanged = false;
 
-			var propertyIDs : Dictionary = this.propertyIDs = new Dictionary();					
-			var mixingTo : Vector.<TrackEntry> = this.mixingTo;			
+			propertyIDs = new Dictionary();					
+				
 			for (var i : int = 0, n : int = tracks.length; i < n; i++) {
 				var entry : TrackEntry = tracks[i];
-				if (entry != null && (i == 0 || entry.mixBlend != MixBlend.add)) 
-					entry.setTimelineData(null, mixingTo, propertyIDs);				
+				if (entry == null) continue;
+				while (entry.mixingFrom != null)
+					entry = entry.mixingFrom;
+				do {
+					if (entry.mixingTo == null || entry.mixBlend != MixBlend.add) setTimelineModes(entry);
+					entry = entry.mixingTo;			
+				} while (entry != null);
+			}
+		}
+		
+		private function setTimelineModes (entry: TrackEntry) {
+			var to: TrackEntry = entry.mixingTo;			
+			var timelines : Vector.<Timeline> = entry.animation.timelines;
+			var timelinesCount : int = entry.animation.timelines.length;
+			var timelineMode : Vector.<int> = entry.timelineMode;
+			timelineMode.length = timelinesCount;			
+			var timelineHoldMix : Vector.<TrackEntry> = entry.timelineHoldMix;
+			timelineHoldMix.length = 0;
+			var propertyIDs: Dictionary = this.propertyIDs;
+			
+			if (to != null && to.holdPrevious) {
+				for (var i : int = 0; i < timelinesCount; i++) {										
+					propertyIDs[timelines[i].getPropertyId().toString()] = true;			
+					timelineMode[i] = HOLD;
+				}
+				return;
 			}
+
+			outer:
+			for (var i : int = 0; i < timelinesCount; i++) {
+				var intId : int = timelines[i].getPropertyId();
+				var id : String = intId.toString();			
+				var contained: Object = propertyIDs[id];
+				propertyIDs[id] = true;
+				if (contained != null) {
+					timelineMode[i] = AnimationState.SUBSEQUENT;
+				} else if (to == null || !hasTimeline(to, intId)) {				
+					timelineMode[i] = AnimationState.FIRST;
+				} else {					
+					for (var next : TrackEntry = to.mixingTo; next != null; next = next.mixingTo) {
+						if (hasTimeline(next, intId)) continue;
+						if (entry.mixDuration > 0) {
+							timelineMode[i] = AnimationState.HOLD_MIX;							
+							timelineHoldMix[i] = entry;
+							continue outer;
+						}
+						break;
+					}
+					timelineMode[i] = AnimationState.HOLD;
+				}
+			}			
+		}
+
+		private static function hasTimeline (entry: TrackEntry, id : int) : Boolean {
+			var timelines : Vector.<Timeline> = entry.animation.timelines;
+			for (var i : int = 0, n : int = entry.animation.timelines.length; i < n; i++)
+				if (timelines[i].getPropertyId() == id) return true;
+			return false;
 		}
 
 		public function getCurrent(trackIndex : int) : TrackEntry {

+ 8 - 57
spine-as3/spine-as3/src/spine/animation/TrackEntry.as

@@ -34,7 +34,7 @@ package spine.animation {
 
 	public class TrackEntry implements Poolable {
 		public var animation : Animation;
-		public var next : TrackEntry, mixingFrom : TrackEntry;
+		public var next : TrackEntry, mixingFrom : TrackEntry, mixingTo: TrackEntry;
 		public var onStart : Listeners = new Listeners();
 		public var onInterrupt : Listeners = new Listeners();
 		public var onEnd : Listeners = new Listeners();
@@ -42,14 +42,14 @@ package spine.animation {
 		public var onComplete : Listeners = new Listeners();
 		public var onEvent : Listeners = new Listeners();
 		public var trackIndex : int;
-		public var loop : Boolean;
+		public var loop : Boolean, holdPrevious: Boolean;
 		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, interruptAlpha : Number, totalAlpha : Number = 0;
 		public var mixBlend: MixBlend = MixBlend.replace;
-		public var timelineData : Vector.<int> = new Vector.<int>();
-		public var timelineDipMix : Vector.<TrackEntry> = new Vector.<TrackEntry>();
+		public var timelineMode : Vector.<int> = new Vector.<int>();
+		public var timelineHoldMix : Vector.<TrackEntry> = new Vector.<TrackEntry>();
 		public var timelinesRotation : Vector.<Number> = new Vector.<Number>();
 
 		public function TrackEntry() {
@@ -67,6 +67,7 @@ package spine.animation {
 		public function reset() : void {
 			next = null;
 			mixingFrom = null;
+			mixingTo = null;
 			animation = null;
 			onStart.listeners.length = 0;
 			onInterrupt.listeners.length = 0;
@@ -74,60 +75,10 @@ package spine.animation {
 			onDispose.listeners.length = 0;
 			onComplete.listeners.length = 0;
 			onEvent.listeners.length = 0;
-			timelineData.length = 0;
-			timelineDipMix.length = 0;
+			timelineMode.length = 0;
+			timelineHoldMix.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;
-			this.timelineDipMix.length = 0;
-			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();
-				var contained: Object = propertyIDs[id];
-				propertyIDs[id] = true;
-				if (contained != null) {
-					timelineData[i] = AnimationState.SUBSEQUENT;
-				} else if (to == null || !to.hasTimeline(intId)) {				
-					timelineData[i] = AnimationState.FIRST;
-				} else {					
-					for (var ii : int = mixingToLast; ii >= 0; ii--) {
-						var entry : TrackEntry = mixingTo[ii];
-						if (!entry.hasTimeline(intId)) {
-							if (entry.mixDuration > 0) {
-								timelineData[i] = AnimationState.DIP_MIX;							
-								timelineDipMix[i] = entry;
-								continue outer;
-							}
-							break;
-						}						
-					}
-					timelineData[i] = AnimationState.DIP;
-				}
-			}
-			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;

+ 12 - 20
spine-csharp/src/AnimationState.cs

@@ -36,7 +36,7 @@ namespace Spine {
 		static readonly Animation EmptyAnimation = new Animation("<empty>", new ExposedList<Timeline>(), 0);
 		internal const int Subsequent = 0, First = 1, Hold = 2, HoldMix = 3;
 
-		private AnimationStateData data;
+		protected AnimationStateData data;
 
 		private readonly Pool<TrackEntry> trackEntryPool = new Pool<TrackEntry>();
 		private readonly ExposedList<TrackEntry> tracks = new ExposedList<TrackEntry>();
@@ -49,6 +49,7 @@ namespace Spine {
 		private float timeScale = 1;
 
 		public AnimationStateData Data { get { return data; } }
+
 		/// <summary>A list of tracks that have animations, which may contain nulls.</summary>
 		public ExposedList<TrackEntry> Tracks { get { return tracks; } }
 		public float TimeScale { get { return timeScale; } set { timeScale = value; } }
@@ -70,7 +71,7 @@ namespace Spine {
 		}
 
 		/// <summary>
-		/// Increments the track entry times, setting queued animations as current if needed</summary>
+		/// Increments the track entry trackTimes, setting queued animations as current if needed</summary>
 		/// <param name="delta">delta time</param>
 		public void Update (float delta) {
 			delta *= timeScale;
@@ -109,7 +110,6 @@ namespace Spine {
 				} else if (current.trackLast >= current.trackEnd && current.mixingFrom == null) {
 					// Clear the track when there is no next entry, the track end time is reached, and there is no mixingFrom.
 					tracksItems[i] = null;
-
 					queue.End(current);
 					DisposeNext(current);
 					continue;
@@ -166,7 +166,6 @@ namespace Spine {
 			if (animationsChanged) AnimationsChanged();
 
 			var events = this.events;
-
 			bool applied = false;
 			var tracksItems = tracks.Items;
 			for (int i = 0, m = tracks.Count; i < m; i++) {
@@ -174,6 +173,7 @@ namespace Spine {
 				if (current == null || current.delay > 0) continue;
 				applied = true;
 
+				// Track 0 animations aren't for layering, so do not show the previously applied animations before the first key.
 				MixBlend blend = i == 0 ? MixBlend.First : current.mixBlend;
 
 				// Apply mixing from entries first.
@@ -242,7 +242,7 @@ namespace Spine {
 
 			if (blend == MixBlend.Add) {
 				for (int i = 0; i < timelineCount; i++)
-					(timelinesItems[i]).Apply(skeleton, animationLast, animationTime, eventBuffer, alphaMix, blend, MixDirection.Out);
+					timelinesItems[i].Apply(skeleton, animationLast, animationTime, eventBuffer, alphaMix, blend, MixDirection.Out);
 			} else {
 				var timelineMode = from.timelineMode.Items;
 				var timelineHoldMix = from.timelineHoldMix.Items;
@@ -258,10 +258,8 @@ namespace Spine {
 					float alpha;
 					switch (timelineMode[i]) {
 						case AnimationState.Subsequent:
-							if (!attachments && timeline is AttachmentTimeline)
-								continue;
-							if (!drawOrder && timeline is DrawOrderTimeline)
-								continue;
+							if (!attachments && timeline is AttachmentTimeline) continue;
+							if (!drawOrder && timeline is DrawOrderTimeline) continue;
 							timelineBlend = blend;
 							alpha = alphaMix;
 							break;
@@ -446,7 +444,7 @@ namespace Spine {
 			if (from != null) {
 				if (interrupt) queue.Interrupt(from);
 				current.mixingFrom = from;
-				current.mixingTo = current;
+				from.mixingTo = current;
 				current.mixTime = 0;
 
 				// Store interrupted mix percentage.
@@ -640,7 +638,7 @@ namespace Spine {
 		private void AnimationsChanged () {
 			animationsChanged = false;
 
-			this.propertyIDs.Clear();
+			propertyIDs.Clear();
 
 			var tracksItems = tracks.Items;
 			for (int i = 0, n = tracks.Count; i < n; i++) {
@@ -659,7 +657,7 @@ namespace Spine {
 		}
 
 		private void SetTimelineModes (TrackEntry entry) {
-			var to = entry.mixingTo;
+			TrackEntry to = entry.mixingTo;
 			var timelines = entry.animation.timelines.Items;
 			int timelinesCount = entry.animation.timelines.Count;
 			var timelineMode = entry.timelineMode.Resize(timelinesCount).Items; //timelineMode.setSize(timelinesCount);
@@ -694,7 +692,7 @@ namespace Spine {
 					}
 					timelineMode[i] = AnimationState.Hold;
 				}
-				continue_outer:	{}
+				continue_outer: {}
 			}
 		}
 
@@ -750,6 +748,7 @@ namespace Spine {
 		public void Reset () {
 			next = null;
 			mixingFrom = null;
+			mixingTo = null;
 			animation = null;
 			timelineMode.Clear();
 			timelineHoldMix.Clear();
@@ -763,13 +762,6 @@ namespace Spine {
 			Event = null;
 		}
 
-		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; } }
 

+ 9 - 1
spine-csharp/src/Atlas.cs

@@ -283,8 +283,12 @@ namespace Spine {
 		public TextureFilter magFilter;
 		public TextureWrap uWrap;
 		public TextureWrap vWrap;
-		public Object rendererObject;
+		public object rendererObject;
 		public int width, height;
+
+		public AtlasPage Clone () {
+			return MemberwiseClone() as AtlasPage;
+		}
 	}
 
 	public class AtlasRegion {
@@ -298,6 +302,10 @@ namespace Spine {
 		public bool rotate;
 		public int[] splits;
 		public int[] pads;
+
+		public AtlasRegion Clone () {
+			return MemberwiseClone() as AtlasRegion;
+		}
 	}
 
 	public interface TextureLoader {

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

@@ -45,6 +45,6 @@ namespace Spine {
 	}
 
 	public interface IHasRendererObject {
-		object RendererObject { get; }
+		object RendererObject { get; set; }
 	}
 }

+ 20 - 6
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java

@@ -202,6 +202,20 @@ public class Animation {
 		twoColor
 	}
 
+	static public interface BoneTimeline extends Timeline {
+		public void setBoneIndex (int index);
+
+		/** The index of the slot in {@link Skeleton#getSlots()} that will be changed. */
+		public int getBoneIndex ();
+	}
+
+	static public interface SlotTimeline extends Timeline {
+		public void setSlotIndex (int index);
+
+		/** The index of the slot in {@link Skeleton#getSlots()} that will be changed. */
+		public int getSlotIndex ();
+	}
+
 	/** The base class for timelines that use interpolation between key frame values. */
 	abstract static public class CurveTimeline implements Timeline {
 		static public final float LINEAR = 0, STEPPED = 1, BEZIER = 2;
@@ -290,7 +304,7 @@ public class Animation {
 	}
 
 	/** Changes a bone's local {@link Bone#getRotation()}. */
-	static public class RotateTimeline extends CurveTimeline {
+	static public class RotateTimeline extends CurveTimeline implements BoneTimeline {
 		static public final int ENTRIES = 2;
 		static final int PREV_TIME = -2, PREV_ROTATION = -1;
 		static final int ROTATION = 1;
@@ -386,7 +400,7 @@ public class Animation {
 	}
 
 	/** Changes a bone's local {@link Bone#getX()} and {@link Bone#getY()}. */
-	static public class TranslateTimeline extends CurveTimeline {
+	static public class TranslateTimeline extends CurveTimeline implements BoneTimeline {
 		static public final int ENTRIES = 3;
 		static final int PREV_TIME = -3, PREV_X = -2, PREV_Y = -1;
 		static final int X = 1, Y = 2;
@@ -641,7 +655,7 @@ public class Animation {
 	}
 
 	/** Changes a slot's {@link Slot#getColor()}. */
-	static public class ColorTimeline extends CurveTimeline {
+	static public class ColorTimeline extends CurveTimeline implements SlotTimeline {
 		static public final int ENTRIES = 5;
 		static private final int PREV_TIME = -5, PREV_R = -4, PREV_G = -3, PREV_B = -2, PREV_A = -1;
 		static private final int R = 1, G = 2, B = 3, A = 4;
@@ -735,7 +749,7 @@ public class Animation {
 	}
 
 	/** Changes a slot's {@link Slot#getColor()} and {@link Slot#getDarkColor()} for two color tinting. */
-	static public class TwoColorTimeline extends CurveTimeline {
+	static public class TwoColorTimeline extends CurveTimeline implements SlotTimeline {
 		static public final int ENTRIES = 8;
 		static private final int PREV_TIME = -8, PREV_R = -7, PREV_G = -6, PREV_B = -5, PREV_A = -4;
 		static private final int PREV_R2 = -3, PREV_G2 = -2, PREV_B2 = -1;
@@ -849,7 +863,7 @@ public class Animation {
 	}
 
 	/** Changes a slot's {@link Slot#getAttachment()}. */
-	static public class AttachmentTimeline implements Timeline {
+	static public class AttachmentTimeline implements SlotTimeline {
 		int slotIndex;
 		final float[] frames; // time, ...
 		final String[] attachmentNames;
@@ -925,7 +939,7 @@ public class Animation {
 	}
 
 	/** Changes a slot's {@link Slot#getAttachmentVertices()} to deform a {@link VertexAttachment}. */
-	static public class DeformTimeline extends CurveTimeline {
+	static public class DeformTimeline extends CurveTimeline implements SlotTimeline {
 		int slotIndex;
 		VertexAttachment attachment;
 		private final float[] frames; // time, ...

BIN
spine-starling/spine-starling-example/lib/spine-as3.swc


+ 1 - 0
spine-starling/spine-starling-example/src/spine/examples/SpineboyExample.as

@@ -69,6 +69,7 @@ package spine.examples {
 			var skeletonData : SkeletonData = json.readSkeletonData(new SpineboyJson());
 
 			var stateData : AnimationStateData = new AnimationStateData(skeletonData);
+			stateData.setMixByName("walk", "run", 0.4);
 			stateData.setMixByName("run", "jump", 0.4);
 			stateData.setMixByName("jump", "run", 0.4);
 			stateData.setMixByName("jump", "jump", 0.4);

BIN
spine-starling/spine-starling/lib/spine-as3.swc


+ 8 - 7
spine-ts/build/spine-all.d.ts

@@ -244,15 +244,14 @@ declare module spine {
 		static emptyAnimation: Animation;
 		static SUBSEQUENT: number;
 		static FIRST: number;
-		static DIP: number;
-		static DIP_MIX: number;
+		static HOLD: number;
+		static HOLD_MIX: number;
 		data: AnimationStateData;
 		tracks: TrackEntry[];
 		events: Event[];
 		listeners: AnimationStateListener2[];
 		queue: EventQueue;
 		propertyIDs: IntSet;
-		mixingTo: TrackEntry[];
 		animationsChanged: boolean;
 		timeScale: number;
 		trackEntryPool: Pool<TrackEntry>;
@@ -277,6 +276,8 @@ declare module spine {
 		trackEntry(trackIndex: number, animation: Animation, loop: boolean, last: TrackEntry): TrackEntry;
 		disposeNext(entry: TrackEntry): void;
 		_animationsChanged(): void;
+		setTimelineModes(entry: TrackEntry): void;
+		hasTimeline(entry: TrackEntry, id: number): boolean;
 		getCurrent(trackIndex: number): TrackEntry;
 		addListener(listener: AnimationStateListener2): void;
 		removeListener(listener: AnimationStateListener2): void;
@@ -287,9 +288,11 @@ declare module spine {
 		animation: Animation;
 		next: TrackEntry;
 		mixingFrom: TrackEntry;
+		mixingTo: TrackEntry;
 		listener: AnimationStateListener2;
 		trackIndex: number;
 		loop: boolean;
+		holdPrevious: boolean;
 		eventThreshold: number;
 		attachmentThreshold: number;
 		drawOrderThreshold: number;
@@ -309,12 +312,10 @@ declare module spine {
 		interruptAlpha: number;
 		totalAlpha: number;
 		mixBlend: MixBlend;
-		timelineData: number[];
-		timelineDipMix: TrackEntry[];
+		timelineMode: number[];
+		timelineHoldMix: TrackEntry[];
 		timelinesRotation: number[];
 		reset(): void;
-		setTimelineData(to: TrackEntry, mixingToArray: Array<TrackEntry>, propertyIDs: IntSet): TrackEntry;
-		hasTimeline(id: number): boolean;
 		getAnimationTime(): number;
 		setAnimationLast(animationLast: number): void;
 		isComplete(): boolean;

+ 91 - 78
spine-ts/build/spine-all.js

@@ -1303,7 +1303,6 @@ var spine;
 			this.listeners = new Array();
 			this.queue = new EventQueue(this);
 			this.propertyIDs = new spine.IntSet();
-			this.mixingTo = new Array();
 			this.animationsChanged = false;
 			this.timeScale = 1;
 			this.trackEntryPool = new spine.Pool(function () { return new TrackEntry(); });
@@ -1350,6 +1349,8 @@ var spine;
 				if (current.mixingFrom != null && this.updateMixingFrom(current, delta)) {
 					var from = current.mixingFrom;
 					current.mixingFrom = null;
+					if (from != null)
+						from.mixingTo = null;
 					while (from != null) {
 						this.queue.end(from);
 						from = from.mixingFrom;
@@ -1369,6 +1370,8 @@ var spine;
 			if (to.mixTime > 0 && (to.mixTime >= to.mixDuration || to.timeScale == 0)) {
 				if (from.totalAlpha == 0 || to.mixDuration == 0) {
 					to.mixingFrom = from.mixingFrom;
+					if (from.mixingFrom != null)
+						from.mixingFrom.mixingTo = to;
 					to.interruptAlpha = from.interruptAlpha;
 					this.queue.end(from);
 				}
@@ -1400,19 +1403,19 @@ var spine;
 				var animationLast = current.animationLast, animationTime = current.getAnimationTime();
 				var timelineCount = current.animation.timelines.length;
 				var timelines = current.animation.timelines;
-				if (mix == 1 || blend == spine.MixBlend.add) {
+				if (i == 0 && (mix == 1 || blend == spine.MixBlend.add)) {
 					for (var ii = 0; ii < timelineCount; ii++)
 						timelines[ii].apply(skeleton, animationLast, animationTime, events, mix, blend, spine.MixDirection["in"]);
 				}
 				else {
-					var timelineData = current.timelineData;
+					var timelineMode = current.timelineMode;
 					var firstFrame = current.timelinesRotation.length == 0;
 					if (firstFrame)
 						spine.Utils.setArraySize(current.timelinesRotation, timelineCount << 1, null);
 					var timelinesRotation = current.timelinesRotation;
 					for (var ii = 0; ii < timelineCount; ii++) {
 						var timeline = timelines[ii];
-						var timelineBlend = timelineData[ii] == AnimationState.SUBSEQUENT ? blend : spine.MixBlend.setup;
+						var timelineBlend = timelineMode[ii] == AnimationState.SUBSEQUENT ? blend : spine.MixBlend.setup;
 						if (timeline instanceof spine.RotateTimeline) {
 							this.applyRotateTimeline(timeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, ii << 1, firstFrame);
 						}
@@ -1452,14 +1455,14 @@ var spine;
 			var animationLast = from.animationLast, animationTime = from.getAnimationTime();
 			var timelineCount = from.animation.timelines.length;
 			var timelines = from.animation.timelines;
-			var alphaDip = from.alpha * to.interruptAlpha, alphaMix = alphaDip * (1 - mix);
+			var alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix);
 			if (blend == spine.MixBlend.add) {
 				for (var i = 0; i < timelineCount; i++)
 					timelines[i].apply(skeleton, animationLast, animationTime, events, alphaMix, blend, spine.MixDirection.out);
 			}
 			else {
-				var timelineData = from.timelineData;
-				var timelineDipMix = from.timelineDipMix;
+				var timelineMode = from.timelineMode;
+				var timelineHoldMix = from.timelineHoldMix;
 				var firstFrame = from.timelinesRotation.length == 0;
 				if (firstFrame)
 					spine.Utils.setArraySize(from.timelinesRotation, timelineCount << 1, null);
@@ -1469,7 +1472,7 @@ var spine;
 					var timeline = timelines[i];
 					var timelineBlend;
 					var alpha = 0;
-					switch (timelineData[i]) {
+					switch (timelineMode[i]) {
 						case AnimationState.SUBSEQUENT:
 							if (!attachments && timeline instanceof spine.AttachmentTimeline)
 								continue;
@@ -1482,14 +1485,14 @@ var spine;
 							timelineBlend = spine.MixBlend.setup;
 							alpha = alphaMix;
 							break;
-						case AnimationState.DIP:
+						case AnimationState.HOLD:
 							timelineBlend = spine.MixBlend.setup;
-							alpha = alphaDip;
+							alpha = alphaHold;
 							break;
 						default:
 							timelineBlend = spine.MixBlend.setup;
-							var dipMix = timelineDipMix[i];
-							alpha = alphaDip * Math.max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
+							var holdMix = timelineHoldMix[i];
+							alpha = alphaHold * Math.max(0, 1 - holdMix.mixTime / holdMix.mixDuration);
 							break;
 					}
 					from.totalAlpha += alpha;
@@ -1619,6 +1622,7 @@ var spine;
 					break;
 				this.queue.end(from);
 				entry.mixingFrom = null;
+				entry.mixingTo = null;
 				entry = from;
 			}
 			this.tracks[current.trackIndex] = null;
@@ -1631,6 +1635,7 @@ var spine;
 				if (interrupt)
 					this.queue.interrupt(from);
 				current.mixingFrom = from;
+				from.mixingTo = current;
 				current.mixTime = 0;
 				if (from.mixingFrom != null && from.mixDuration > 0)
 					current.interruptAlpha *= Math.min(1, from.mixTime / from.mixDuration);
@@ -1740,6 +1745,7 @@ var spine;
 			entry.trackIndex = trackIndex;
 			entry.animation = animation;
 			entry.loop = loop;
+			entry.holdPrevious = false;
 			entry.eventThreshold = 0;
 			entry.attachmentThreshold = 0;
 			entry.drawOrderThreshold = 0;
@@ -1769,14 +1775,62 @@ var spine;
 		};
 		AnimationState.prototype._animationsChanged = function () {
 			this.animationsChanged = false;
-			var propertyIDs = this.propertyIDs;
-			propertyIDs.clear();
-			var mixingTo = this.mixingTo;
+			this.propertyIDs.clear();
 			for (var i = 0, n = this.tracks.length; i < n; i++) {
 				var entry = this.tracks[i];
-				if (entry != null && (i == 0 || entry.mixBlend != spine.MixBlend.add))
-					entry.setTimelineData(null, mixingTo, propertyIDs);
+				if (entry == null)
+					continue;
+				while (entry.mixingFrom != null)
+					entry = entry.mixingFrom;
+				do {
+					if (entry.mixingFrom == null || entry.mixBlend != spine.MixBlend.add)
+						this.setTimelineModes(entry);
+					entry = entry.mixingTo;
+				} while (entry != null);
+			}
+		};
+		AnimationState.prototype.setTimelineModes = function (entry) {
+			var to = entry.mixingTo;
+			var timelines = entry.animation.timelines;
+			var timelinesCount = entry.animation.timelines.length;
+			var timelineMode = spine.Utils.setArraySize(entry.timelineMode, timelinesCount);
+			entry.timelineHoldMix.length = 0;
+			var timelineDipMix = spine.Utils.setArraySize(entry.timelineHoldMix, timelinesCount);
+			var propertyIDs = this.propertyIDs;
+			if (to != null && to.holdPrevious) {
+				for (var i_16 = 0; i_16 < timelinesCount; i_16++) {
+					propertyIDs.add(timelines[i_16].getPropertyId());
+					timelineMode[i_16] = AnimationState.HOLD;
+				}
+				return;
 			}
+			outer: for (var i = 0; i < timelinesCount; i++) {
+				var id = timelines[i].getPropertyId();
+				if (!propertyIDs.add(id))
+					timelineMode[i] = AnimationState.SUBSEQUENT;
+				else if (to == null || !this.hasTimeline(to, id))
+					timelineMode[i] = AnimationState.FIRST;
+				else {
+					for (var next = to.mixingTo; next != null; next = next.mixingTo) {
+						if (this.hasTimeline(next, id))
+							continue;
+						if (entry.mixDuration > 0) {
+							timelineMode[i] = AnimationState.HOLD_MIX;
+							timelineDipMix[i] = next;
+							continue outer;
+						}
+						break;
+					}
+					timelineMode[i] = AnimationState.HOLD;
+				}
+			}
+		};
+		AnimationState.prototype.hasTimeline = function (entry, id) {
+			var timelines = entry.animation.timelines;
+			for (var i = 0, n = timelines.length; i < n; i++)
+				if (timelines[i].getPropertyId() == id)
+					return true;
+			return false;
 		};
 		AnimationState.prototype.getCurrent = function (trackIndex) {
 			if (trackIndex >= this.tracks.length)
@@ -1802,69 +1856,28 @@ var spine;
 		AnimationState.emptyAnimation = new spine.Animation("<empty>", [], 0);
 		AnimationState.SUBSEQUENT = 0;
 		AnimationState.FIRST = 1;
-		AnimationState.DIP = 2;
-		AnimationState.DIP_MIX = 3;
+		AnimationState.HOLD = 2;
+		AnimationState.HOLD_MIX = 3;
 		return AnimationState;
 	}());
 	spine.AnimationState = AnimationState;
 	var TrackEntry = (function () {
 		function TrackEntry() {
 			this.mixBlend = spine.MixBlend.replace;
-			this.timelineData = new Array();
-			this.timelineDipMix = new Array();
+			this.timelineMode = new Array();
+			this.timelineHoldMix = new Array();
 			this.timelinesRotation = new Array();
 		}
 		TrackEntry.prototype.reset = function () {
 			this.next = null;
 			this.mixingFrom = null;
+			this.mixingTo = null;
 			this.animation = null;
 			this.listener = null;
-			this.timelineData.length = 0;
-			this.timelineDipMix.length = 0;
+			this.timelineMode.length = 0;
+			this.timelineHoldMix.length = 0;
 			this.timelinesRotation.length = 0;
 		};
-		TrackEntry.prototype.setTimelineData = function (to, mixingToArray, propertyIDs) {
-			if (to != null)
-				mixingToArray.push(to);
-			var lastEntry = this.mixingFrom != null ? this.mixingFrom.setTimelineData(this, mixingToArray, propertyIDs) : this;
-			if (to != null)
-				mixingToArray.pop();
-			var mixingTo = mixingToArray;
-			var mixingToLast = mixingToArray.length - 1;
-			var timelines = this.animation.timelines;
-			var timelinesCount = this.animation.timelines.length;
-			var timelineData = spine.Utils.setArraySize(this.timelineData, timelinesCount);
-			this.timelineDipMix.length = 0;
-			var timelineDipMix = spine.Utils.setArraySize(this.timelineDipMix, timelinesCount);
-			outer: for (var i = 0; i < timelinesCount; i++) {
-				var id = timelines[i].getPropertyId();
-				if (!propertyIDs.add(id))
-					timelineData[i] = AnimationState.SUBSEQUENT;
-				else if (to == null || !to.hasTimeline(id))
-					timelineData[i] = AnimationState.FIRST;
-				else {
-					for (var ii = mixingToLast; ii >= 0; ii--) {
-						var entry = mixingTo[ii];
-						if (!entry.hasTimeline(id)) {
-							if (entry.mixDuration > 0) {
-								timelineData[i] = AnimationState.DIP_MIX;
-								timelineDipMix[i] = entry;
-								continue outer;
-							}
-						}
-					}
-					timelineData[i] = AnimationState.DIP;
-				}
-			}
-			return lastEntry;
-		};
-		TrackEntry.prototype.hasTimeline = function (id) {
-			var timelines = this.animation.timelines;
-			for (var i = 0, n = timelines.length; i < n; i++)
-				if (timelines[i].getPropertyId() == id)
-					return true;
-			return false;
-		};
 		TrackEntry.prototype.getAnimationTime = function () {
 			if (this.loop) {
 				var duration = this.animationEnd - this.animationStart;
@@ -7266,8 +7279,8 @@ var spine;
 						break;
 					}
 					var listeners = _this.listeners;
-					for (var i_16 = 0; i_16 < listeners.length; i_16++) {
-						listeners[i_16].down(_this.currTouch.x, _this.currTouch.y);
+					for (var i_17 = 0; i_17 < listeners.length; i_17++) {
+						listeners[i_17].down(_this.currTouch.x, _this.currTouch.y);
 					}
 					console.log("Start " + _this.currTouch.x + ", " + _this.currTouch.y);
 					_this.lastX = _this.currTouch.x;
@@ -7285,8 +7298,8 @@ var spine;
 							var y = _this.currTouch.y = touch.clientY - rect.top;
 							_this.touchesPool.free(_this.currTouch);
 							var listeners = _this.listeners;
-							for (var i_17 = 0; i_17 < listeners.length; i_17++) {
-								listeners[i_17].up(x, y);
+							for (var i_18 = 0; i_18 < listeners.length; i_18++) {
+								listeners[i_18].up(x, y);
 							}
 							console.log("End " + x + ", " + y);
 							_this.lastX = x;
@@ -7308,8 +7321,8 @@ var spine;
 							var y = _this.currTouch.y = touch.clientY - rect.top;
 							_this.touchesPool.free(_this.currTouch);
 							var listeners = _this.listeners;
-							for (var i_18 = 0; i_18 < listeners.length; i_18++) {
-								listeners[i_18].up(x, y);
+							for (var i_19 = 0; i_19 < listeners.length; i_19++) {
+								listeners[i_19].up(x, y);
 							}
 							console.log("End " + x + ", " + y);
 							_this.lastX = x;
@@ -7332,8 +7345,8 @@ var spine;
 							var x = touch.clientX - rect.left;
 							var y = touch.clientY - rect.top;
 							var listeners = _this.listeners;
-							for (var i_19 = 0; i_19 < listeners.length; i_19++) {
-								listeners[i_19].dragged(x, y);
+							for (var i_20 = 0; i_20 < listeners.length; i_20++) {
+								listeners[i_20].dragged(x, y);
 							}
 							console.log("Drag " + x + ", " + y);
 							_this.lastX = _this.currTouch.x = x;
@@ -9183,11 +9196,11 @@ var spine;
 						var nn = clip.worldVerticesLength;
 						var world = this.temp = spine.Utils.setArraySize(this.temp, nn, 0);
 						clip.computeWorldVertices(slot, 0, nn, world, 0, 2);
-						for (var i_20 = 0, n_2 = world.length; i_20 < n_2; i_20 += 2) {
-							var x = world[i_20];
-							var y = world[i_20 + 1];
-							var x2 = world[(i_20 + 2) % world.length];
-							var y2 = world[(i_20 + 3) % world.length];
+						for (var i_21 = 0, n_2 = world.length; i_21 < n_2; i_21 += 2) {
+							var x = world[i_21];
+							var y = world[i_21 + 1];
+							var x2 = world[(i_21 + 2) % world.length];
+							var y2 = world[(i_21 + 3) % world.length];
 							shapes.line(x, y, x2, y2);
 						}
 					}

File diff suppressed because it is too large
+ 0 - 0
spine-ts/build/spine-all.js.map


+ 8 - 7
spine-ts/build/spine-canvas.d.ts

@@ -244,15 +244,14 @@ declare module spine {
 		static emptyAnimation: Animation;
 		static SUBSEQUENT: number;
 		static FIRST: number;
-		static DIP: number;
-		static DIP_MIX: number;
+		static HOLD: number;
+		static HOLD_MIX: number;
 		data: AnimationStateData;
 		tracks: TrackEntry[];
 		events: Event[];
 		listeners: AnimationStateListener2[];
 		queue: EventQueue;
 		propertyIDs: IntSet;
-		mixingTo: TrackEntry[];
 		animationsChanged: boolean;
 		timeScale: number;
 		trackEntryPool: Pool<TrackEntry>;
@@ -277,6 +276,8 @@ declare module spine {
 		trackEntry(trackIndex: number, animation: Animation, loop: boolean, last: TrackEntry): TrackEntry;
 		disposeNext(entry: TrackEntry): void;
 		_animationsChanged(): void;
+		setTimelineModes(entry: TrackEntry): void;
+		hasTimeline(entry: TrackEntry, id: number): boolean;
 		getCurrent(trackIndex: number): TrackEntry;
 		addListener(listener: AnimationStateListener2): void;
 		removeListener(listener: AnimationStateListener2): void;
@@ -287,9 +288,11 @@ declare module spine {
 		animation: Animation;
 		next: TrackEntry;
 		mixingFrom: TrackEntry;
+		mixingTo: TrackEntry;
 		listener: AnimationStateListener2;
 		trackIndex: number;
 		loop: boolean;
+		holdPrevious: boolean;
 		eventThreshold: number;
 		attachmentThreshold: number;
 		drawOrderThreshold: number;
@@ -309,12 +312,10 @@ declare module spine {
 		interruptAlpha: number;
 		totalAlpha: number;
 		mixBlend: MixBlend;
-		timelineData: number[];
-		timelineDipMix: TrackEntry[];
+		timelineMode: number[];
+		timelineHoldMix: TrackEntry[];
 		timelinesRotation: number[];
 		reset(): void;
-		setTimelineData(to: TrackEntry, mixingToArray: Array<TrackEntry>, propertyIDs: IntSet): TrackEntry;
-		hasTimeline(id: number): boolean;
 		getAnimationTime(): number;
 		setAnimationLast(animationLast: number): void;
 		isComplete(): boolean;

+ 78 - 65
spine-ts/build/spine-canvas.js

@@ -1303,7 +1303,6 @@ var spine;
 			this.listeners = new Array();
 			this.queue = new EventQueue(this);
 			this.propertyIDs = new spine.IntSet();
-			this.mixingTo = new Array();
 			this.animationsChanged = false;
 			this.timeScale = 1;
 			this.trackEntryPool = new spine.Pool(function () { return new TrackEntry(); });
@@ -1350,6 +1349,8 @@ var spine;
 				if (current.mixingFrom != null && this.updateMixingFrom(current, delta)) {
 					var from = current.mixingFrom;
 					current.mixingFrom = null;
+					if (from != null)
+						from.mixingTo = null;
 					while (from != null) {
 						this.queue.end(from);
 						from = from.mixingFrom;
@@ -1369,6 +1370,8 @@ var spine;
 			if (to.mixTime > 0 && (to.mixTime >= to.mixDuration || to.timeScale == 0)) {
 				if (from.totalAlpha == 0 || to.mixDuration == 0) {
 					to.mixingFrom = from.mixingFrom;
+					if (from.mixingFrom != null)
+						from.mixingFrom.mixingTo = to;
 					to.interruptAlpha = from.interruptAlpha;
 					this.queue.end(from);
 				}
@@ -1400,19 +1403,19 @@ var spine;
 				var animationLast = current.animationLast, animationTime = current.getAnimationTime();
 				var timelineCount = current.animation.timelines.length;
 				var timelines = current.animation.timelines;
-				if (mix == 1 || blend == spine.MixBlend.add) {
+				if (i == 0 && (mix == 1 || blend == spine.MixBlend.add)) {
 					for (var ii = 0; ii < timelineCount; ii++)
 						timelines[ii].apply(skeleton, animationLast, animationTime, events, mix, blend, spine.MixDirection["in"]);
 				}
 				else {
-					var timelineData = current.timelineData;
+					var timelineMode = current.timelineMode;
 					var firstFrame = current.timelinesRotation.length == 0;
 					if (firstFrame)
 						spine.Utils.setArraySize(current.timelinesRotation, timelineCount << 1, null);
 					var timelinesRotation = current.timelinesRotation;
 					for (var ii = 0; ii < timelineCount; ii++) {
 						var timeline = timelines[ii];
-						var timelineBlend = timelineData[ii] == AnimationState.SUBSEQUENT ? blend : spine.MixBlend.setup;
+						var timelineBlend = timelineMode[ii] == AnimationState.SUBSEQUENT ? blend : spine.MixBlend.setup;
 						if (timeline instanceof spine.RotateTimeline) {
 							this.applyRotateTimeline(timeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, ii << 1, firstFrame);
 						}
@@ -1452,14 +1455,14 @@ var spine;
 			var animationLast = from.animationLast, animationTime = from.getAnimationTime();
 			var timelineCount = from.animation.timelines.length;
 			var timelines = from.animation.timelines;
-			var alphaDip = from.alpha * to.interruptAlpha, alphaMix = alphaDip * (1 - mix);
+			var alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix);
 			if (blend == spine.MixBlend.add) {
 				for (var i = 0; i < timelineCount; i++)
 					timelines[i].apply(skeleton, animationLast, animationTime, events, alphaMix, blend, spine.MixDirection.out);
 			}
 			else {
-				var timelineData = from.timelineData;
-				var timelineDipMix = from.timelineDipMix;
+				var timelineMode = from.timelineMode;
+				var timelineHoldMix = from.timelineHoldMix;
 				var firstFrame = from.timelinesRotation.length == 0;
 				if (firstFrame)
 					spine.Utils.setArraySize(from.timelinesRotation, timelineCount << 1, null);
@@ -1469,7 +1472,7 @@ var spine;
 					var timeline = timelines[i];
 					var timelineBlend;
 					var alpha = 0;
-					switch (timelineData[i]) {
+					switch (timelineMode[i]) {
 						case AnimationState.SUBSEQUENT:
 							if (!attachments && timeline instanceof spine.AttachmentTimeline)
 								continue;
@@ -1482,14 +1485,14 @@ var spine;
 							timelineBlend = spine.MixBlend.setup;
 							alpha = alphaMix;
 							break;
-						case AnimationState.DIP:
+						case AnimationState.HOLD:
 							timelineBlend = spine.MixBlend.setup;
-							alpha = alphaDip;
+							alpha = alphaHold;
 							break;
 						default:
 							timelineBlend = spine.MixBlend.setup;
-							var dipMix = timelineDipMix[i];
-							alpha = alphaDip * Math.max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
+							var holdMix = timelineHoldMix[i];
+							alpha = alphaHold * Math.max(0, 1 - holdMix.mixTime / holdMix.mixDuration);
 							break;
 					}
 					from.totalAlpha += alpha;
@@ -1619,6 +1622,7 @@ var spine;
 					break;
 				this.queue.end(from);
 				entry.mixingFrom = null;
+				entry.mixingTo = null;
 				entry = from;
 			}
 			this.tracks[current.trackIndex] = null;
@@ -1631,6 +1635,7 @@ var spine;
 				if (interrupt)
 					this.queue.interrupt(from);
 				current.mixingFrom = from;
+				from.mixingTo = current;
 				current.mixTime = 0;
 				if (from.mixingFrom != null && from.mixDuration > 0)
 					current.interruptAlpha *= Math.min(1, from.mixTime / from.mixDuration);
@@ -1740,6 +1745,7 @@ var spine;
 			entry.trackIndex = trackIndex;
 			entry.animation = animation;
 			entry.loop = loop;
+			entry.holdPrevious = false;
 			entry.eventThreshold = 0;
 			entry.attachmentThreshold = 0;
 			entry.drawOrderThreshold = 0;
@@ -1769,15 +1775,63 @@ var spine;
 		};
 		AnimationState.prototype._animationsChanged = function () {
 			this.animationsChanged = false;
-			var propertyIDs = this.propertyIDs;
-			propertyIDs.clear();
-			var mixingTo = this.mixingTo;
+			this.propertyIDs.clear();
 			for (var i = 0, n = this.tracks.length; i < n; i++) {
 				var entry = this.tracks[i];
-				if (entry != null && (i == 0 || entry.mixBlend != spine.MixBlend.add))
-					entry.setTimelineData(null, mixingTo, propertyIDs);
+				if (entry == null)
+					continue;
+				while (entry.mixingFrom != null)
+					entry = entry.mixingFrom;
+				do {
+					if (entry.mixingFrom == null || entry.mixBlend != spine.MixBlend.add)
+						this.setTimelineModes(entry);
+					entry = entry.mixingTo;
+				} while (entry != null);
+			}
+		};
+		AnimationState.prototype.setTimelineModes = function (entry) {
+			var to = entry.mixingTo;
+			var timelines = entry.animation.timelines;
+			var timelinesCount = entry.animation.timelines.length;
+			var timelineMode = spine.Utils.setArraySize(entry.timelineMode, timelinesCount);
+			entry.timelineHoldMix.length = 0;
+			var timelineDipMix = spine.Utils.setArraySize(entry.timelineHoldMix, timelinesCount);
+			var propertyIDs = this.propertyIDs;
+			if (to != null && to.holdPrevious) {
+				for (var i_16 = 0; i_16 < timelinesCount; i_16++) {
+					propertyIDs.add(timelines[i_16].getPropertyId());
+					timelineMode[i_16] = AnimationState.HOLD;
+				}
+				return;
+			}
+			outer: for (var i = 0; i < timelinesCount; i++) {
+				var id = timelines[i].getPropertyId();
+				if (!propertyIDs.add(id))
+					timelineMode[i] = AnimationState.SUBSEQUENT;
+				else if (to == null || !this.hasTimeline(to, id))
+					timelineMode[i] = AnimationState.FIRST;
+				else {
+					for (var next = to.mixingTo; next != null; next = next.mixingTo) {
+						if (this.hasTimeline(next, id))
+							continue;
+						if (entry.mixDuration > 0) {
+							timelineMode[i] = AnimationState.HOLD_MIX;
+							timelineDipMix[i] = next;
+							continue outer;
+						}
+						break;
+					}
+					timelineMode[i] = AnimationState.HOLD;
+				}
 			}
 		};
+		AnimationState.prototype.hasTimeline = function (entry, id) {
+			var timelines = entry.animation.timelines;
+			for (var i = 0, n = timelines.length; i < n; i++)
+				if (timelines[i].getPropertyId() == id)
+					return true;
+			return false;
+		};
 		AnimationState.prototype.getCurrent = function (trackIndex) {
 			if (trackIndex >= this.tracks.length)
 				return null;
@@ -1802,69 +1856,28 @@ var spine;
 		AnimationState.emptyAnimation = new spine.Animation("<empty>", [], 0);
 		AnimationState.SUBSEQUENT = 0;
 		AnimationState.FIRST = 1;
-		AnimationState.DIP = 2;
-		AnimationState.DIP_MIX = 3;
+		AnimationState.HOLD = 2;
+		AnimationState.HOLD_MIX = 3;
 		return AnimationState;
 	}());
 	spine.AnimationState = AnimationState;
 	var TrackEntry = (function () {
 		function TrackEntry() {
 			this.mixBlend = spine.MixBlend.replace;
-			this.timelineData = new Array();
-			this.timelineDipMix = new Array();
+			this.timelineMode = new Array();
+			this.timelineHoldMix = new Array();
 			this.timelinesRotation = new Array();
 		}
 		TrackEntry.prototype.reset = function () {
 			this.next = null;
 			this.mixingFrom = null;
+			this.mixingTo = null;
 			this.animation = null;
 			this.listener = null;
-			this.timelineData.length = 0;
-			this.timelineDipMix.length = 0;
+			this.timelineMode.length = 0;
+			this.timelineHoldMix.length = 0;
 			this.timelinesRotation.length = 0;
 		};
-		TrackEntry.prototype.setTimelineData = function (to, mixingToArray, propertyIDs) {
-			if (to != null)
-				mixingToArray.push(to);
-			var lastEntry = this.mixingFrom != null ? this.mixingFrom.setTimelineData(this, mixingToArray, propertyIDs) : this;
-			if (to != null)
-				mixingToArray.pop();
-			var mixingTo = mixingToArray;
-			var mixingToLast = mixingToArray.length - 1;
-			var timelines = this.animation.timelines;
-			var timelinesCount = this.animation.timelines.length;
-			var timelineData = spine.Utils.setArraySize(this.timelineData, timelinesCount);
-			this.timelineDipMix.length = 0;
-			var timelineDipMix = spine.Utils.setArraySize(this.timelineDipMix, timelinesCount);
-			outer: for (var i = 0; i < timelinesCount; i++) {
-				var id = timelines[i].getPropertyId();
-				if (!propertyIDs.add(id))
-					timelineData[i] = AnimationState.SUBSEQUENT;
-				else if (to == null || !to.hasTimeline(id))
-					timelineData[i] = AnimationState.FIRST;
-				else {
-					for (var ii = mixingToLast; ii >= 0; ii--) {
-						var entry = mixingTo[ii];
-						if (!entry.hasTimeline(id)) {
-							if (entry.mixDuration > 0) {
-								timelineData[i] = AnimationState.DIP_MIX;
-								timelineDipMix[i] = entry;
-								continue outer;
-							}
-						}
-					}
-					timelineData[i] = AnimationState.DIP;
-				}
-			}
-			return lastEntry;
-		};
-		TrackEntry.prototype.hasTimeline = function (id) {
-			var timelines = this.animation.timelines;
-			for (var i = 0, n = timelines.length; i < n; i++)
-				if (timelines[i].getPropertyId() == id)
-					return true;
-			return false;
-		};
 		TrackEntry.prototype.getAnimationTime = function () {
 			if (this.loop) {
 				var duration = this.animationEnd - this.animationStart;

File diff suppressed because it is too large
+ 0 - 0
spine-ts/build/spine-canvas.js.map


+ 8 - 7
spine-ts/build/spine-core.d.ts

@@ -244,15 +244,14 @@ declare module spine {
 		static emptyAnimation: Animation;
 		static SUBSEQUENT: number;
 		static FIRST: number;
-		static DIP: number;
-		static DIP_MIX: number;
+		static HOLD: number;
+		static HOLD_MIX: number;
 		data: AnimationStateData;
 		tracks: TrackEntry[];
 		events: Event[];
 		listeners: AnimationStateListener2[];
 		queue: EventQueue;
 		propertyIDs: IntSet;
-		mixingTo: TrackEntry[];
 		animationsChanged: boolean;
 		timeScale: number;
 		trackEntryPool: Pool<TrackEntry>;
@@ -277,6 +276,8 @@ declare module spine {
 		trackEntry(trackIndex: number, animation: Animation, loop: boolean, last: TrackEntry): TrackEntry;
 		disposeNext(entry: TrackEntry): void;
 		_animationsChanged(): void;
+		setTimelineModes(entry: TrackEntry): void;
+		hasTimeline(entry: TrackEntry, id: number): boolean;
 		getCurrent(trackIndex: number): TrackEntry;
 		addListener(listener: AnimationStateListener2): void;
 		removeListener(listener: AnimationStateListener2): void;
@@ -287,9 +288,11 @@ declare module spine {
 		animation: Animation;
 		next: TrackEntry;
 		mixingFrom: TrackEntry;
+		mixingTo: TrackEntry;
 		listener: AnimationStateListener2;
 		trackIndex: number;
 		loop: boolean;
+		holdPrevious: boolean;
 		eventThreshold: number;
 		attachmentThreshold: number;
 		drawOrderThreshold: number;
@@ -309,12 +312,10 @@ declare module spine {
 		interruptAlpha: number;
 		totalAlpha: number;
 		mixBlend: MixBlend;
-		timelineData: number[];
-		timelineDipMix: TrackEntry[];
+		timelineMode: number[];
+		timelineHoldMix: TrackEntry[];
 		timelinesRotation: number[];
 		reset(): void;
-		setTimelineData(to: TrackEntry, mixingToArray: Array<TrackEntry>, propertyIDs: IntSet): TrackEntry;
-		hasTimeline(id: number): boolean;
 		getAnimationTime(): number;
 		setAnimationLast(animationLast: number): void;
 		isComplete(): boolean;

+ 78 - 65
spine-ts/build/spine-core.js

@@ -1303,7 +1303,6 @@ var spine;
 			this.listeners = new Array();
 			this.queue = new EventQueue(this);
 			this.propertyIDs = new spine.IntSet();
-			this.mixingTo = new Array();
 			this.animationsChanged = false;
 			this.timeScale = 1;
 			this.trackEntryPool = new spine.Pool(function () { return new TrackEntry(); });
@@ -1350,6 +1349,8 @@ var spine;
 				if (current.mixingFrom != null && this.updateMixingFrom(current, delta)) {
 					var from = current.mixingFrom;
 					current.mixingFrom = null;
+					if (from != null)
+						from.mixingTo = null;
 					while (from != null) {
 						this.queue.end(from);
 						from = from.mixingFrom;
@@ -1369,6 +1370,8 @@ var spine;
 			if (to.mixTime > 0 && (to.mixTime >= to.mixDuration || to.timeScale == 0)) {
 				if (from.totalAlpha == 0 || to.mixDuration == 0) {
 					to.mixingFrom = from.mixingFrom;
+					if (from.mixingFrom != null)
+						from.mixingFrom.mixingTo = to;
 					to.interruptAlpha = from.interruptAlpha;
 					this.queue.end(from);
 				}
@@ -1400,19 +1403,19 @@ var spine;
 				var animationLast = current.animationLast, animationTime = current.getAnimationTime();
 				var timelineCount = current.animation.timelines.length;
 				var timelines = current.animation.timelines;
-				if (mix == 1 || blend == spine.MixBlend.add) {
+				if (i == 0 && (mix == 1 || blend == spine.MixBlend.add)) {
 					for (var ii = 0; ii < timelineCount; ii++)
 						timelines[ii].apply(skeleton, animationLast, animationTime, events, mix, blend, spine.MixDirection["in"]);
 				}
 				else {
-					var timelineData = current.timelineData;
+					var timelineMode = current.timelineMode;
 					var firstFrame = current.timelinesRotation.length == 0;
 					if (firstFrame)
 						spine.Utils.setArraySize(current.timelinesRotation, timelineCount << 1, null);
 					var timelinesRotation = current.timelinesRotation;
 					for (var ii = 0; ii < timelineCount; ii++) {
 						var timeline = timelines[ii];
-						var timelineBlend = timelineData[ii] == AnimationState.SUBSEQUENT ? blend : spine.MixBlend.setup;
+						var timelineBlend = timelineMode[ii] == AnimationState.SUBSEQUENT ? blend : spine.MixBlend.setup;
 						if (timeline instanceof spine.RotateTimeline) {
 							this.applyRotateTimeline(timeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, ii << 1, firstFrame);
 						}
@@ -1452,14 +1455,14 @@ var spine;
 			var animationLast = from.animationLast, animationTime = from.getAnimationTime();
 			var timelineCount = from.animation.timelines.length;
 			var timelines = from.animation.timelines;
-			var alphaDip = from.alpha * to.interruptAlpha, alphaMix = alphaDip * (1 - mix);
+			var alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix);
 			if (blend == spine.MixBlend.add) {
 				for (var i = 0; i < timelineCount; i++)
 					timelines[i].apply(skeleton, animationLast, animationTime, events, alphaMix, blend, spine.MixDirection.out);
 			}
 			else {
-				var timelineData = from.timelineData;
-				var timelineDipMix = from.timelineDipMix;
+				var timelineMode = from.timelineMode;
+				var timelineHoldMix = from.timelineHoldMix;
 				var firstFrame = from.timelinesRotation.length == 0;
 				if (firstFrame)
 					spine.Utils.setArraySize(from.timelinesRotation, timelineCount << 1, null);
@@ -1469,7 +1472,7 @@ var spine;
 					var timeline = timelines[i];
 					var timelineBlend;
 					var alpha = 0;
-					switch (timelineData[i]) {
+					switch (timelineMode[i]) {
 						case AnimationState.SUBSEQUENT:
 							if (!attachments && timeline instanceof spine.AttachmentTimeline)
 								continue;
@@ -1482,14 +1485,14 @@ var spine;
 							timelineBlend = spine.MixBlend.setup;
 							alpha = alphaMix;
 							break;
-						case AnimationState.DIP:
+						case AnimationState.HOLD:
 							timelineBlend = spine.MixBlend.setup;
-							alpha = alphaDip;
+							alpha = alphaHold;
 							break;
 						default:
 							timelineBlend = spine.MixBlend.setup;
-							var dipMix = timelineDipMix[i];
-							alpha = alphaDip * Math.max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
+							var holdMix = timelineHoldMix[i];
+							alpha = alphaHold * Math.max(0, 1 - holdMix.mixTime / holdMix.mixDuration);
 							break;
 					}
 					from.totalAlpha += alpha;
@@ -1619,6 +1622,7 @@ var spine;
 					break;
 				this.queue.end(from);
 				entry.mixingFrom = null;
+				entry.mixingTo = null;
 				entry = from;
 			}
 			this.tracks[current.trackIndex] = null;
@@ -1631,6 +1635,7 @@ var spine;
 				if (interrupt)
 					this.queue.interrupt(from);
 				current.mixingFrom = from;
+				from.mixingTo = current;
 				current.mixTime = 0;
 				if (from.mixingFrom != null && from.mixDuration > 0)
 					current.interruptAlpha *= Math.min(1, from.mixTime / from.mixDuration);
@@ -1740,6 +1745,7 @@ var spine;
 			entry.trackIndex = trackIndex;
 			entry.animation = animation;
 			entry.loop = loop;
+			entry.holdPrevious = false;
 			entry.eventThreshold = 0;
 			entry.attachmentThreshold = 0;
 			entry.drawOrderThreshold = 0;
@@ -1769,15 +1775,63 @@ var spine;
 		};
 		AnimationState.prototype._animationsChanged = function () {
 			this.animationsChanged = false;
-			var propertyIDs = this.propertyIDs;
-			propertyIDs.clear();
-			var mixingTo = this.mixingTo;
+			this.propertyIDs.clear();
 			for (var i = 0, n = this.tracks.length; i < n; i++) {
 				var entry = this.tracks[i];
-				if (entry != null && (i == 0 || entry.mixBlend != spine.MixBlend.add))
-					entry.setTimelineData(null, mixingTo, propertyIDs);
+				if (entry == null)
+					continue;
+				while (entry.mixingFrom != null)
+					entry = entry.mixingFrom;
+				do {
+					if (entry.mixingFrom == null || entry.mixBlend != spine.MixBlend.add)
+						this.setTimelineModes(entry);
+					entry = entry.mixingTo;
+				} while (entry != null);
+			}
+		};
+		AnimationState.prototype.setTimelineModes = function (entry) {
+			var to = entry.mixingTo;
+			var timelines = entry.animation.timelines;
+			var timelinesCount = entry.animation.timelines.length;
+			var timelineMode = spine.Utils.setArraySize(entry.timelineMode, timelinesCount);
+			entry.timelineHoldMix.length = 0;
+			var timelineDipMix = spine.Utils.setArraySize(entry.timelineHoldMix, timelinesCount);
+			var propertyIDs = this.propertyIDs;
+			if (to != null && to.holdPrevious) {
+				for (var i_16 = 0; i_16 < timelinesCount; i_16++) {
+					propertyIDs.add(timelines[i_16].getPropertyId());
+					timelineMode[i_16] = AnimationState.HOLD;
+				}
+				return;
+			}
+			outer: for (var i = 0; i < timelinesCount; i++) {
+				var id = timelines[i].getPropertyId();
+				if (!propertyIDs.add(id))
+					timelineMode[i] = AnimationState.SUBSEQUENT;
+				else if (to == null || !this.hasTimeline(to, id))
+					timelineMode[i] = AnimationState.FIRST;
+				else {
+					for (var next = to.mixingTo; next != null; next = next.mixingTo) {
+						if (this.hasTimeline(next, id))
+							continue;
+						if (entry.mixDuration > 0) {
+							timelineMode[i] = AnimationState.HOLD_MIX;
+							timelineDipMix[i] = next;
+							continue outer;
+						}
+						break;
+					}
+					timelineMode[i] = AnimationState.HOLD;
+				}
 			}
 		};
+		AnimationState.prototype.hasTimeline = function (entry, id) {
+			var timelines = entry.animation.timelines;
+			for (var i = 0, n = timelines.length; i < n; i++)
+				if (timelines[i].getPropertyId() == id)
+					return true;
+			return false;
+		};
 		AnimationState.prototype.getCurrent = function (trackIndex) {
 			if (trackIndex >= this.tracks.length)
 				return null;
@@ -1802,69 +1856,28 @@ var spine;
 		AnimationState.emptyAnimation = new spine.Animation("<empty>", [], 0);
 		AnimationState.SUBSEQUENT = 0;
 		AnimationState.FIRST = 1;
-		AnimationState.DIP = 2;
-		AnimationState.DIP_MIX = 3;
+		AnimationState.HOLD = 2;
+		AnimationState.HOLD_MIX = 3;
 		return AnimationState;
 	}());
 	spine.AnimationState = AnimationState;
 	var TrackEntry = (function () {
 		function TrackEntry() {
 			this.mixBlend = spine.MixBlend.replace;
-			this.timelineData = new Array();
-			this.timelineDipMix = new Array();
+			this.timelineMode = new Array();
+			this.timelineHoldMix = new Array();
 			this.timelinesRotation = new Array();
 		}
 		TrackEntry.prototype.reset = function () {
 			this.next = null;
 			this.mixingFrom = null;
+			this.mixingTo = null;
 			this.animation = null;
 			this.listener = null;
-			this.timelineData.length = 0;
-			this.timelineDipMix.length = 0;
+			this.timelineMode.length = 0;
+			this.timelineHoldMix.length = 0;
 			this.timelinesRotation.length = 0;
 		};
-		TrackEntry.prototype.setTimelineData = function (to, mixingToArray, propertyIDs) {
-			if (to != null)
-				mixingToArray.push(to);
-			var lastEntry = this.mixingFrom != null ? this.mixingFrom.setTimelineData(this, mixingToArray, propertyIDs) : this;
-			if (to != null)
-				mixingToArray.pop();
-			var mixingTo = mixingToArray;
-			var mixingToLast = mixingToArray.length - 1;
-			var timelines = this.animation.timelines;
-			var timelinesCount = this.animation.timelines.length;
-			var timelineData = spine.Utils.setArraySize(this.timelineData, timelinesCount);
-			this.timelineDipMix.length = 0;
-			var timelineDipMix = spine.Utils.setArraySize(this.timelineDipMix, timelinesCount);
-			outer: for (var i = 0; i < timelinesCount; i++) {
-				var id = timelines[i].getPropertyId();
-				if (!propertyIDs.add(id))
-					timelineData[i] = AnimationState.SUBSEQUENT;
-				else if (to == null || !to.hasTimeline(id))
-					timelineData[i] = AnimationState.FIRST;
-				else {
-					for (var ii = mixingToLast; ii >= 0; ii--) {
-						var entry = mixingTo[ii];
-						if (!entry.hasTimeline(id)) {
-							if (entry.mixDuration > 0) {
-								timelineData[i] = AnimationState.DIP_MIX;
-								timelineDipMix[i] = entry;
-								continue outer;
-							}
-						}
-					}
-					timelineData[i] = AnimationState.DIP;
-				}
-			}
-			return lastEntry;
-		};
-		TrackEntry.prototype.hasTimeline = function (id) {
-			var timelines = this.animation.timelines;
-			for (var i = 0, n = timelines.length; i < n; i++)
-				if (timelines[i].getPropertyId() == id)
-					return true;
-			return false;
-		};
 		TrackEntry.prototype.getAnimationTime = function () {
 			if (this.loop) {
 				var duration = this.animationEnd - this.animationStart;

File diff suppressed because it is too large
+ 0 - 0
spine-ts/build/spine-core.js.map


+ 8 - 7
spine-ts/build/spine-threejs.d.ts

@@ -244,15 +244,14 @@ declare module spine {
 		static emptyAnimation: Animation;
 		static SUBSEQUENT: number;
 		static FIRST: number;
-		static DIP: number;
-		static DIP_MIX: number;
+		static HOLD: number;
+		static HOLD_MIX: number;
 		data: AnimationStateData;
 		tracks: TrackEntry[];
 		events: Event[];
 		listeners: AnimationStateListener2[];
 		queue: EventQueue;
 		propertyIDs: IntSet;
-		mixingTo: TrackEntry[];
 		animationsChanged: boolean;
 		timeScale: number;
 		trackEntryPool: Pool<TrackEntry>;
@@ -277,6 +276,8 @@ declare module spine {
 		trackEntry(trackIndex: number, animation: Animation, loop: boolean, last: TrackEntry): TrackEntry;
 		disposeNext(entry: TrackEntry): void;
 		_animationsChanged(): void;
+		setTimelineModes(entry: TrackEntry): void;
+		hasTimeline(entry: TrackEntry, id: number): boolean;
 		getCurrent(trackIndex: number): TrackEntry;
 		addListener(listener: AnimationStateListener2): void;
 		removeListener(listener: AnimationStateListener2): void;
@@ -287,9 +288,11 @@ declare module spine {
 		animation: Animation;
 		next: TrackEntry;
 		mixingFrom: TrackEntry;
+		mixingTo: TrackEntry;
 		listener: AnimationStateListener2;
 		trackIndex: number;
 		loop: boolean;
+		holdPrevious: boolean;
 		eventThreshold: number;
 		attachmentThreshold: number;
 		drawOrderThreshold: number;
@@ -309,12 +312,10 @@ declare module spine {
 		interruptAlpha: number;
 		totalAlpha: number;
 		mixBlend: MixBlend;
-		timelineData: number[];
-		timelineDipMix: TrackEntry[];
+		timelineMode: number[];
+		timelineHoldMix: TrackEntry[];
 		timelinesRotation: number[];
 		reset(): void;
-		setTimelineData(to: TrackEntry, mixingToArray: Array<TrackEntry>, propertyIDs: IntSet): TrackEntry;
-		hasTimeline(id: number): boolean;
 		getAnimationTime(): number;
 		setAnimationLast(animationLast: number): void;
 		isComplete(): boolean;

+ 78 - 65
spine-ts/build/spine-threejs.js

@@ -1303,7 +1303,6 @@ var spine;
 			this.listeners = new Array();
 			this.queue = new EventQueue(this);
 			this.propertyIDs = new spine.IntSet();
-			this.mixingTo = new Array();
 			this.animationsChanged = false;
 			this.timeScale = 1;
 			this.trackEntryPool = new spine.Pool(function () { return new TrackEntry(); });
@@ -1350,6 +1349,8 @@ var spine;
 				if (current.mixingFrom != null && this.updateMixingFrom(current, delta)) {
 					var from = current.mixingFrom;
 					current.mixingFrom = null;
+					if (from != null)
+						from.mixingTo = null;
 					while (from != null) {
 						this.queue.end(from);
 						from = from.mixingFrom;
@@ -1369,6 +1370,8 @@ var spine;
 			if (to.mixTime > 0 && (to.mixTime >= to.mixDuration || to.timeScale == 0)) {
 				if (from.totalAlpha == 0 || to.mixDuration == 0) {
 					to.mixingFrom = from.mixingFrom;
+					if (from.mixingFrom != null)
+						from.mixingFrom.mixingTo = to;
 					to.interruptAlpha = from.interruptAlpha;
 					this.queue.end(from);
 				}
@@ -1400,19 +1403,19 @@ var spine;
 				var animationLast = current.animationLast, animationTime = current.getAnimationTime();
 				var timelineCount = current.animation.timelines.length;
 				var timelines = current.animation.timelines;
-				if (mix == 1 || blend == spine.MixBlend.add) {
+				if (i == 0 && (mix == 1 || blend == spine.MixBlend.add)) {
 					for (var ii = 0; ii < timelineCount; ii++)
 						timelines[ii].apply(skeleton, animationLast, animationTime, events, mix, blend, spine.MixDirection["in"]);
 				}
 				else {
-					var timelineData = current.timelineData;
+					var timelineMode = current.timelineMode;
 					var firstFrame = current.timelinesRotation.length == 0;
 					if (firstFrame)
 						spine.Utils.setArraySize(current.timelinesRotation, timelineCount << 1, null);
 					var timelinesRotation = current.timelinesRotation;
 					for (var ii = 0; ii < timelineCount; ii++) {
 						var timeline = timelines[ii];
-						var timelineBlend = timelineData[ii] == AnimationState.SUBSEQUENT ? blend : spine.MixBlend.setup;
+						var timelineBlend = timelineMode[ii] == AnimationState.SUBSEQUENT ? blend : spine.MixBlend.setup;
 						if (timeline instanceof spine.RotateTimeline) {
 							this.applyRotateTimeline(timeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, ii << 1, firstFrame);
 						}
@@ -1452,14 +1455,14 @@ var spine;
 			var animationLast = from.animationLast, animationTime = from.getAnimationTime();
 			var timelineCount = from.animation.timelines.length;
 			var timelines = from.animation.timelines;
-			var alphaDip = from.alpha * to.interruptAlpha, alphaMix = alphaDip * (1 - mix);
+			var alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix);
 			if (blend == spine.MixBlend.add) {
 				for (var i = 0; i < timelineCount; i++)
 					timelines[i].apply(skeleton, animationLast, animationTime, events, alphaMix, blend, spine.MixDirection.out);
 			}
 			else {
-				var timelineData = from.timelineData;
-				var timelineDipMix = from.timelineDipMix;
+				var timelineMode = from.timelineMode;
+				var timelineHoldMix = from.timelineHoldMix;
 				var firstFrame = from.timelinesRotation.length == 0;
 				if (firstFrame)
 					spine.Utils.setArraySize(from.timelinesRotation, timelineCount << 1, null);
@@ -1469,7 +1472,7 @@ var spine;
 					var timeline = timelines[i];
 					var timelineBlend;
 					var alpha = 0;
-					switch (timelineData[i]) {
+					switch (timelineMode[i]) {
 						case AnimationState.SUBSEQUENT:
 							if (!attachments && timeline instanceof spine.AttachmentTimeline)
 								continue;
@@ -1482,14 +1485,14 @@ var spine;
 							timelineBlend = spine.MixBlend.setup;
 							alpha = alphaMix;
 							break;
-						case AnimationState.DIP:
+						case AnimationState.HOLD:
 							timelineBlend = spine.MixBlend.setup;
-							alpha = alphaDip;
+							alpha = alphaHold;
 							break;
 						default:
 							timelineBlend = spine.MixBlend.setup;
-							var dipMix = timelineDipMix[i];
-							alpha = alphaDip * Math.max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
+							var holdMix = timelineHoldMix[i];
+							alpha = alphaHold * Math.max(0, 1 - holdMix.mixTime / holdMix.mixDuration);
 							break;
 					}
 					from.totalAlpha += alpha;
@@ -1619,6 +1622,7 @@ var spine;
 					break;
 				this.queue.end(from);
 				entry.mixingFrom = null;
+				entry.mixingTo = null;
 				entry = from;
 			}
 			this.tracks[current.trackIndex] = null;
@@ -1631,6 +1635,7 @@ var spine;
 				if (interrupt)
 					this.queue.interrupt(from);
 				current.mixingFrom = from;
+				from.mixingTo = current;
 				current.mixTime = 0;
 				if (from.mixingFrom != null && from.mixDuration > 0)
 					current.interruptAlpha *= Math.min(1, from.mixTime / from.mixDuration);
@@ -1740,6 +1745,7 @@ var spine;
 			entry.trackIndex = trackIndex;
 			entry.animation = animation;
 			entry.loop = loop;
+			entry.holdPrevious = false;
 			entry.eventThreshold = 0;
 			entry.attachmentThreshold = 0;
 			entry.drawOrderThreshold = 0;
@@ -1769,15 +1775,63 @@ var spine;
 		};
 		AnimationState.prototype._animationsChanged = function () {
 			this.animationsChanged = false;
-			var propertyIDs = this.propertyIDs;
-			propertyIDs.clear();
-			var mixingTo = this.mixingTo;
+			this.propertyIDs.clear();
 			for (var i = 0, n = this.tracks.length; i < n; i++) {
 				var entry = this.tracks[i];
-				if (entry != null && (i == 0 || entry.mixBlend != spine.MixBlend.add))
-					entry.setTimelineData(null, mixingTo, propertyIDs);
+				if (entry == null)
+					continue;
+				while (entry.mixingFrom != null)
+					entry = entry.mixingFrom;
+				do {
+					if (entry.mixingFrom == null || entry.mixBlend != spine.MixBlend.add)
+						this.setTimelineModes(entry);
+					entry = entry.mixingTo;
+				} while (entry != null);
+			}
+		};
+		AnimationState.prototype.setTimelineModes = function (entry) {
+			var to = entry.mixingTo;
+			var timelines = entry.animation.timelines;
+			var timelinesCount = entry.animation.timelines.length;
+			var timelineMode = spine.Utils.setArraySize(entry.timelineMode, timelinesCount);
+			entry.timelineHoldMix.length = 0;
+			var timelineDipMix = spine.Utils.setArraySize(entry.timelineHoldMix, timelinesCount);
+			var propertyIDs = this.propertyIDs;
+			if (to != null && to.holdPrevious) {
+				for (var i_16 = 0; i_16 < timelinesCount; i_16++) {
+					propertyIDs.add(timelines[i_16].getPropertyId());
+					timelineMode[i_16] = AnimationState.HOLD;
+				}
+				return;
+			}
+			outer: for (var i = 0; i < timelinesCount; i++) {
+				var id = timelines[i].getPropertyId();
+				if (!propertyIDs.add(id))
+					timelineMode[i] = AnimationState.SUBSEQUENT;
+				else if (to == null || !this.hasTimeline(to, id))
+					timelineMode[i] = AnimationState.FIRST;
+				else {
+					for (var next = to.mixingTo; next != null; next = next.mixingTo) {
+						if (this.hasTimeline(next, id))
+							continue;
+						if (entry.mixDuration > 0) {
+							timelineMode[i] = AnimationState.HOLD_MIX;
+							timelineDipMix[i] = next;
+							continue outer;
+						}
+						break;
+					}
+					timelineMode[i] = AnimationState.HOLD;
+				}
 			}
 		};
+		AnimationState.prototype.hasTimeline = function (entry, id) {
+			var timelines = entry.animation.timelines;
+			for (var i = 0, n = timelines.length; i < n; i++)
+				if (timelines[i].getPropertyId() == id)
+					return true;
+			return false;
+		};
 		AnimationState.prototype.getCurrent = function (trackIndex) {
 			if (trackIndex >= this.tracks.length)
 				return null;
@@ -1802,69 +1856,28 @@ var spine;
 		AnimationState.emptyAnimation = new spine.Animation("<empty>", [], 0);
 		AnimationState.SUBSEQUENT = 0;
 		AnimationState.FIRST = 1;
-		AnimationState.DIP = 2;
-		AnimationState.DIP_MIX = 3;
+		AnimationState.HOLD = 2;
+		AnimationState.HOLD_MIX = 3;
 		return AnimationState;
 	}());
 	spine.AnimationState = AnimationState;
 	var TrackEntry = (function () {
 		function TrackEntry() {
 			this.mixBlend = spine.MixBlend.replace;
-			this.timelineData = new Array();
-			this.timelineDipMix = new Array();
+			this.timelineMode = new Array();
+			this.timelineHoldMix = new Array();
 			this.timelinesRotation = new Array();
 		}
 		TrackEntry.prototype.reset = function () {
 			this.next = null;
 			this.mixingFrom = null;
+			this.mixingTo = null;
 			this.animation = null;
 			this.listener = null;
-			this.timelineData.length = 0;
-			this.timelineDipMix.length = 0;
+			this.timelineMode.length = 0;
+			this.timelineHoldMix.length = 0;
 			this.timelinesRotation.length = 0;
 		};
-		TrackEntry.prototype.setTimelineData = function (to, mixingToArray, propertyIDs) {
-			if (to != null)
-				mixingToArray.push(to);
-			var lastEntry = this.mixingFrom != null ? this.mixingFrom.setTimelineData(this, mixingToArray, propertyIDs) : this;
-			if (to != null)
-				mixingToArray.pop();
-			var mixingTo = mixingToArray;
-			var mixingToLast = mixingToArray.length - 1;
-			var timelines = this.animation.timelines;
-			var timelinesCount = this.animation.timelines.length;
-			var timelineData = spine.Utils.setArraySize(this.timelineData, timelinesCount);
-			this.timelineDipMix.length = 0;
-			var timelineDipMix = spine.Utils.setArraySize(this.timelineDipMix, timelinesCount);
-			outer: for (var i = 0; i < timelinesCount; i++) {
-				var id = timelines[i].getPropertyId();
-				if (!propertyIDs.add(id))
-					timelineData[i] = AnimationState.SUBSEQUENT;
-				else if (to == null || !to.hasTimeline(id))
-					timelineData[i] = AnimationState.FIRST;
-				else {
-					for (var ii = mixingToLast; ii >= 0; ii--) {
-						var entry = mixingTo[ii];
-						if (!entry.hasTimeline(id)) {
-							if (entry.mixDuration > 0) {
-								timelineData[i] = AnimationState.DIP_MIX;
-								timelineDipMix[i] = entry;
-								continue outer;
-							}
-						}
-					}
-					timelineData[i] = AnimationState.DIP;
-				}
-			}
-			return lastEntry;
-		};
-		TrackEntry.prototype.hasTimeline = function (id) {
-			var timelines = this.animation.timelines;
-			for (var i = 0, n = timelines.length; i < n; i++)
-				if (timelines[i].getPropertyId() == id)
-					return true;
-			return false;
-		};
 		TrackEntry.prototype.getAnimationTime = function () {
 			if (this.loop) {
 				var duration = this.animationEnd - this.animationStart;

File diff suppressed because it is too large
+ 0 - 0
spine-ts/build/spine-threejs.js.map


+ 8 - 7
spine-ts/build/spine-webgl.d.ts

@@ -244,15 +244,14 @@ declare module spine {
 		static emptyAnimation: Animation;
 		static SUBSEQUENT: number;
 		static FIRST: number;
-		static DIP: number;
-		static DIP_MIX: number;
+		static HOLD: number;
+		static HOLD_MIX: number;
 		data: AnimationStateData;
 		tracks: TrackEntry[];
 		events: Event[];
 		listeners: AnimationStateListener2[];
 		queue: EventQueue;
 		propertyIDs: IntSet;
-		mixingTo: TrackEntry[];
 		animationsChanged: boolean;
 		timeScale: number;
 		trackEntryPool: Pool<TrackEntry>;
@@ -277,6 +276,8 @@ declare module spine {
 		trackEntry(trackIndex: number, animation: Animation, loop: boolean, last: TrackEntry): TrackEntry;
 		disposeNext(entry: TrackEntry): void;
 		_animationsChanged(): void;
+		setTimelineModes(entry: TrackEntry): void;
+		hasTimeline(entry: TrackEntry, id: number): boolean;
 		getCurrent(trackIndex: number): TrackEntry;
 		addListener(listener: AnimationStateListener2): void;
 		removeListener(listener: AnimationStateListener2): void;
@@ -287,9 +288,11 @@ declare module spine {
 		animation: Animation;
 		next: TrackEntry;
 		mixingFrom: TrackEntry;
+		mixingTo: TrackEntry;
 		listener: AnimationStateListener2;
 		trackIndex: number;
 		loop: boolean;
+		holdPrevious: boolean;
 		eventThreshold: number;
 		attachmentThreshold: number;
 		drawOrderThreshold: number;
@@ -309,12 +312,10 @@ declare module spine {
 		interruptAlpha: number;
 		totalAlpha: number;
 		mixBlend: MixBlend;
-		timelineData: number[];
-		timelineDipMix: TrackEntry[];
+		timelineMode: number[];
+		timelineHoldMix: TrackEntry[];
 		timelinesRotation: number[];
 		reset(): void;
-		setTimelineData(to: TrackEntry, mixingToArray: Array<TrackEntry>, propertyIDs: IntSet): TrackEntry;
-		hasTimeline(id: number): boolean;
 		getAnimationTime(): number;
 		setAnimationLast(animationLast: number): void;
 		isComplete(): boolean;

+ 91 - 78
spine-ts/build/spine-webgl.js

@@ -1303,7 +1303,6 @@ var spine;
 			this.listeners = new Array();
 			this.queue = new EventQueue(this);
 			this.propertyIDs = new spine.IntSet();
-			this.mixingTo = new Array();
 			this.animationsChanged = false;
 			this.timeScale = 1;
 			this.trackEntryPool = new spine.Pool(function () { return new TrackEntry(); });
@@ -1350,6 +1349,8 @@ var spine;
 				if (current.mixingFrom != null && this.updateMixingFrom(current, delta)) {
 					var from = current.mixingFrom;
 					current.mixingFrom = null;
+					if (from != null)
+						from.mixingTo = null;
 					while (from != null) {
 						this.queue.end(from);
 						from = from.mixingFrom;
@@ -1369,6 +1370,8 @@ var spine;
 			if (to.mixTime > 0 && (to.mixTime >= to.mixDuration || to.timeScale == 0)) {
 				if (from.totalAlpha == 0 || to.mixDuration == 0) {
 					to.mixingFrom = from.mixingFrom;
+					if (from.mixingFrom != null)
+						from.mixingFrom.mixingTo = to;
 					to.interruptAlpha = from.interruptAlpha;
 					this.queue.end(from);
 				}
@@ -1400,19 +1403,19 @@ var spine;
 				var animationLast = current.animationLast, animationTime = current.getAnimationTime();
 				var timelineCount = current.animation.timelines.length;
 				var timelines = current.animation.timelines;
-				if (mix == 1 || blend == spine.MixBlend.add) {
+				if (i == 0 && (mix == 1 || blend == spine.MixBlend.add)) {
 					for (var ii = 0; ii < timelineCount; ii++)
 						timelines[ii].apply(skeleton, animationLast, animationTime, events, mix, blend, spine.MixDirection["in"]);
 				}
 				else {
-					var timelineData = current.timelineData;
+					var timelineMode = current.timelineMode;
 					var firstFrame = current.timelinesRotation.length == 0;
 					if (firstFrame)
 						spine.Utils.setArraySize(current.timelinesRotation, timelineCount << 1, null);
 					var timelinesRotation = current.timelinesRotation;
 					for (var ii = 0; ii < timelineCount; ii++) {
 						var timeline = timelines[ii];
-						var timelineBlend = timelineData[ii] == AnimationState.SUBSEQUENT ? blend : spine.MixBlend.setup;
+						var timelineBlend = timelineMode[ii] == AnimationState.SUBSEQUENT ? blend : spine.MixBlend.setup;
 						if (timeline instanceof spine.RotateTimeline) {
 							this.applyRotateTimeline(timeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, ii << 1, firstFrame);
 						}
@@ -1452,14 +1455,14 @@ var spine;
 			var animationLast = from.animationLast, animationTime = from.getAnimationTime();
 			var timelineCount = from.animation.timelines.length;
 			var timelines = from.animation.timelines;
-			var alphaDip = from.alpha * to.interruptAlpha, alphaMix = alphaDip * (1 - mix);
+			var alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix);
 			if (blend == spine.MixBlend.add) {
 				for (var i = 0; i < timelineCount; i++)
 					timelines[i].apply(skeleton, animationLast, animationTime, events, alphaMix, blend, spine.MixDirection.out);
 			}
 			else {
-				var timelineData = from.timelineData;
-				var timelineDipMix = from.timelineDipMix;
+				var timelineMode = from.timelineMode;
+				var timelineHoldMix = from.timelineHoldMix;
 				var firstFrame = from.timelinesRotation.length == 0;
 				if (firstFrame)
 					spine.Utils.setArraySize(from.timelinesRotation, timelineCount << 1, null);
@@ -1469,7 +1472,7 @@ var spine;
 					var timeline = timelines[i];
 					var timelineBlend;
 					var alpha = 0;
-					switch (timelineData[i]) {
+					switch (timelineMode[i]) {
 						case AnimationState.SUBSEQUENT:
 							if (!attachments && timeline instanceof spine.AttachmentTimeline)
 								continue;
@@ -1482,14 +1485,14 @@ var spine;
 							timelineBlend = spine.MixBlend.setup;
 							alpha = alphaMix;
 							break;
-						case AnimationState.DIP:
+						case AnimationState.HOLD:
 							timelineBlend = spine.MixBlend.setup;
-							alpha = alphaDip;
+							alpha = alphaHold;
 							break;
 						default:
 							timelineBlend = spine.MixBlend.setup;
-							var dipMix = timelineDipMix[i];
-							alpha = alphaDip * Math.max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
+							var holdMix = timelineHoldMix[i];
+							alpha = alphaHold * Math.max(0, 1 - holdMix.mixTime / holdMix.mixDuration);
 							break;
 					}
 					from.totalAlpha += alpha;
@@ -1619,6 +1622,7 @@ var spine;
 					break;
 				this.queue.end(from);
 				entry.mixingFrom = null;
+				entry.mixingTo = null;
 				entry = from;
 			}
 			this.tracks[current.trackIndex] = null;
@@ -1631,6 +1635,7 @@ var spine;
 				if (interrupt)
 					this.queue.interrupt(from);
 				current.mixingFrom = from;
+				from.mixingTo = current;
 				current.mixTime = 0;
 				if (from.mixingFrom != null && from.mixDuration > 0)
 					current.interruptAlpha *= Math.min(1, from.mixTime / from.mixDuration);
@@ -1740,6 +1745,7 @@ var spine;
 			entry.trackIndex = trackIndex;
 			entry.animation = animation;
 			entry.loop = loop;
+			entry.holdPrevious = false;
 			entry.eventThreshold = 0;
 			entry.attachmentThreshold = 0;
 			entry.drawOrderThreshold = 0;
@@ -1769,14 +1775,62 @@ var spine;
 		};
 		AnimationState.prototype._animationsChanged = function () {
 			this.animationsChanged = false;
-			var propertyIDs = this.propertyIDs;
-			propertyIDs.clear();
-			var mixingTo = this.mixingTo;
+			this.propertyIDs.clear();
 			for (var i = 0, n = this.tracks.length; i < n; i++) {
 				var entry = this.tracks[i];
-				if (entry != null && (i == 0 || entry.mixBlend != spine.MixBlend.add))
-					entry.setTimelineData(null, mixingTo, propertyIDs);
+				if (entry == null)
+					continue;
+				while (entry.mixingFrom != null)
+					entry = entry.mixingFrom;
+				do {
+					if (entry.mixingFrom == null || entry.mixBlend != spine.MixBlend.add)
+						this.setTimelineModes(entry);
+					entry = entry.mixingTo;
+				} while (entry != null);
+			}
+		};
+		AnimationState.prototype.setTimelineModes = function (entry) {
+			var to = entry.mixingTo;
+			var timelines = entry.animation.timelines;
+			var timelinesCount = entry.animation.timelines.length;
+			var timelineMode = spine.Utils.setArraySize(entry.timelineMode, timelinesCount);
+			entry.timelineHoldMix.length = 0;
+			var timelineDipMix = spine.Utils.setArraySize(entry.timelineHoldMix, timelinesCount);
+			var propertyIDs = this.propertyIDs;
+			if (to != null && to.holdPrevious) {
+				for (var i_16 = 0; i_16 < timelinesCount; i_16++) {
+					propertyIDs.add(timelines[i_16].getPropertyId());
+					timelineMode[i_16] = AnimationState.HOLD;
+				}
+				return;
 			}
+			outer: for (var i = 0; i < timelinesCount; i++) {
+				var id = timelines[i].getPropertyId();
+				if (!propertyIDs.add(id))
+					timelineMode[i] = AnimationState.SUBSEQUENT;
+				else if (to == null || !this.hasTimeline(to, id))
+					timelineMode[i] = AnimationState.FIRST;
+				else {
+					for (var next = to.mixingTo; next != null; next = next.mixingTo) {
+						if (this.hasTimeline(next, id))
+							continue;
+						if (entry.mixDuration > 0) {
+							timelineMode[i] = AnimationState.HOLD_MIX;
+							timelineDipMix[i] = next;
+							continue outer;
+						}
+						break;
+					}
+					timelineMode[i] = AnimationState.HOLD;
+				}
+			}
+		};
+		AnimationState.prototype.hasTimeline = function (entry, id) {
+			var timelines = entry.animation.timelines;
+			for (var i = 0, n = timelines.length; i < n; i++)
+				if (timelines[i].getPropertyId() == id)
+					return true;
+			return false;
 		};
 		AnimationState.prototype.getCurrent = function (trackIndex) {
 			if (trackIndex >= this.tracks.length)
@@ -1802,69 +1856,28 @@ var spine;
 		AnimationState.emptyAnimation = new spine.Animation("<empty>", [], 0);
 		AnimationState.SUBSEQUENT = 0;
 		AnimationState.FIRST = 1;
-		AnimationState.DIP = 2;
-		AnimationState.DIP_MIX = 3;
+		AnimationState.HOLD = 2;
+		AnimationState.HOLD_MIX = 3;
 		return AnimationState;
 	}());
 	spine.AnimationState = AnimationState;
 	var TrackEntry = (function () {
 		function TrackEntry() {
 			this.mixBlend = spine.MixBlend.replace;
-			this.timelineData = new Array();
-			this.timelineDipMix = new Array();
+			this.timelineMode = new Array();
+			this.timelineHoldMix = new Array();
 			this.timelinesRotation = new Array();
 		}
 		TrackEntry.prototype.reset = function () {
 			this.next = null;
 			this.mixingFrom = null;
+			this.mixingTo = null;
 			this.animation = null;
 			this.listener = null;
-			this.timelineData.length = 0;
-			this.timelineDipMix.length = 0;
+			this.timelineMode.length = 0;
+			this.timelineHoldMix.length = 0;
 			this.timelinesRotation.length = 0;
 		};
-		TrackEntry.prototype.setTimelineData = function (to, mixingToArray, propertyIDs) {
-			if (to != null)
-				mixingToArray.push(to);
-			var lastEntry = this.mixingFrom != null ? this.mixingFrom.setTimelineData(this, mixingToArray, propertyIDs) : this;
-			if (to != null)
-				mixingToArray.pop();
-			var mixingTo = mixingToArray;
-			var mixingToLast = mixingToArray.length - 1;
-			var timelines = this.animation.timelines;
-			var timelinesCount = this.animation.timelines.length;
-			var timelineData = spine.Utils.setArraySize(this.timelineData, timelinesCount);
-			this.timelineDipMix.length = 0;
-			var timelineDipMix = spine.Utils.setArraySize(this.timelineDipMix, timelinesCount);
-			outer: for (var i = 0; i < timelinesCount; i++) {
-				var id = timelines[i].getPropertyId();
-				if (!propertyIDs.add(id))
-					timelineData[i] = AnimationState.SUBSEQUENT;
-				else if (to == null || !to.hasTimeline(id))
-					timelineData[i] = AnimationState.FIRST;
-				else {
-					for (var ii = mixingToLast; ii >= 0; ii--) {
-						var entry = mixingTo[ii];
-						if (!entry.hasTimeline(id)) {
-							if (entry.mixDuration > 0) {
-								timelineData[i] = AnimationState.DIP_MIX;
-								timelineDipMix[i] = entry;
-								continue outer;
-							}
-						}
-					}
-					timelineData[i] = AnimationState.DIP;
-				}
-			}
-			return lastEntry;
-		};
-		TrackEntry.prototype.hasTimeline = function (id) {
-			var timelines = this.animation.timelines;
-			for (var i = 0, n = timelines.length; i < n; i++)
-				if (timelines[i].getPropertyId() == id)
-					return true;
-			return false;
-		};
 		TrackEntry.prototype.getAnimationTime = function () {
 			if (this.loop) {
 				var duration = this.animationEnd - this.animationStart;
@@ -7000,8 +7013,8 @@ var spine;
 						break;
 					}
 					var listeners = _this.listeners;
-					for (var i_16 = 0; i_16 < listeners.length; i_16++) {
-						listeners[i_16].down(_this.currTouch.x, _this.currTouch.y);
+					for (var i_17 = 0; i_17 < listeners.length; i_17++) {
+						listeners[i_17].down(_this.currTouch.x, _this.currTouch.y);
 					}
 					console.log("Start " + _this.currTouch.x + ", " + _this.currTouch.y);
 					_this.lastX = _this.currTouch.x;
@@ -7019,8 +7032,8 @@ var spine;
 							var y = _this.currTouch.y = touch.clientY - rect.top;
 							_this.touchesPool.free(_this.currTouch);
 							var listeners = _this.listeners;
-							for (var i_17 = 0; i_17 < listeners.length; i_17++) {
-								listeners[i_17].up(x, y);
+							for (var i_18 = 0; i_18 < listeners.length; i_18++) {
+								listeners[i_18].up(x, y);
 							}
 							console.log("End " + x + ", " + y);
 							_this.lastX = x;
@@ -7042,8 +7055,8 @@ var spine;
 							var y = _this.currTouch.y = touch.clientY - rect.top;
 							_this.touchesPool.free(_this.currTouch);
 							var listeners = _this.listeners;
-							for (var i_18 = 0; i_18 < listeners.length; i_18++) {
-								listeners[i_18].up(x, y);
+							for (var i_19 = 0; i_19 < listeners.length; i_19++) {
+								listeners[i_19].up(x, y);
 							}
 							console.log("End " + x + ", " + y);
 							_this.lastX = x;
@@ -7066,8 +7079,8 @@ var spine;
 							var x = touch.clientX - rect.left;
 							var y = touch.clientY - rect.top;
 							var listeners = _this.listeners;
-							for (var i_19 = 0; i_19 < listeners.length; i_19++) {
-								listeners[i_19].dragged(x, y);
+							for (var i_20 = 0; i_20 < listeners.length; i_20++) {
+								listeners[i_20].dragged(x, y);
 							}
 							console.log("Drag " + x + ", " + y);
 							_this.lastX = _this.currTouch.x = x;
@@ -8917,11 +8930,11 @@ var spine;
 						var nn = clip.worldVerticesLength;
 						var world = this.temp = spine.Utils.setArraySize(this.temp, nn, 0);
 						clip.computeWorldVertices(slot, 0, nn, world, 0, 2);
-						for (var i_20 = 0, n_2 = world.length; i_20 < n_2; i_20 += 2) {
-							var x = world[i_20];
-							var y = world[i_20 + 1];
-							var x2 = world[(i_20 + 2) % world.length];
-							var y2 = world[(i_20 + 3) % world.length];
+						for (var i_21 = 0, n_2 = world.length; i_21 < n_2; i_21 += 2) {
+							var x = world[i_21];
+							var y = world[i_21 + 1];
+							var x2 = world[(i_21 + 2) % world.length];
+							var y2 = world[(i_21 + 3) % world.length];
 							shapes.line(x, y, x2, y2);
 						}
 					}

File diff suppressed because it is too large
+ 0 - 0
spine-ts/build/spine-webgl.js.map


+ 8 - 7
spine-ts/build/spine-widget.d.ts

@@ -244,15 +244,14 @@ declare module spine {
 		static emptyAnimation: Animation;
 		static SUBSEQUENT: number;
 		static FIRST: number;
-		static DIP: number;
-		static DIP_MIX: number;
+		static HOLD: number;
+		static HOLD_MIX: number;
 		data: AnimationStateData;
 		tracks: TrackEntry[];
 		events: Event[];
 		listeners: AnimationStateListener2[];
 		queue: EventQueue;
 		propertyIDs: IntSet;
-		mixingTo: TrackEntry[];
 		animationsChanged: boolean;
 		timeScale: number;
 		trackEntryPool: Pool<TrackEntry>;
@@ -277,6 +276,8 @@ declare module spine {
 		trackEntry(trackIndex: number, animation: Animation, loop: boolean, last: TrackEntry): TrackEntry;
 		disposeNext(entry: TrackEntry): void;
 		_animationsChanged(): void;
+		setTimelineModes(entry: TrackEntry): void;
+		hasTimeline(entry: TrackEntry, id: number): boolean;
 		getCurrent(trackIndex: number): TrackEntry;
 		addListener(listener: AnimationStateListener2): void;
 		removeListener(listener: AnimationStateListener2): void;
@@ -287,9 +288,11 @@ declare module spine {
 		animation: Animation;
 		next: TrackEntry;
 		mixingFrom: TrackEntry;
+		mixingTo: TrackEntry;
 		listener: AnimationStateListener2;
 		trackIndex: number;
 		loop: boolean;
+		holdPrevious: boolean;
 		eventThreshold: number;
 		attachmentThreshold: number;
 		drawOrderThreshold: number;
@@ -309,12 +312,10 @@ declare module spine {
 		interruptAlpha: number;
 		totalAlpha: number;
 		mixBlend: MixBlend;
-		timelineData: number[];
-		timelineDipMix: TrackEntry[];
+		timelineMode: number[];
+		timelineHoldMix: TrackEntry[];
 		timelinesRotation: number[];
 		reset(): void;
-		setTimelineData(to: TrackEntry, mixingToArray: Array<TrackEntry>, propertyIDs: IntSet): TrackEntry;
-		hasTimeline(id: number): boolean;
 		getAnimationTime(): number;
 		setAnimationLast(animationLast: number): void;
 		isComplete(): boolean;

+ 91 - 78
spine-ts/build/spine-widget.js

@@ -1303,7 +1303,6 @@ var spine;
 			this.listeners = new Array();
 			this.queue = new EventQueue(this);
 			this.propertyIDs = new spine.IntSet();
-			this.mixingTo = new Array();
 			this.animationsChanged = false;
 			this.timeScale = 1;
 			this.trackEntryPool = new spine.Pool(function () { return new TrackEntry(); });
@@ -1350,6 +1349,8 @@ var spine;
 				if (current.mixingFrom != null && this.updateMixingFrom(current, delta)) {
 					var from = current.mixingFrom;
 					current.mixingFrom = null;
+					if (from != null)
+						from.mixingTo = null;
 					while (from != null) {
 						this.queue.end(from);
 						from = from.mixingFrom;
@@ -1369,6 +1370,8 @@ var spine;
 			if (to.mixTime > 0 && (to.mixTime >= to.mixDuration || to.timeScale == 0)) {
 				if (from.totalAlpha == 0 || to.mixDuration == 0) {
 					to.mixingFrom = from.mixingFrom;
+					if (from.mixingFrom != null)
+						from.mixingFrom.mixingTo = to;
 					to.interruptAlpha = from.interruptAlpha;
 					this.queue.end(from);
 				}
@@ -1400,19 +1403,19 @@ var spine;
 				var animationLast = current.animationLast, animationTime = current.getAnimationTime();
 				var timelineCount = current.animation.timelines.length;
 				var timelines = current.animation.timelines;
-				if (mix == 1 || blend == spine.MixBlend.add) {
+				if (i == 0 && (mix == 1 || blend == spine.MixBlend.add)) {
 					for (var ii = 0; ii < timelineCount; ii++)
 						timelines[ii].apply(skeleton, animationLast, animationTime, events, mix, blend, spine.MixDirection["in"]);
 				}
 				else {
-					var timelineData = current.timelineData;
+					var timelineMode = current.timelineMode;
 					var firstFrame = current.timelinesRotation.length == 0;
 					if (firstFrame)
 						spine.Utils.setArraySize(current.timelinesRotation, timelineCount << 1, null);
 					var timelinesRotation = current.timelinesRotation;
 					for (var ii = 0; ii < timelineCount; ii++) {
 						var timeline = timelines[ii];
-						var timelineBlend = timelineData[ii] == AnimationState.SUBSEQUENT ? blend : spine.MixBlend.setup;
+						var timelineBlend = timelineMode[ii] == AnimationState.SUBSEQUENT ? blend : spine.MixBlend.setup;
 						if (timeline instanceof spine.RotateTimeline) {
 							this.applyRotateTimeline(timeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, ii << 1, firstFrame);
 						}
@@ -1452,14 +1455,14 @@ var spine;
 			var animationLast = from.animationLast, animationTime = from.getAnimationTime();
 			var timelineCount = from.animation.timelines.length;
 			var timelines = from.animation.timelines;
-			var alphaDip = from.alpha * to.interruptAlpha, alphaMix = alphaDip * (1 - mix);
+			var alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix);
 			if (blend == spine.MixBlend.add) {
 				for (var i = 0; i < timelineCount; i++)
 					timelines[i].apply(skeleton, animationLast, animationTime, events, alphaMix, blend, spine.MixDirection.out);
 			}
 			else {
-				var timelineData = from.timelineData;
-				var timelineDipMix = from.timelineDipMix;
+				var timelineMode = from.timelineMode;
+				var timelineHoldMix = from.timelineHoldMix;
 				var firstFrame = from.timelinesRotation.length == 0;
 				if (firstFrame)
 					spine.Utils.setArraySize(from.timelinesRotation, timelineCount << 1, null);
@@ -1469,7 +1472,7 @@ var spine;
 					var timeline = timelines[i];
 					var timelineBlend;
 					var alpha = 0;
-					switch (timelineData[i]) {
+					switch (timelineMode[i]) {
 						case AnimationState.SUBSEQUENT:
 							if (!attachments && timeline instanceof spine.AttachmentTimeline)
 								continue;
@@ -1482,14 +1485,14 @@ var spine;
 							timelineBlend = spine.MixBlend.setup;
 							alpha = alphaMix;
 							break;
-						case AnimationState.DIP:
+						case AnimationState.HOLD:
 							timelineBlend = spine.MixBlend.setup;
-							alpha = alphaDip;
+							alpha = alphaHold;
 							break;
 						default:
 							timelineBlend = spine.MixBlend.setup;
-							var dipMix = timelineDipMix[i];
-							alpha = alphaDip * Math.max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
+							var holdMix = timelineHoldMix[i];
+							alpha = alphaHold * Math.max(0, 1 - holdMix.mixTime / holdMix.mixDuration);
 							break;
 					}
 					from.totalAlpha += alpha;
@@ -1619,6 +1622,7 @@ var spine;
 					break;
 				this.queue.end(from);
 				entry.mixingFrom = null;
+				entry.mixingTo = null;
 				entry = from;
 			}
 			this.tracks[current.trackIndex] = null;
@@ -1631,6 +1635,7 @@ var spine;
 				if (interrupt)
 					this.queue.interrupt(from);
 				current.mixingFrom = from;
+				from.mixingTo = current;
 				current.mixTime = 0;
 				if (from.mixingFrom != null && from.mixDuration > 0)
 					current.interruptAlpha *= Math.min(1, from.mixTime / from.mixDuration);
@@ -1740,6 +1745,7 @@ var spine;
 			entry.trackIndex = trackIndex;
 			entry.animation = animation;
 			entry.loop = loop;
+			entry.holdPrevious = false;
 			entry.eventThreshold = 0;
 			entry.attachmentThreshold = 0;
 			entry.drawOrderThreshold = 0;
@@ -1769,14 +1775,62 @@ var spine;
 		};
 		AnimationState.prototype._animationsChanged = function () {
 			this.animationsChanged = false;
-			var propertyIDs = this.propertyIDs;
-			propertyIDs.clear();
-			var mixingTo = this.mixingTo;
+			this.propertyIDs.clear();
 			for (var i = 0, n = this.tracks.length; i < n; i++) {
 				var entry = this.tracks[i];
-				if (entry != null && (i == 0 || entry.mixBlend != spine.MixBlend.add))
-					entry.setTimelineData(null, mixingTo, propertyIDs);
+				if (entry == null)
+					continue;
+				while (entry.mixingFrom != null)
+					entry = entry.mixingFrom;
+				do {
+					if (entry.mixingFrom == null || entry.mixBlend != spine.MixBlend.add)
+						this.setTimelineModes(entry);
+					entry = entry.mixingTo;
+				} while (entry != null);
+			}
+		};
+		AnimationState.prototype.setTimelineModes = function (entry) {
+			var to = entry.mixingTo;
+			var timelines = entry.animation.timelines;
+			var timelinesCount = entry.animation.timelines.length;
+			var timelineMode = spine.Utils.setArraySize(entry.timelineMode, timelinesCount);
+			entry.timelineHoldMix.length = 0;
+			var timelineDipMix = spine.Utils.setArraySize(entry.timelineHoldMix, timelinesCount);
+			var propertyIDs = this.propertyIDs;
+			if (to != null && to.holdPrevious) {
+				for (var i_16 = 0; i_16 < timelinesCount; i_16++) {
+					propertyIDs.add(timelines[i_16].getPropertyId());
+					timelineMode[i_16] = AnimationState.HOLD;
+				}
+				return;
 			}
+			outer: for (var i = 0; i < timelinesCount; i++) {
+				var id = timelines[i].getPropertyId();
+				if (!propertyIDs.add(id))
+					timelineMode[i] = AnimationState.SUBSEQUENT;
+				else if (to == null || !this.hasTimeline(to, id))
+					timelineMode[i] = AnimationState.FIRST;
+				else {
+					for (var next = to.mixingTo; next != null; next = next.mixingTo) {
+						if (this.hasTimeline(next, id))
+							continue;
+						if (entry.mixDuration > 0) {
+							timelineMode[i] = AnimationState.HOLD_MIX;
+							timelineDipMix[i] = next;
+							continue outer;
+						}
+						break;
+					}
+					timelineMode[i] = AnimationState.HOLD;
+				}
+			}
+		};
+		AnimationState.prototype.hasTimeline = function (entry, id) {
+			var timelines = entry.animation.timelines;
+			for (var i = 0, n = timelines.length; i < n; i++)
+				if (timelines[i].getPropertyId() == id)
+					return true;
+			return false;
 		};
 		AnimationState.prototype.getCurrent = function (trackIndex) {
 			if (trackIndex >= this.tracks.length)
@@ -1802,69 +1856,28 @@ var spine;
 		AnimationState.emptyAnimation = new spine.Animation("<empty>", [], 0);
 		AnimationState.SUBSEQUENT = 0;
 		AnimationState.FIRST = 1;
-		AnimationState.DIP = 2;
-		AnimationState.DIP_MIX = 3;
+		AnimationState.HOLD = 2;
+		AnimationState.HOLD_MIX = 3;
 		return AnimationState;
 	}());
 	spine.AnimationState = AnimationState;
 	var TrackEntry = (function () {
 		function TrackEntry() {
 			this.mixBlend = spine.MixBlend.replace;
-			this.timelineData = new Array();
-			this.timelineDipMix = new Array();
+			this.timelineMode = new Array();
+			this.timelineHoldMix = new Array();
 			this.timelinesRotation = new Array();
 		}
 		TrackEntry.prototype.reset = function () {
 			this.next = null;
 			this.mixingFrom = null;
+			this.mixingTo = null;
 			this.animation = null;
 			this.listener = null;
-			this.timelineData.length = 0;
-			this.timelineDipMix.length = 0;
+			this.timelineMode.length = 0;
+			this.timelineHoldMix.length = 0;
 			this.timelinesRotation.length = 0;
 		};
-		TrackEntry.prototype.setTimelineData = function (to, mixingToArray, propertyIDs) {
-			if (to != null)
-				mixingToArray.push(to);
-			var lastEntry = this.mixingFrom != null ? this.mixingFrom.setTimelineData(this, mixingToArray, propertyIDs) : this;
-			if (to != null)
-				mixingToArray.pop();
-			var mixingTo = mixingToArray;
-			var mixingToLast = mixingToArray.length - 1;
-			var timelines = this.animation.timelines;
-			var timelinesCount = this.animation.timelines.length;
-			var timelineData = spine.Utils.setArraySize(this.timelineData, timelinesCount);
-			this.timelineDipMix.length = 0;
-			var timelineDipMix = spine.Utils.setArraySize(this.timelineDipMix, timelinesCount);
-			outer: for (var i = 0; i < timelinesCount; i++) {
-				var id = timelines[i].getPropertyId();
-				if (!propertyIDs.add(id))
-					timelineData[i] = AnimationState.SUBSEQUENT;
-				else if (to == null || !to.hasTimeline(id))
-					timelineData[i] = AnimationState.FIRST;
-				else {
-					for (var ii = mixingToLast; ii >= 0; ii--) {
-						var entry = mixingTo[ii];
-						if (!entry.hasTimeline(id)) {
-							if (entry.mixDuration > 0) {
-								timelineData[i] = AnimationState.DIP_MIX;
-								timelineDipMix[i] = entry;
-								continue outer;
-							}
-						}
-					}
-					timelineData[i] = AnimationState.DIP;
-				}
-			}
-			return lastEntry;
-		};
-		TrackEntry.prototype.hasTimeline = function (id) {
-			var timelines = this.animation.timelines;
-			for (var i = 0, n = timelines.length; i < n; i++)
-				if (timelines[i].getPropertyId() == id)
-					return true;
-			return false;
-		};
 		TrackEntry.prototype.getAnimationTime = function () {
 			if (this.loop) {
 				var duration = this.animationEnd - this.animationStart;
@@ -7000,8 +7013,8 @@ var spine;
 						break;
 					}
 					var listeners = _this.listeners;
-					for (var i_16 = 0; i_16 < listeners.length; i_16++) {
-						listeners[i_16].down(_this.currTouch.x, _this.currTouch.y);
+					for (var i_17 = 0; i_17 < listeners.length; i_17++) {
+						listeners[i_17].down(_this.currTouch.x, _this.currTouch.y);
 					}
 					console.log("Start " + _this.currTouch.x + ", " + _this.currTouch.y);
 					_this.lastX = _this.currTouch.x;
@@ -7019,8 +7032,8 @@ var spine;
 							var y = _this.currTouch.y = touch.clientY - rect.top;
 							_this.touchesPool.free(_this.currTouch);
 							var listeners = _this.listeners;
-							for (var i_17 = 0; i_17 < listeners.length; i_17++) {
-								listeners[i_17].up(x, y);
+							for (var i_18 = 0; i_18 < listeners.length; i_18++) {
+								listeners[i_18].up(x, y);
 							}
 							console.log("End " + x + ", " + y);
 							_this.lastX = x;
@@ -7042,8 +7055,8 @@ var spine;
 							var y = _this.currTouch.y = touch.clientY - rect.top;
 							_this.touchesPool.free(_this.currTouch);
 							var listeners = _this.listeners;
-							for (var i_18 = 0; i_18 < listeners.length; i_18++) {
-								listeners[i_18].up(x, y);
+							for (var i_19 = 0; i_19 < listeners.length; i_19++) {
+								listeners[i_19].up(x, y);
 							}
 							console.log("End " + x + ", " + y);
 							_this.lastX = x;
@@ -7066,8 +7079,8 @@ var spine;
 							var x = touch.clientX - rect.left;
 							var y = touch.clientY - rect.top;
 							var listeners = _this.listeners;
-							for (var i_19 = 0; i_19 < listeners.length; i_19++) {
-								listeners[i_19].dragged(x, y);
+							for (var i_20 = 0; i_20 < listeners.length; i_20++) {
+								listeners[i_20].dragged(x, y);
 							}
 							console.log("Drag " + x + ", " + y);
 							_this.lastX = _this.currTouch.x = x;
@@ -8917,11 +8930,11 @@ var spine;
 						var nn = clip.worldVerticesLength;
 						var world = this.temp = spine.Utils.setArraySize(this.temp, nn, 0);
 						clip.computeWorldVertices(slot, 0, nn, world, 0, 2);
-						for (var i_20 = 0, n_2 = world.length; i_20 < n_2; i_20 += 2) {
-							var x = world[i_20];
-							var y = world[i_20 + 1];
-							var x2 = world[(i_20 + 2) % world.length];
-							var y2 = world[(i_20 + 3) % world.length];
+						for (var i_21 = 0, n_2 = world.length; i_21 < n_2; i_21 += 2) {
+							var x = world[i_21];
+							var y = world[i_21 + 1];
+							var x2 = world[(i_21 + 2) % world.length];
+							var y2 = world[(i_21 + 3) % world.length];
 							shapes.line(x, y, x2, y2);
 						}
 					}

File diff suppressed because it is too large
+ 0 - 0
spine-ts/build/spine-widget.js.map


+ 80 - 68
spine-ts/core/src/AnimationState.ts

@@ -33,8 +33,8 @@ module spine {
 		static emptyAnimation = new Animation("<empty>", [], 0);
 		static SUBSEQUENT = 0;
 		static FIRST = 1;
-		static DIP = 2;
-		static DIP_MIX = 3;
+		static HOLD = 2;
+		static HOLD_MIX = 3;
 
 		data: AnimationStateData;
 		tracks = new Array<TrackEntry>();
@@ -42,7 +42,6 @@ module spine {
 		listeners = new Array<AnimationStateListener2>();
 		queue = new EventQueue(this);
 		propertyIDs = new IntSet();
-		mixingTo = new Array<TrackEntry>();
 		animationsChanged = false;
 		timeScale = 1;
 
@@ -96,6 +95,7 @@ module spine {
 					// End mixing from entries once all have completed.
 					let from = current.mixingFrom;
 					current.mixingFrom = null;
+					if (from != null) from.mixingTo = null;
 					while (from != null) {
 						this.queue.end(from);
 						from = from.mixingFrom;
@@ -122,6 +122,7 @@ module spine {
 				// Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame).
 				if (from.totalAlpha == 0 || to.mixDuration == 0) {
 					to.mixingFrom = from.mixingFrom;
+					if (from.mixingFrom != null) from.mixingFrom.mixingTo = to;
 					to.interruptAlpha = from.interruptAlpha;
 					this.queue.end(from);
 				}
@@ -158,11 +159,11 @@ module spine {
 				let animationLast = current.animationLast, animationTime = current.getAnimationTime();
 				let timelineCount = current.animation.timelines.length;
 				let timelines = current.animation.timelines;
-				if (mix == 1 || blend == MixBlend.add) {
+				if (i == 0 && (mix == 1 || blend == MixBlend.add)) {
 					for (let ii = 0; ii < timelineCount; ii++)
 						timelines[ii].apply(skeleton, animationLast, animationTime, events, mix, blend, MixDirection.in);
 				} else {
-					let timelineData = current.timelineData;
+					let timelineMode = current.timelineMode;
 
 					let firstFrame = current.timelinesRotation.length == 0;
 					if (firstFrame) Utils.setArraySize(current.timelinesRotation, timelineCount << 1, null);
@@ -170,7 +171,7 @@ module spine {
 
 					for (let ii = 0; ii < timelineCount; ii++) {
 						let timeline = timelines[ii];
-						let timelineBlend = timelineData[ii] == AnimationState.SUBSEQUENT ? blend : MixBlend.setup;
+						let timelineBlend = timelineMode[ii] == AnimationState.SUBSEQUENT ? blend : MixBlend.setup;
 						if (timeline instanceof RotateTimeline) {
 							this.applyRotateTimeline(timeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, ii << 1, firstFrame);
 						} else {
@@ -209,13 +210,13 @@ module spine {
 			let animationLast = from.animationLast, animationTime = from.getAnimationTime();
 			let timelineCount = from.animation.timelines.length;
 			let timelines = from.animation.timelines;
-			let alphaDip = from.alpha * to.interruptAlpha, alphaMix = alphaDip * (1 - mix);
+			let alphaHold = from.alpha * to.interruptAlpha, alphaMix = alphaHold * (1 - mix);
 			if (blend == MixBlend.add) {
 				for (var i = 0; i < timelineCount; i++)
 					timelines[i].apply(skeleton, animationLast, animationTime, events, alphaMix, blend, MixDirection.out);
 			} else {
-				let timelineData = from.timelineData;
-				let timelineDipMix = from.timelineDipMix;
+				let timelineMode = from.timelineMode;
+				let timelineHoldMix = from.timelineHoldMix;
 
 				let firstFrame = from.timelinesRotation.length == 0;
 				if (firstFrame) Utils.setArraySize(from.timelinesRotation, timelineCount << 1, null);
@@ -226,7 +227,7 @@ module spine {
 					let timeline = timelines[i];
 					var timelineBlend: MixBlend;
 					var alpha = 0;
-					switch (timelineData[i]) {
+					switch (timelineMode[i]) {
 					case AnimationState.SUBSEQUENT:
 						if (!attachments && timeline instanceof AttachmentTimeline) continue;
 						if (!drawOrder && timeline instanceof DrawOrderTimeline) continue;
@@ -237,14 +238,14 @@ module spine {
 						timelineBlend = MixBlend.setup;
 						alpha = alphaMix;
 						break;
-					case AnimationState.DIP:
+					case AnimationState.HOLD:
 						timelineBlend = MixBlend.setup;
-						alpha = alphaDip;
+						alpha = alphaHold;
 						break;
 					default:
 						timelineBlend = MixBlend.setup;
-						let dipMix = timelineDipMix[i];
-						alpha = alphaDip * Math.max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
+						let holdMix = timelineHoldMix[i];
+						alpha = alphaHold * Math.max(0, 1 - holdMix.mixTime / holdMix.mixDuration);
 						break;
 					}
 					from.totalAlpha += alpha;
@@ -388,6 +389,7 @@ module spine {
 				if (from == null) break;
 				this.queue.end(from);
 				entry.mixingFrom = null;
+				entry.mixingTo = null;
 				entry = from;
 			}
 
@@ -403,6 +405,7 @@ module spine {
 			if (from != null) {
 				if (interrupt) this.queue.interrupt(from);
 				current.mixingFrom = from;
+				from.mixingTo = current;
 				current.mixTime = 0;
 
 				// Store the interrupted mix percentage.
@@ -520,6 +523,7 @@ module spine {
 			entry.trackIndex = trackIndex;
 			entry.animation = animation;
 			entry.loop = loop;
+			entry.holdPrevious = false;
 
 			entry.eventThreshold = 0;
 			entry.attachmentThreshold = 0;
@@ -556,17 +560,67 @@ module spine {
 		_animationsChanged () {
 			this.animationsChanged = false;
 
-			let propertyIDs = this.propertyIDs;
-			propertyIDs.clear();
-			let mixingTo = this.mixingTo;
+			this.propertyIDs.clear();
 
 			for (var i = 0, n = this.tracks.length; i < n; i++) {
 				let entry = this.tracks[i];
-				if (entry != null && (i == 0 || entry.mixBlend != MixBlend.add))
-					entry.setTimelineData(null, mixingTo, propertyIDs);
+				if (entry == null) continue;
+				while (entry.mixingFrom != null)
+					entry = entry.mixingFrom;
+
+				do {
+					if (entry.mixingFrom == null || entry.mixBlend != MixBlend.add) this.setTimelineModes(entry);
+					entry = entry.mixingTo;
+				} while (entry != null)
 			}
 		}
 
+		setTimelineModes (entry: TrackEntry) {
+			let to = entry.mixingTo;
+			let timelines = entry.animation.timelines;
+			let timelinesCount = entry.animation.timelines.length;
+			let timelineMode = Utils.setArraySize(entry.timelineMode, timelinesCount);
+			entry.timelineHoldMix.length = 0;
+			let timelineDipMix = Utils.setArraySize(entry.timelineHoldMix, timelinesCount);
+			let propertyIDs = this.propertyIDs;
+
+			if (to != null && to.holdPrevious) {
+				for (let i = 0; i < timelinesCount; i++) {
+					propertyIDs.add(timelines[i].getPropertyId());
+					timelineMode[i] = AnimationState.HOLD;
+				}
+				return;
+			}
+
+			outer:
+			for (var i = 0; i < timelinesCount; i++) {
+				let id = timelines[i].getPropertyId();
+				if (!propertyIDs.add(id))
+					timelineMode[i] = AnimationState.SUBSEQUENT;
+				else if (to == null || !this.hasTimeline(to, id))
+					timelineMode[i] = AnimationState.FIRST;
+				else {
+					for (let next = to.mixingTo; next != null; next = next.mixingTo) {
+						if (this.hasTimeline(next, id)) continue;
+						if (entry.mixDuration > 0) {
+							timelineMode[i] = AnimationState.HOLD_MIX;
+							timelineDipMix[i] = next;
+							continue outer;
+						}
+						break;
+					}
+					timelineMode[i] = AnimationState.HOLD;
+				}
+			}
+		}
+
+		hasTimeline (entry: TrackEntry, id: number) : boolean {
+			let timelines = entry.animation.timelines;
+			for (var i = 0, n = timelines.length; i < n; i++)
+				if (timelines[i].getPropertyId() == id) return true;
+			return false;
+		}
+
 		getCurrent (trackIndex: number) {
 			if (trackIndex >= this.tracks.length) return null;
 			return this.tracks[trackIndex];
@@ -594,73 +648,31 @@ module spine {
 
 	export class TrackEntry {
 		animation: Animation;
-		next: TrackEntry; mixingFrom: TrackEntry;
+		next: TrackEntry; mixingFrom: TrackEntry; mixingTo: TrackEntry;
 		listener: AnimationStateListener2;
 		trackIndex: number;
 		loop: boolean;
+		holdPrevious: boolean;
 		eventThreshold: number; attachmentThreshold: number; drawOrderThreshold: number;
 		animationStart: number; animationEnd: number; animationLast: number; nextAnimationLast: number;
 		delay: number; trackTime: number; trackLast: number; nextTrackLast: number; trackEnd: number; timeScale: number;
 		alpha: number; mixTime: number; mixDuration: number; interruptAlpha: number; totalAlpha: number;
 		mixBlend = MixBlend.replace;
-		timelineData = new Array<number>();
-		timelineDipMix = new Array<TrackEntry>();
+		timelineMode = new Array<number>();
+		timelineHoldMix = new Array<TrackEntry>();
 		timelinesRotation = new Array<number>();
 
 		reset () {
 			this.next = null;
 			this.mixingFrom = null;
+			this.mixingTo = null;
 			this.animation = null;
 			this.listener = null;
-			this.timelineData.length = 0;
-			this.timelineDipMix.length = 0;
+			this.timelineMode.length = 0;
+			this.timelineHoldMix.length = 0;
 			this.timelinesRotation.length = 0;
 		}
 
-		setTimelineData (to: TrackEntry, mixingToArray: Array<TrackEntry>, propertyIDs: IntSet) : TrackEntry {
-			if (to != null) mixingToArray.push(to);
-			let lastEntry = this.mixingFrom != null ? this.mixingFrom.setTimelineData(this, mixingToArray, propertyIDs) : this;
-			if (to != null) mixingToArray.pop();
-
-			let mixingTo = mixingToArray;
-			let mixingToLast = mixingToArray.length - 1;
-			let timelines = this.animation.timelines;
-			let timelinesCount = this.animation.timelines.length;
-			let timelineData = Utils.setArraySize(this.timelineData, timelinesCount);
-			this.timelineDipMix.length = 0;
-			let timelineDipMix = Utils.setArraySize(this.timelineDipMix, timelinesCount);
-
-			outer:
-			for (var i = 0; i < timelinesCount; i++) {
-				let id = timelines[i].getPropertyId();
-				if (!propertyIDs.add(id))
-					timelineData[i] = AnimationState.SUBSEQUENT;
-				else if (to == null || !to.hasTimeline(id))
-					timelineData[i] = AnimationState.FIRST;
-				else {
-					for (var ii = mixingToLast; ii >= 0; ii--) {
-						let entry = mixingTo[ii];
-						if (!entry.hasTimeline(id)) {
-							if (entry.mixDuration > 0) {
-								timelineData[i] = AnimationState.DIP_MIX;
-								timelineDipMix[i] = entry;
-								continue outer;
-							}
-						}
-					}
-					timelineData[i] = AnimationState.DIP;
-				}
-			}
-			return lastEntry;
-		}
-
-		hasTimeline (id: number) : boolean {
-			let timelines = this.animation.timelines;
-			for (var i = 0, n = timelines.length; i < n; i++)
-				if (timelines[i].getPropertyId() == id) return true;
-			return false;
-		}
-
 		getAnimationTime () {
 			if (this.loop) {
 				let duration = this.animationEnd - this.animationStart;

+ 3 - 0
spine-ts/webgl/tests/test-atlas-loading.html

@@ -26,6 +26,7 @@ function load () {
         var skeletonJson = new spine.SkeletonJson(new spine.AtlasAttachmentLoader(atlas));
         var skeletonData = skeletonJson.readSkeletonData(assetManager.get("spineboy-ess.json"));
         var animationStateData = new spine.AnimationStateData(skeletonData);
+        animationStateData.defaultMix = 0.3;
 
         skeleton = new spine.Skeleton(skeletonData);
         animationState = new spine.AnimationState(animationStateData);
@@ -38,6 +39,8 @@ function load () {
         renderer.camera.zoom = size.x > size.y ? size.x / canvas.width : size.y / canvas.height;
 
         animationState.setAnimation(0, "walk", true);
+        animationState.addAnimation(0, "run", true, 1);
+        animationState.addAnimation(0, "jump", true, 3);
 
         requestAnimationFrame(render);
     } else {

+ 113 - 0
spine-ts/webgl/tests/test-rig.html

@@ -0,0 +1,113 @@
+<html>
+<script src="../../build/spine-webgl.js"></script>
+<script src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
+<style>
+	* { margin: 0; padding: 0; }
+	body, html { height: 100% }
+	canvas { position: absolute; width: 100% ;height: 100%; }
+</style>
+<body>
+<div id="label" style="position: absolute; top: 0; left: 0; color: #fff; z-index: 10"></div>
+<canvas id="canvas" style="background: red;"></canvas>
+</body>
+<script>
+
+var FILE = "spineboy-ess";
+var ANIMATION = "idle";
+var SCALE = 0.5;
+
+var canvas, context, gl, renderer, input, assetManager;
+var skeletons = [];
+var timeKeeper;
+var label = document.getElementById("label");
+var updateMean = new spine.WindowedMean();
+var renderMean = new spine.WindowedMean();
+
+function init() {
+	canvas = document.getElementById("canvas");
+	canvas.width = canvas.clientWidth; canvas.height = canvas.clientHeight;
+	context = new spine.webgl.ManagedWebGLRenderingContext(canvas, { alpha: false });
+	gl = context.gl;
+
+	renderer = new spine.webgl.SceneRenderer(canvas, context);
+	assetManager = new spine.webgl.AssetManager(context, "../example/assets/");
+	input = new spine.webgl.Input(canvas);
+
+	assetManager.loadTextureAtlas(FILE.replace("-pro", "").replace("-ess", "") + ".atlas");
+	assetManager.loadText(FILE + ".json");
+
+	timeKeeper = new spine.TimeKeeper();
+	requestAnimationFrame(load);
+}
+
+alert("blah");
+
+var run = true;
+
+function switchAnimation() {
+	var skel = skeletons[0];
+	skel.state.setAnimation(0, run ? "run" : "idle", true);
+	run = !run;
+	setTimeout(switchAnimation, 300);
+}
+
+function load() {
+	timeKeeper.update();
+	if (assetManager.isLoadingComplete()) {
+		var atlas = assetManager.get(FILE.replace("-pro", "").replace("-ess", "") + ".atlas");
+		var atlasLoader = new spine.AtlasAttachmentLoader(atlas);
+		var skeletonJson = new spine.SkeletonJson(atlasLoader);
+		skeletonJson.scale = SCALE;
+		var skeletonData = skeletonJson.readSkeletonData(JSON.parse(assetManager.get(FILE + ".json")));
+
+		skeleton = new spine.Skeleton(skeletonData);
+		var stateData = new spine.AnimationStateData(skeleton.data);
+		state = new spine.AnimationState(stateData);
+		stateData.defaultMix = 0;
+
+		state.setAnimation(0, ANIMATION, true);
+		skeletons.push({ skeleton: skeleton, state: state });
+
+		setTimeout(switchAnimation, 300);
+
+		requestAnimationFrame(render);
+	} else {
+		requestAnimationFrame(load);
+	}
+}
+
+function render() {
+	var start = Date.now()
+	timeKeeper.update();
+	var delta = timeKeeper.delta;
+
+	for (var i = 0; i < skeletons.length; i++) {
+		var state = skeletons[i].state;
+		var skeleton = skeletons[i].skeleton;
+		state.update(delta);
+		state.apply(skeleton);
+		skeleton.updateWorldTransform();
+	}
+	updateMean.addValue(Date.now() - start);
+	start = Date.now();
+
+	gl.clearColor(0.2, 0.2, 0.2, 1);
+	gl.clear(gl.COLOR_BUFFER_BIT);
+
+	renderer.resize(spine.webgl.ResizeMode.Fit);
+	renderer.begin();
+	for (var i = 0; i < skeletons.length; i++) {
+		var skeleton = skeletons[i].skeleton;
+		renderer.drawSkeleton(skeleton, false);
+	}
+	renderer.end();
+
+	requestAnimationFrame(render)
+	renderMean.addValue(Date.now() - start);
+	label.innerHTML = ("Update time: " + Number(updateMean.getMean()).toFixed(2) + " ms\n" +
+					   "Render time: " + Number(renderMean.getMean()).toFixed(2) + " ms\n");
+}
+
+init();
+</script>
+</html>

+ 5 - 0
spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonDataAssetInspector.cs

@@ -51,6 +51,7 @@ namespace Spine.Unity.Editor {
 		internal static bool showAttachments = false;
 
 		SerializedProperty atlasAssets, skeletonJSON, scale, fromAnimation, toAnimation, duration, defaultMix;
+		SerializedProperty blendModeMaterials;
 		#if SPINE_TK2D
 		SerializedProperty spriteCollection;
 		#endif
@@ -100,6 +101,8 @@ namespace Spine.Unity.Editor {
 			duration = serializedObject.FindProperty("duration");
 			defaultMix = serializedObject.FindProperty("defaultMix");
 
+			blendModeMaterials = serializedObject.FindProperty("blendModeMaterials");
+
 			#if SPINE_SKELETON_MECANIM
 			controller = serializedObject.FindProperty("controller");
 			#endif
@@ -315,6 +318,8 @@ namespace Spine.Unity.Editor {
 
 			if (atlasAssets.arraySize == 0)
 				EditorGUILayout.HelpBox("AtlasAssets array is empty. Skeleton's attachments will load without being mapped to images.", MessageType.Info);
+
+			EditorGUILayout.PropertyField(blendModeMaterials);
 		}
 
 		void HandleAtlasAssetsNulls () {

+ 54 - 10
spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineEditorUtilities.cs

@@ -179,7 +179,7 @@ namespace Spine.Unity.Editor {
 		static void Initialize () {
 			Preferences.Load();
 
-			DirectoryInfo rootDir = new DirectoryInfo(Application.dataPath);
+			var rootDir = new DirectoryInfo(Application.dataPath);
 			FileInfo[] files = rootDir.GetFiles("SpineEditorUtilities.cs", SearchOption.AllDirectories);
 			editorPath = Path.GetDirectoryName(files[0].FullName.Replace("\\", "/").Replace(Application.dataPath, "Assets"));
 			editorGUIPath = editorPath + "/GUI";
@@ -194,15 +194,27 @@ namespace Spine.Unity.Editor {
 			EditorApplication.hierarchyWindowItemOnGUI += HierarchyHandler.HandleDragAndDrop;
 
 			// Hierarchy Icons
-			#if UNITY_2017_2_OR_NEWER
+#if UNITY_2017_2_OR_NEWER
 			EditorApplication.playModeStateChanged -= HierarchyHandler.IconsOnPlaymodeStateChanged;
 			EditorApplication.playModeStateChanged += HierarchyHandler.IconsOnPlaymodeStateChanged;
 			HierarchyHandler.IconsOnPlaymodeStateChanged(PlayModeStateChange.EnteredEditMode);
-			#else
+#else
 			EditorApplication.playmodeStateChanged -= HierarchyHandler.IconsOnPlaymodeStateChanged;
 			EditorApplication.playmodeStateChanged += HierarchyHandler.IconsOnPlaymodeStateChanged;
 			HierarchyHandler.IconsOnPlaymodeStateChanged();
-			#endif
+#endif
+
+			// Data Refresh Edit Mode.
+			// This prevents deserialized SkeletonData from persisting from play mode to edit mode.
+#if UNITY_2017_2_OR_NEWER
+			EditorApplication.playModeStateChanged -= DataReloadHandler.OnPlaymodeStateChanged;
+			EditorApplication.playModeStateChanged += DataReloadHandler.OnPlaymodeStateChanged;
+			DataReloadHandler.OnPlaymodeStateChanged(PlayModeStateChange.EnteredEditMode);
+#else
+			EditorApplication.playmodeStateChanged -= DataReloadHandler.OnPlaymodeStateChanged;
+			EditorApplication.playmodeStateChanged += DataReloadHandler.OnPlaymodeStateChanged;
+			DataReloadHandler.OnPlaymodeStateChanged();
+#endif
 
 			initialized = true;
 		}
@@ -214,11 +226,11 @@ namespace Spine.Unity.Editor {
 		#endregion
 
 		public static class Preferences {
-			#if SPINE_TK2D
+#if SPINE_TK2D
 			const float DEFAULT_DEFAULT_SCALE = 1f;
-			#else
+#else
 			const float DEFAULT_DEFAULT_SCALE = 0.01f;
-			#endif
+#endif
 			const string DEFAULT_SCALE_KEY = "SPINE_DEFAULT_SCALE";
 			public static float defaultScale = DEFAULT_DEFAULT_SCALE;
 
@@ -268,11 +280,11 @@ namespace Spine.Unity.Editor {
 				showHierarchyIcons = EditorGUILayout.Toggle(new GUIContent("Show Hierarchy Icons", "Show relevant icons on GameObjects with Spine Components on them. Disable this if you have large, complex scenes."), showHierarchyIcons);
 				if (EditorGUI.EndChangeCheck()) {
 					EditorPrefs.SetBool(SHOW_HIERARCHY_ICONS_KEY, showHierarchyIcons);
-					#if UNITY_2017_2_OR_NEWER
+#if UNITY_2017_2_OR_NEWER
 					HierarchyHandler.IconsOnPlaymodeStateChanged(PlayModeStateChange.EnteredEditMode);
-					#else
+#else
 					HierarchyHandler.IconsOnPlaymodeStateChanged();
-					#endif
+#endif
 				}
 
 				EditorGUILayout.Separator();
@@ -333,6 +345,38 @@ namespace Spine.Unity.Editor {
 			}
 		}
 
+		public static class DataReloadHandler {
+#if UNITY_2017_2_OR_NEWER
+			internal static void OnPlaymodeStateChanged (PlayModeStateChange stateChange) {
+#else
+			internal static void OnPlaymodeStateChanged () {
+#endif
+				ReloadAllActiveSkeletons();
+			}
+
+			static void ReloadAllActiveSkeletons () {
+				var skeletonDataAssetsToReload = new HashSet<SkeletonDataAsset>();
+
+				var activeSkeletonRenderers = GameObject.FindObjectsOfType<SkeletonRenderer>();
+				foreach (var sr in activeSkeletonRenderers) {
+					var skeletonDataAsset = sr.skeletonDataAsset;
+					if (skeletonDataAsset != null) skeletonDataAssetsToReload.Add(skeletonDataAsset);
+				}
+
+				var activeSkeletonGraphics = GameObject.FindObjectsOfType<SkeletonGraphic>();
+				foreach (var sg in activeSkeletonGraphics) {
+					var skeletonDataAsset = sg.skeletonDataAsset;
+					if (skeletonDataAsset != null) skeletonDataAssetsToReload.Add(skeletonDataAsset);
+				}
+
+				foreach (var sda in skeletonDataAssetsToReload)
+					sda.Clear();
+
+				foreach (var sr in activeSkeletonRenderers)	sr.Initialize(true);
+				foreach (var sg in activeSkeletonGraphics) sg.Initialize(true);
+			}
+		}
+
 		public static class AssetUtility {
 			public const string SkeletonDataSuffix = "_SkeletonData";
 			public const string AtlasSuffix = "_Atlas";

+ 120 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/BlendModeMaterialsAsset.cs

@@ -0,0 +1,120 @@
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, Esoteric Software
+ * All rights reserved.
+ *
+ * You are granted a perpetual, non-exclusive, non-sublicensable, and
+ * non-transferable license to use, install, execute, and perform the Spine
+ * Runtimes software and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * or other intellectual property or proprietary rights notices on or in the
+ * Software, including any copy thereof. Redistributions in binary or source
+ * form must include this license and terms.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+
+using Spine;
+using Spine.Unity;
+
+namespace Spine.Unity {
+	[CreateAssetMenu(menuName = "Spine/Blend Mode Materials Asset", order = 200)]
+	public class BlendModeMaterialsAsset : SkeletonDataModifierAsset {
+		public Material multiplyMaterialTemplate;
+		public Material screenMaterialTemplate;
+		public Material additiveMaterialTemplate;
+
+		public bool applyAdditiveMaterial;
+
+		public override void Apply (SkeletonData skeletonData) {
+			ApplyMaterials(skeletonData, multiplyMaterialTemplate, screenMaterialTemplate, additiveMaterialTemplate, applyAdditiveMaterial);
+		}
+
+		public static void ApplyMaterials (SkeletonData skeletonData, Material multiplyTemplate, Material screenTemplate, Material additiveTemplate, bool includeAdditiveSlots) {
+			if (skeletonData == null) throw new ArgumentNullException("skeletonData");
+
+			var atlasPageMaterialCache = new Dictionary<KeyValuePair<AtlasPage, Material>, AtlasPage>();
+			var attachmentBuffer = new List<Attachment>();
+			var slotsItems = skeletonData.Slots.Items;
+			for (int i = 0, slotCount = skeletonData.Slots.Count; i < slotCount; i++) {
+				var slot = slotsItems[i];
+				if (slot.blendMode == BlendMode.Normal) continue;
+				if (!includeAdditiveSlots && slot.blendMode == BlendMode.Additive) continue;
+
+				attachmentBuffer.Clear();
+				foreach (var skin in skeletonData.Skins)
+					skin.FindAttachmentsForSlot(i, attachmentBuffer);
+
+				Material templateMaterial = null;
+				switch (slot.blendMode) {
+					case BlendMode.Multiply:
+						templateMaterial = multiplyTemplate;
+						break;
+					case BlendMode.Screen:
+						templateMaterial = screenTemplate;
+						break;
+					case BlendMode.Additive:
+						templateMaterial = additiveTemplate;
+						break;
+				}
+				if (templateMaterial == null) continue;
+
+				foreach (var attachment in attachmentBuffer) {
+					var renderableAttachment = attachment as IHasRendererObject;
+					if (renderableAttachment != null) {
+						renderableAttachment.RendererObject = AtlasRegionCloneWithMaterial((AtlasRegion)renderableAttachment.RendererObject, templateMaterial, atlasPageMaterialCache);
+					}
+				}
+			}
+			//atlasPageMaterialCache.Clear();
+			//attachmentBuffer.Clear();
+		}
+
+		static AtlasRegion AtlasRegionCloneWithMaterial (AtlasRegion originalRegion, Material materialTemplate, Dictionary<KeyValuePair<AtlasPage, Material>, AtlasPage> cache) {
+			var newRegion = originalRegion.Clone();
+			newRegion.page = GetAtlasPageWithMaterial(originalRegion.page, materialTemplate, cache);
+			return newRegion;
+		}
+
+		static AtlasPage GetAtlasPageWithMaterial (AtlasPage originalPage, Material materialTemplate, Dictionary<KeyValuePair<AtlasPage, Material>, AtlasPage> cache) {
+			if (originalPage == null) throw new ArgumentNullException("originalPage");
+
+			AtlasPage newPage = null;
+			var key = new KeyValuePair<AtlasPage, Material>(originalPage, materialTemplate);
+			cache.TryGetValue(key, out newPage);
+
+			if (newPage == null) {
+				newPage = originalPage.Clone();
+				var originalMaterial = originalPage.rendererObject as Material;
+				newPage.rendererObject = new Material(materialTemplate) {
+					name = originalMaterial.name + " " + materialTemplate.name,
+					mainTexture = originalMaterial.mainTexture
+				};
+				cache.Add(key, newPage);
+			}
+
+			return newPage;
+		}
+	
+	}
+
+}

+ 18 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/BlendModeMaterialsAsset.cs.meta

@@ -0,0 +1,18 @@
+fileFormatVersion: 2
+guid: 12b0b98acbcda44468a7ae4e35000abe
+timeCreated: 1536404384
+licenseType: Pro
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences:
+  - multiplyMaterialTemplate: {fileID: 2100000, guid: 53bf0ab317d032d418cf1252d68f51df,
+      type: 2}
+  - screenMaterialTemplate: {fileID: 2100000, guid: 73f0f46d3177c614baf0fa48d646a9be,
+      type: 2}
+  - additiveMaterialTemplate: {fileID: 2100000, guid: 4deba332d47209e4780b3c5fcf0e3745,
+      type: 2}
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 15 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SkeletonDataAsset.cs

@@ -29,8 +29,10 @@
  *****************************************************************************/
 
 using System;
+using System.Collections.Generic;
 using System.IO;
 using UnityEngine;
+
 using Spine;
 
 namespace Spine.Unity {
@@ -39,6 +41,7 @@ namespace Spine.Unity {
 	public class SkeletonDataAsset : ScriptableObject {
 		#region Inspector
 		public AtlasAssetBase[] atlasAssets = new AtlasAssetBase[0];
+
 		#if SPINE_TK2D
 		public tk2dSpriteCollectionData spriteCollection;
 		public float scale = 1f;
@@ -46,6 +49,10 @@ namespace Spine.Unity {
 		public float scale = 0.01f;
 		#endif
 		public TextAsset skeletonJSON;
+
+		[Tooltip("Use SkeletonDataModifierAssets to apply changes to the SkeletonData after being loaded, such as apply blend mode Materials to Attachments under slots with special blend modes.")]
+		public List<SkeletonDataModifierAsset> skeletonDataModifiers = new List<SkeletonDataModifierAsset>();
+
 		[SpineAnimation(includeNone: false)]
 		public string[] fromAnimation = new string[0];
 		[SpineAnimation(includeNone: false)]
@@ -87,6 +94,7 @@ namespace Spine.Unity {
 		}
 		#endregion
 
+		/// <summary>Clears the loaded SkeletonData and AnimationStateData. Use this to force a reload for the next time GetSkeletonData is called.</summary>
 		public void Clear () {
 			skeletonData = null;
 			stateData = null;
@@ -161,6 +169,12 @@ namespace Spine.Unity {
 
 			}
 
+			if (skeletonDataModifiers != null) {
+				foreach (var m in skeletonDataModifiers) {
+					if (m != null) m.Apply(loadedSkeletonData);
+				}
+			}
+
 			this.InitializeWithData(loadedSkeletonData);
 
 			return skeletonData;
@@ -218,6 +232,7 @@ namespace Spine.Unity {
 			GetSkeletonData(false);
 			return stateData;
 		}
+
 	}
 
 }

+ 40 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SkeletonDataModifierAsset.cs

@@ -0,0 +1,40 @@
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, Esoteric Software
+ * All rights reserved.
+ *
+ * You are granted a perpetual, non-exclusive, non-sublicensable, and
+ * non-transferable license to use, install, execute, and perform the Spine
+ * Runtimes software and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes or (b) remove,
+ * delete, alter, or obscure any trademarks or any copyright, trademark, patent,
+ * or other intellectual property or proprietary rights notices on or in the
+ * Software, including any copy thereof. Redistributions in binary or source
+ * form must include this license and terms.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace Spine.Unity {
+	/// <summary>Can be stored by SkeletonDataAsset to automatically apply modifications to loaded SkeletonData.</summary>
+	public abstract class SkeletonDataModifierAsset : ScriptableObject {
+		public abstract void Apply (SkeletonData skeletonData);
+	}
+}

+ 12 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SkeletonDataModifierAsset.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 79a44aba1f342f440965874280b4c318
+timeCreated: 1536412736
+licenseType: Pro
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 8
spine-unity/Assets/Spine/Runtime/spine-unity/Modules/AttachmentTools/AttachmentTools.cs

@@ -69,7 +69,7 @@ namespace Spine.Unity.Modules.AttachmentTools {
 
 		/// <summary>Sets the region (image) of a RegionAttachment</summary>
 		public static void SetRegion (this RegionAttachment attachment, AtlasRegion region, bool updateOffset = true) {
-			if (region == null) throw new System.ArgumentNullException("region");
+			if (region == null) throw new System.ArgumentNullException("region"); 
 
 			// (AtlasAttachmentLoader.cs)
 			attachment.RendererObject = region;
@@ -86,7 +86,7 @@ namespace Spine.Unity.Modules.AttachmentTools {
 
 		/// <summary>Sets the region (image) of a MeshAttachment</summary>
 		public static void SetRegion (this MeshAttachment attachment, AtlasRegion region, bool updateUVs = true) {
-			if (region == null) throw new System.ArgumentNullException("region");
+			if (region == null) throw new System.ArgumentNullException("region"); 
 
 			// (AtlasAttachmentLoader.cs)
 			attachment.RendererObject = region;
@@ -395,7 +395,7 @@ namespace Spine.Unity.Modules.AttachmentTools {
 		/// Fills the outputAttachments list with new attachment objects based on the attachments in sourceAttachments, but mapped to a new single texture using the same material.</summary>
 		/// <param name="sourceAttachments">The list of attachments to be repacked.</param>
 		/// <param name = "outputAttachments">The List(Attachment) to populate with the newly created Attachment objects.</param>
-		///
+		/// 
 		/// <param name="materialPropertySource">May be null. If no Material property source is provided, no special </param>
 		public static void GetRepackedAttachments (List<Attachment> sourceAttachments, List<Attachment> outputAttachments, Material materialPropertySource, out Material outputMaterial, out Texture2D outputTexture, int maxAtlasSize = 1024, int padding = 2, TextureFormat textureFormat = SpineTextureFormat, bool mipmaps = UseMipMaps, string newAssetName = "Repacked Attachments", bool clearCache = false, bool useOriginalNonrenderables = true) {
 			if (sourceAttachments == null) throw new System.ArgumentNullException("sourceAttachments");
@@ -413,7 +413,7 @@ namespace Spine.Unity.Modules.AttachmentTools {
 			int newRegionIndex = 0;
 			for (int i = 0, n = sourceAttachments.Count; i < n; i++) {
 				var originalAttachment = sourceAttachments[i];
-
+				
 				if (IsRenderable(originalAttachment)) {
 					var newAttachment = originalAttachment.GetClone(true);
 					var region = newAttachment.GetRegion();
@@ -524,7 +524,7 @@ namespace Spine.Unity.Modules.AttachmentTools {
 					newSkin.AddAttachment(originalKey.slotIndex, originalKey.name, newAttachment);
 				} else {
 					newSkin.AddAttachment(originalKey.slotIndex, originalKey.name, useOriginalNonrenderables ? originalAttachment : originalAttachment.GetClone(true));
-				}
+				}	
 			}
 
 			// Fill a new texture with the collected attachment textures.
@@ -657,7 +657,7 @@ namespace Spine.Unity.Modules.AttachmentTools {
 		/// Returns a Rect of the AtlasRegion according to Spine texture coordinates. (x-right, y-down)</summary>
 		static Rect GetSpineAtlasRect (this AtlasRegion region, bool includeRotate = true) {
 			if (includeRotate && region.rotate)
-				return new Rect(region.x, region.y, region.height, region.width);
+				return new Rect(region.x, region.y, region.height, region.width);				
 			else
 				return new Rect(region.x, region.y, region.width, region.height);
 		}
@@ -684,7 +684,7 @@ namespace Spine.Unity.Modules.AttachmentTools {
 
 		/// <summary>
 		/// Creates a new Spine AtlasRegion according to a Unity UV Rect (x-right, y-up, uv-normalized).</summary>
-		static AtlasRegion UVRectToAtlasRegion (Rect uvRect, string name, AtlasPage page, float offsetX, float offsetY, bool rotate) {
+		static AtlasRegion UVRectToAtlasRegion (Rect uvRect, string name, AtlasPage page, float offsetX, float offsetY, bool rotate) {			
 			var tr  = UVRectToTextureRect(uvRect, page.width, page.height);
 			var rr = tr.SpineUnityFlipRect(page.height);
 
@@ -875,7 +875,7 @@ namespace Spine.Unity.Modules.AttachmentTools {
 		public static Attachment GetClone (this Attachment o, bool cloneMeshesAsLinked) {
 			var regionAttachment = o as RegionAttachment;
 			if (regionAttachment != null)
-				return regionAttachment.GetClone();
+				return regionAttachment.GetClone();			
 
 			var meshAttachment = o as MeshAttachment;
 			if (meshAttachment != null)

+ 9 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/BlendModes.meta

@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: baf1d09e18b500d41a714f6207ddda2d
+folderAsset: yes
+timeCreated: 1536402197
+licenseType: Pro
+DefaultImporter:
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/BlendModes/Default BlendModeMaterials.asset


+ 9 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/BlendModes/Default BlendModeMaterials.asset.meta

@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: 22c0225612a65ee4fb15bad49f644762
+timeCreated: 1536404361
+licenseType: Pro
+NativeFormatImporter:
+  mainObjectFileID: 11400000
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/BlendModes/SkeletonPMAAdditive.mat


+ 9 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/BlendModes/SkeletonPMAAdditive.mat.meta

@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: 4deba332d47209e4780b3c5fcf0e3745
+timeCreated: 1496447909
+licenseType: Free
+NativeFormatImporter:
+  mainObjectFileID: 2100000
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 0 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Modules/SlotBlendModes/SkeletonPMAMultiply.mat → spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/BlendModes/SkeletonPMAMultiply.mat


+ 0 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Modules/SlotBlendModes/SkeletonPMAMultiply.mat.meta → spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/BlendModes/SkeletonPMAMultiply.mat.meta


+ 0 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Modules/SlotBlendModes/SkeletonPMAScreen.mat → spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/BlendModes/SkeletonPMAScreen.mat


+ 0 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Modules/SlotBlendModes/SkeletonPMAScreen.mat.meta → spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/BlendModes/SkeletonPMAScreen.mat.meta


+ 106 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/BlendModes/Spine-Skeleton-PMA-Additive.shader

@@ -0,0 +1,106 @@
+// Spine/Skeleton PMA Screen
+// - single color multiply tint
+// - unlit
+// - Premultiplied alpha Multiply blending
+// - No depth, no backface culling, no fog.
+// - ShadowCaster pass
+
+Shader "Spine/Blend Modes/Skeleton PMA Additive" {
+	Properties {
+		_Color ("Tint Color", Color) = (1,1,1,1)
+		[NoScaleOffset] _MainTex ("MainTex", 2D) = "black" {}
+		[Toggle(_STRAIGHT_ALPHA_INPUT)] _StraightAlphaInput("Straight Alpha Texture", Int) = 0
+		_Cutoff ("Shadow alpha cutoff", Range(0,1)) = 0.1
+	}
+
+	SubShader {
+		Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
+		LOD 100
+
+		Fog { Mode Off }
+		Cull Off
+		ZWrite Off
+		Blend One One
+		Lighting Off
+
+		Pass {
+			CGPROGRAM
+			#pragma shader_feature _ _STRAIGHT_ALPHA_INPUT
+			#pragma vertex vert
+			#pragma fragment frag
+			#include "UnityCG.cginc"
+			uniform sampler2D _MainTex;
+			uniform float4 _Color;
+
+			struct VertexInput {
+				float4 vertex : POSITION;
+				float2 uv : TEXCOORD0;
+				float4 vertexColor : COLOR;
+			};
+
+			struct VertexOutput {
+				float4 pos : SV_POSITION;
+				float2 uv : TEXCOORD0;
+				float4 vertexColor : COLOR;
+			};
+
+			VertexOutput vert (VertexInput v) {
+				VertexOutput o;
+				o.pos = UnityObjectToClipPos(v.vertex);
+				o.uv = v.uv;
+				o.vertexColor = v.vertexColor * float4(_Color.rgb * _Color.a, _Color.a); // Combine a PMA version of _Color with vertexColor.
+				return o;
+			}
+
+			float4 frag (VertexOutput i) : COLOR {
+				float4 texColor = tex2D(_MainTex, i.uv);
+
+				#if defined(_STRAIGHT_ALPHA_INPUT)
+				texColor.rgb *= texColor.a;
+				#endif
+
+				return (texColor * i.vertexColor);
+			}
+			ENDCG
+		}
+
+		Pass {
+			Name "Caster"
+			Tags { "LightMode"="ShadowCaster" }
+			Offset 1, 1
+
+			ZWrite On
+			ZTest LEqual
+
+			CGPROGRAM
+			#pragma vertex vert
+			#pragma fragment frag
+			#pragma multi_compile_shadowcaster
+			#pragma fragmentoption ARB_precision_hint_fastest
+			#include "UnityCG.cginc"
+			struct v2f { 
+				V2F_SHADOW_CASTER;
+				float2 uv : TEXCOORD1;
+			};
+
+			uniform float4 _MainTex_ST;
+
+			v2f vert (appdata_base v) {
+				v2f o;
+				TRANSFER_SHADOW_CASTER(o)
+				o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
+				return o;
+			}
+
+			uniform sampler2D _MainTex;
+			uniform fixed _Cutoff;
+
+			float4 frag (v2f i) : COLOR {
+				fixed4 texcol = tex2D(_MainTex, i.uv);
+				clip(texcol.a - _Cutoff);
+				SHADOW_CASTER_FRAGMENT(i)
+			}
+			ENDCG
+		}
+	}
+}

+ 9 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/BlendModes/Spine-Skeleton-PMA-Additive.shader.meta

@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: 53efa1d97f5d9f74285d4330cda14e36
+timeCreated: 1496446742
+licenseType: Free
+ShaderImporter:
+  defaultTextures: []
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 2 - 4
spine-unity/Assets/Spine/Runtime/spine-unity/Modules/SlotBlendModes/Spine-Skeleton-PMA-Multiply.shader → spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/BlendModes/Spine-Skeleton-PMA-Multiply.shader

@@ -1,13 +1,11 @@
-// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
-
-// Spine/Skeleton PMA Multiply
+// Spine/Skeleton PMA Multiply
 // - single color multiply tint
 // - unlit
 // - Premultiplied alpha Multiply blending
 // - No depth, no backface culling, no fog.
 // - ShadowCaster pass
 
-Shader "Spine/Skeleton PMA Multiply" {
+Shader "Spine/Blend Modes/Skeleton PMA Multiply" {
 	Properties {
 		_Color ("Tint Color", Color) = (1,1,1,1)
 		[NoScaleOffset] _MainTex ("MainTex", 2D) = "black" {}

+ 0 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Modules/SlotBlendModes/Spine-Skeleton-PMA-Multiply.shader.meta → spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/BlendModes/Spine-Skeleton-PMA-Multiply.shader.meta


+ 2 - 2
spine-unity/Assets/Spine/Runtime/spine-unity/Modules/SlotBlendModes/Spine-Skeleton-PMA-Screen.shader → spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/BlendModes/Spine-Skeleton-PMA-Screen.shader

@@ -1,11 +1,11 @@
-// Spine/Skeleton PMA Multiply
+// Spine/Skeleton PMA Screen
 // - single color multiply tint
 // - unlit
 // - Premultiplied alpha Multiply blending
 // - No depth, no backface culling, no fog.
 // - ShadowCaster pass
 
-Shader "Spine/Skeleton PMA Screen" {
+Shader "Spine/Blend Modes/Skeleton PMA Screen" {
 	Properties {
 		_Color ("Tint Color", Color) = (1,1,1,1)
 		[NoScaleOffset] _MainTex ("MainTex", 2D) = "black" {}

+ 0 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Modules/SlotBlendModes/Spine-Skeleton-PMA-Screen.shader.meta → spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/BlendModes/Spine-Skeleton-PMA-Screen.shader.meta


+ 24 - 7
spine-unity/Assets/Spine/Runtime/spine-unity/SkeletonExtensions.cs

@@ -98,6 +98,27 @@ namespace Spine.Unity {
 		}
 		#endregion
 
+		#region Skeleton
+		/// <summary>Sets the Skeleton's local scale using a UnityEngine.Vector2. If only individual components need to be set, set Skeleton.ScaleX or Skeleton.ScaleY.</summary>
+		public static void SetLocalScale (this Skeleton skeleton, Vector2 scale) {
+			skeleton.scaleX = scale.x;
+			skeleton.scaleY = scale.y;
+		}
+
+		/// <summary>Gets the internal bone matrix as a Unity bonespace-to-skeletonspace transformation matrix.</summary>
+		public static Matrix4x4 GetMatrix4x4 (this Bone bone) {
+			return new Matrix4x4 {
+				m00 = bone.a,
+				m01 = bone.b,
+				m03 = bone.worldX,
+				m10 = bone.c,
+				m11 = bone.d,
+				m13 = bone.worldY,
+				m33 = 1
+			};
+		}
+		#endregion
+
 		#region Bone
 		/// <summary>Sets the bone's (local) X and Y according to a Vector2</summary>
 		public static void SetPosition (this Bone bone, Vector2 position) {
@@ -149,13 +170,9 @@ namespace Spine.Unity {
 			return new Quaternion(0, 0, Mathf.Sin(halfRotation), Mathf.Cos(halfRotation));
 		}
 
-		/// <summary>Gets the internal bone matrix as a Unity bonespace-to-skeletonspace transformation matrix.</summary>
-		public static Matrix4x4 GetMatrix4x4 (this Bone bone) {
-			return new Matrix4x4 {
-				m00 = bone.a, m01 = bone.b, m03 = bone.worldX,
-				m10 = bone.c, m11 = bone.d, m13 = bone.worldY,
-				m33 = 1
-			};
+		/// <summary>Returns the Skeleton's local scale as a UnityEngine.Vector2. If only individual components are needed, use Skeleton.ScaleX or Skeleton.ScaleY.</summary>
+		public static Vector2 GetLocalScale (this Skeleton skeleton) {
+			return new Vector2(skeleton.scaleX, skeleton.scaleY);
 		}
 
 		/// <summary>Calculates a 2x2 Transformation Matrix that can convert a skeleton-space position to a bone-local position.</summary>

+ 2 - 8
spine-unity/Assets/Spine/Runtime/spine-unity/SkeletonUtility/SkeletonUtility.cs

@@ -103,6 +103,7 @@ namespace Spine.Unity {
 			int floatCount = floats.Length;
 
 			Bounds bounds = new Bounds();
+
 			bounds.center = new Vector3(floats[0], floats[1], 0);
 			for (int i = 2; i < floatCount; i += 2)
 				bounds.Encapsulate(new Vector3(floats[i], floats[i + 1], 0));
@@ -122,14 +123,7 @@ namespace Spine.Unity {
 		void Update () {
 			var skeleton = skeletonRenderer.skeleton;
 			if (boneRoot != null && skeleton != null) {
-				Vector3 flipScale = Vector3.one;
-				if (skeleton.scaleX < 0)
-					flipScale.x = -1;
-
-				if (skeleton.scaleY < 0)
-					flipScale.y = -1;
-
-				boneRoot.localScale = flipScale;
+				boneRoot.localScale = new Vector3(skeleton.scaleX, skeleton.scaleY, 1f);
 			}
 		}
 

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