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