[as3] Ported animation state changes, see #904

This commit is contained in:
badlogic 2017-05-17 15:27:57 +02:00
parent 21e05ea1b0
commit 03e08dc74f
5 changed files with 131 additions and 141 deletions

View File

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

View File

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