Moved track changes to update, so AnimationState can be applied to multiple skeletons without side effects.

Also:
- Moved queue drains outside of update loop.
- Better naming.
- Dirty flag after animations have been changed to avoid computing timelinesFirst more times than needed.
- Don't use timelinesFirst when mix == 1.
This commit is contained in:
NathanSweet 2016-08-22 21:33:04 +02:00
parent ef2a440655
commit 4c2d2b0c0a
3 changed files with 126 additions and 98 deletions

View File

@ -167,9 +167,11 @@ public class AnimationStateTest {
expect(1, "start", 0.1f, 1.1f), // expect(1, "start", 0.1f, 1.1f), //
expect(0, "interrupt", 1.1f, 1.1f), // expect(0, "interrupt", 1.1f, 1.1f), //
expect(0, "end", 1.1f, 1.1f), //
expect(1, "event 0", 0.1f, 1.1f), // expect(1, "event 0", 0.1f, 1.1f), //
expect(0, "end", 1.1f, 1.2f), //
expect(1, "event 14", 0.5f, 1.5f), // expect(1, "event 14", 0.5f, 1.5f), //
expect(1, "event 30", 1, 2), // expect(1, "event 30", 1, 2), //
expect(1, "complete", 1, 2), // expect(1, "complete", 1, 2), //
@ -177,9 +179,11 @@ public class AnimationStateTest {
expect(0, "start", 0.1f, 2.1f), // expect(0, "start", 0.1f, 2.1f), //
expect(1, "interrupt", 1.1f, 2.1f), // expect(1, "interrupt", 1.1f, 2.1f), //
expect(1, "end", 1.1f, 2.1f), //
expect(0, "event 0", 0.1f, 2.1f), // expect(0, "event 0", 0.1f, 2.1f), //
expect(1, "end", 1.1f, 2.2f), //
expect(0, "event 14", 0.5f, 2.5f), // expect(0, "event 14", 0.5f, 2.5f), //
expect(0, "event 30", 1, 3), // expect(0, "event 30", 1, 3), //
expect(0, "complete", 1, 3), // expect(0, "complete", 1, 3), //
@ -198,9 +202,11 @@ public class AnimationStateTest {
expect(1, "start", 0.1f, 0.6f), // expect(1, "start", 0.1f, 0.6f), //
expect(0, "interrupt", 0.6f, 0.6f), // expect(0, "interrupt", 0.6f, 0.6f), //
expect(0, "end", 0.6f, 0.6f), //
expect(1, "event 0", 0.1f, 0.6f), // expect(1, "event 0", 0.1f, 0.6f), //
expect(0, "end", 0.6f, 0.7f), //
expect(1, "event 14", 0.5f, 1.0f), // expect(1, "event 14", 0.5f, 1.0f), //
expect(1, "event 30", 1, 1.5f), // expect(1, "event 30", 1, 1.5f), //
expect(1, "complete", 1, 1.5f), // expect(1, "complete", 1, 1.5f), //
@ -223,7 +229,7 @@ public class AnimationStateTest {
expect(1, "event 0", 0.1f, 1), // expect(1, "event 0", 0.1f, 1), //
expect(1, "event 14", 0.5f, 1.4f), // expect(1, "event 14", 0.5f, 1.4f), //
expect(0, "end", 1.6f, 1.6f), // expect(0, "end", 1.6f, 1.7f), //
expect(1, "event 30", 1, 1.9f), // expect(1, "event 30", 1, 1.9f), //
expect(1, "complete", 1, 1.9f), // expect(1, "complete", 1, 1.9f), //
@ -246,7 +252,7 @@ public class AnimationStateTest {
expect(1, "event 14", 0.5f, 0.9f), // expect(1, "event 14", 0.5f, 0.9f), //
expect(0, "complete", 1, 1), // expect(0, "complete", 1, 1), //
expect(0, "end", 1.1f, 1.1f), // expect(0, "end", 1.1f, 1.2f), //
expect(1, "event 30", 1, 1.4f), // expect(1, "event 30", 1, 1.4f), //
expect(1, "complete", 1, 1.4f), // expect(1, "complete", 1, 1.4f), //
@ -270,7 +276,7 @@ public class AnimationStateTest {
expect(1, "event 14", 0.5f, 0.9f), // expect(1, "event 14", 0.5f, 0.9f), //
expect(0, "complete", 1, 1), // expect(0, "complete", 1, 1), //
expect(0, "end", 1.1f, 1.1f), // expect(0, "end", 1.1f, 1.2f), //
expect(1, "event 30", 1, 1.4f), // expect(1, "event 30", 1, 1.4f), //
expect(1, "complete", 1, 1.4f), // expect(1, "complete", 1, 1.4f), //
@ -298,7 +304,7 @@ public class AnimationStateTest {
expect(1, "event 14", 0.5f, 1.3f), // expect(1, "event 14", 0.5f, 1.3f), //
expect(0, "end", 1.5f, 1.5f), // expect(0, "end", 1.5f, 1.6f), //
expect(1, "event 30", 1, 1.8f), // expect(1, "event 30", 1, 1.8f), //
expect(1, "complete", 1, 1.8f), // expect(1, "complete", 1, 1.8f), //
@ -341,9 +347,11 @@ public class AnimationStateTest {
expect(1, "start", 0.1f, 2.1f), // expect(1, "start", 0.1f, 2.1f), //
expect(0, "interrupt", 2.1f, 2.1f), // expect(0, "interrupt", 2.1f, 2.1f), //
expect(0, "end", 2.1f, 2.1f), //
expect(1, "event 0", 0.1f, 2.1f), // expect(1, "event 0", 0.1f, 2.1f), //
expect(0, "end", 2.1f, 2.2f), //
expect(1, "event 14", 0.5f, 2.5f), // expect(1, "event 14", 0.5f, 2.5f), //
expect(1, "event 30", 1, 3), // expect(1, "event 30", 1, 3), //
expect(1, "complete", 1, 3), // expect(1, "complete", 1, 3), //
@ -368,9 +376,11 @@ public class AnimationStateTest {
expect(1, "start", 0.1f, 2.1f), // expect(1, "start", 0.1f, 2.1f), //
expect(0, "interrupt", 2.1f, 2.1f), // expect(0, "interrupt", 2.1f, 2.1f), //
expect(0, "end", 2.1f, 2.1f), //
expect(1, "event 0", 0.1f, 2.1f), // expect(1, "event 0", 0.1f, 2.1f), //
expect(0, "end", 2.1f, 2.2f), //
expect(1, "event 14", 0.5f, 2.5f), // expect(1, "event 14", 0.5f, 2.5f), //
expect(1, "event 30", 1, 3), // expect(1, "event 30", 1, 3), //
expect(1, "complete", 1, 3), // expect(1, "complete", 1, 3), //
@ -461,7 +471,7 @@ public class AnimationStateTest {
expect(1, "event 14", 0.5f, 1.2f), // expect(1, "event 14", 0.5f, 1.2f), //
expect(0, "end", 1.4f, 1.4f), // expect(0, "end", 1.4f, 1.5f), //
expect(1, "event 30", 1, 1.7f), // expect(1, "event 30", 1, 1.7f), //
expect(1, "complete", 1, 1.7f), // expect(1, "complete", 1, 1.7f), //
@ -488,7 +498,7 @@ public class AnimationStateTest {
expect(1, "event 0", 0.1f, 1), // expect(1, "event 0", 0.1f, 1), //
expect(1, "event 14", 0.5f, 1.4f), // expect(1, "event 14", 0.5f, 1.4f), //
expect(0, "end", 1.6f, 1.6f), // expect(0, "end", 1.6f, 1.7f), //
expect(1, "event 30", 1, 1.9f), // expect(1, "event 30", 1, 1.9f), //
expect(1, "complete", 1, 1.9f), // expect(1, "complete", 1, 1.9f), //
@ -507,17 +517,21 @@ public class AnimationStateTest {
expect(1, "start", 0, 0), // expect(1, "start", 0, 0), //
expect(0, "interrupt", 0, 0), // expect(0, "interrupt", 0, 0), //
expect(0, "end", 0, 0), //
expect(1, "event 0", 0, 0), // expect(1, "event 0", 0, 0), //
expect(0, "end", 0.1f, 0.2f), //
expect(1, "event 14", 0.5f, 0.5f), // expect(1, "event 14", 0.5f, 0.5f), //
expect(0, "start", 0.1f, 0.8f), // expect(0, "start", 0.1f, 0.8f), //
expect(1, "interrupt", 0.8f, 0.8f), // expect(1, "interrupt", 0.8f, 0.8f), //
expect(1, "end", 0.8f, 0.8f), //
expect(0, "event 0", 0.1f, 0.8f), // expect(0, "event 0", 0.1f, 0.8f), //
expect(1, "end", 0.8f, 0.9f), //
expect(0, "event 14", 0.5f, 1.2f), // expect(0, "event 14", 0.5f, 1.2f), //
expect(0, "event 30", 1, 1.7f), // expect(0, "event 30", 1, 1.7f), //
expect(0, "complete", 1, 1.7f), // expect(0, "complete", 1, 1.7f), //

View File

@ -144,7 +144,7 @@ public class Animation {
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, boolean setupPose, public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, boolean setupPose,
boolean mixingOut); boolean mixingOut);
public int getId (); public int getPropertyId ();
} }
static private enum TimelineType { static private enum TimelineType {
@ -255,7 +255,7 @@ public class Animation {
frames = new float[frameCount << 1]; frames = new float[frameCount << 1];
} }
public int getId () { public int getPropertyId () {
return (TimelineType.rotate.ordinal() << 24) + boneIndex; return (TimelineType.rotate.ordinal() << 24) + boneIndex;
} }
@ -330,7 +330,7 @@ public class Animation {
frames = new float[frameCount * ENTRIES]; frames = new float[frameCount * ENTRIES];
} }
public int getId () { public int getPropertyId () {
return (TimelineType.translate.ordinal() << 24) + boneIndex; return (TimelineType.translate.ordinal() << 24) + boneIndex;
} }
@ -397,7 +397,7 @@ public class Animation {
super(frameCount); super(frameCount);
} }
public int getId () { public int getPropertyId () {
return (TimelineType.scale.ordinal() << 24) + boneIndex; return (TimelineType.scale.ordinal() << 24) + boneIndex;
} }
@ -455,7 +455,7 @@ public class Animation {
super(frameCount); super(frameCount);
} }
public int getId () { public int getPropertyId () {
return (TimelineType.shear.ordinal() << 24) + boneIndex; return (TimelineType.shear.ordinal() << 24) + boneIndex;
} }
@ -508,7 +508,7 @@ public class Animation {
frames = new float[frameCount * ENTRIES]; frames = new float[frameCount * ENTRIES];
} }
public int getId () { public int getPropertyId () {
return (TimelineType.color.ordinal() << 24) + slotIndex; return (TimelineType.color.ordinal() << 24) + slotIndex;
} }
@ -584,7 +584,7 @@ public class Animation {
attachmentNames = new String[frameCount]; attachmentNames = new String[frameCount];
} }
public int getId () { public int getPropertyId () {
return (TimelineType.attachment.ordinal() << 24) + slotIndex; return (TimelineType.attachment.ordinal() << 24) + slotIndex;
} }
@ -651,7 +651,7 @@ public class Animation {
frameVertices = new float[frameCount][]; frameVertices = new float[frameCount][];
} }
public int getId () { public int getPropertyId () {
return (TimelineType.deform.ordinal() << 24) + slotIndex; return (TimelineType.deform.ordinal() << 24) + slotIndex;
} }
@ -742,7 +742,7 @@ public class Animation {
events = new Event[frameCount]; events = new Event[frameCount];
} }
public int getId () { public int getPropertyId () {
return TimelineType.event.ordinal() << 24; return TimelineType.event.ordinal() << 24;
} }
@ -803,7 +803,7 @@ public class Animation {
drawOrders = new int[frameCount][]; drawOrders = new int[frameCount][];
} }
public int getId () { public int getPropertyId () {
return TimelineType.drawOrder.ordinal() << 24; return TimelineType.drawOrder.ordinal() << 24;
} }
@ -862,7 +862,7 @@ public class Animation {
frames = new float[frameCount * ENTRIES]; frames = new float[frameCount * ENTRIES];
} }
public int getId () { public int getPropertyId () {
return (TimelineType.ikConstraint.ordinal() << 24) + ikConstraintIndex; return (TimelineType.ikConstraint.ordinal() << 24) + ikConstraintIndex;
} }
@ -924,7 +924,7 @@ public class Animation {
frames = new float[frameCount * ENTRIES]; frames = new float[frameCount * ENTRIES];
} }
public int getId () { public int getPropertyId () {
return (TimelineType.transformConstraint.ordinal() << 24) + transformConstraintIndex; return (TimelineType.transformConstraint.ordinal() << 24) + transformConstraintIndex;
} }
@ -998,7 +998,7 @@ public class Animation {
frames = new float[frameCount * ENTRIES]; frames = new float[frameCount * ENTRIES];
} }
public int getId () { public int getPropertyId () {
return (TimelineType.pathConstraintPosition.ordinal() << 24) + pathConstraintIndex; return (TimelineType.pathConstraintPosition.ordinal() << 24) + pathConstraintIndex;
} }
@ -1050,7 +1050,7 @@ public class Animation {
super(frameCount); super(frameCount);
} }
public int getId () { public int getPropertyId () {
return (TimelineType.pathConstraintSpacing.ordinal() << 24) + pathConstraintIndex; return (TimelineType.pathConstraintSpacing.ordinal() << 24) + pathConstraintIndex;
} }
@ -1091,7 +1091,7 @@ public class Animation {
frames = new float[frameCount * ENTRIES]; frames = new float[frameCount * ENTRIES];
} }
public int getId () { public int getPropertyId () {
return (TimelineType.pathConstraintMix.ordinal() << 24) + pathConstraintIndex; return (TimelineType.pathConstraintMix.ordinal() << 24) + pathConstraintIndex;
} }

View File

@ -53,7 +53,8 @@ public class AnimationState {
} }
}; };
private final EventQueue queue = new EventQueue(listeners, trackEntryPool); private final EventQueue queue = new EventQueue(listeners, trackEntryPool);
private final IntSet usage = new IntSet(); private final IntSet propertyIDs = new IntSet();
private boolean animationsChanged;
private float timeScale = 1; private float timeScale = 1;
/** Creates an uninitialized AnimationState. The animation state data must be set before use. */ /** Creates an uninitialized AnimationState. The animation state data must be set before use. */
@ -68,7 +69,7 @@ public class AnimationState {
/** Increments the track entry times, setting queued animations as current if needed. */ /** Increments the track entry times, setting queued animations as current if needed. */
public void update (float delta) { public void update (float delta) {
delta *= timeScale; delta *= timeScale;
for (int i = 0; i < tracks.size; i++) { for (int i = 0, n = tracks.size; i < n; i++) {
TrackEntry current = tracks.get(i); TrackEntry current = tracks.get(i);
if (current == null) continue; if (current == null) continue;
@ -81,7 +82,7 @@ public class AnimationState {
current.delay = 0; current.delay = 0;
} }
TrackEntry next = current.next; TrackEntry next = current.next, mixingFrom = current.mixingFrom;
if (next != null) { if (next != null) {
// When the next entry's delay is passed, change to the next entry. // When the next entry's delay is passed, change to the next entry.
float nextTime = current.trackLast - next.delay; float nextTime = current.trackLast - next.delay;
@ -95,23 +96,40 @@ public class AnimationState {
} }
} else if (current.trackLast >= current.trackEnd) { } else if (current.trackLast >= current.trackEnd) {
// Clear the track when the end time is reached and there is no next entry. // Clear the track when the end time is reached and there is no next entry.
clearTrack(i); // BOZO - This leaves the skeleton in the last pose, with no easy way of resetting.
// Should we get rid of the track end time?
// Or default it to MAX_VALUE even for non-looping animations?
// Or reset the skeleton before clearing? Note only apply() has a skeleton.
freeAll(current.next);
queue.end(current);
if (mixingFrom != null) queue.end(mixingFrom);
tracks.set(i, null);
continue; continue;
} }
current.trackTime += currentDelta; current.trackTime += currentDelta;
if (current.mixingFrom != null) { if (mixingFrom != null) {
float mixingFromDelta = delta * current.mixingFrom.timeScale; if (current.mixTime >= current.mixDuration && current.mixTime > 0) {
current.mixingFrom.trackTime += mixingFromDelta; queue.end(mixingFrom);
current.mixTime += mixingFromDelta; current.mixingFrom = null;
animationsChanged = true;
} else {
float mixingFromDelta = delta * mixingFrom.timeScale;
mixingFrom.trackTime += mixingFromDelta;
current.mixTime += mixingFromDelta;
}
} }
} }
queue.drain();
} }
/** Poses the skeleton using the track entry animations. */ /** Poses the skeleton using the track entry animations. */
public void apply (Skeleton skeleton) { public void apply (Skeleton skeleton) {
if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
if (animationsChanged) animationsChanged();
Array<Event> events = this.events; Array<Event> events = this.events;
for (int i = 0; i < tracks.size; i++) { for (int i = 0; i < tracks.size; i++) {
@ -128,18 +146,18 @@ public class AnimationState {
if (mix > 1) mix = 1; if (mix > 1) mix = 1;
} }
applyMixingFrom(current.mixingFrom, skeleton, mix); applyMixingFrom(current.mixingFrom, skeleton, mix);
if (mix == 1) {
queue.end(current.mixingFrom);
current.mixingFrom = null;
updateSetupPose();
}
} }
float animationLast = current.animationLast, animationTime = current.getAnimationTime(); float animationLast = current.animationLast, animationTime = current.getAnimationTime();
Array<Timeline> timelines = current.animation.timelines; Array<Timeline> timelines = current.animation.timelines;
BooleanArray setupPose = current.setupPose; if (mix == 1) {
for (int ii = 0, n = timelines.size; ii < n; ii++) for (int ii = 0, n = timelines.size; ii < n; ii++)
timelines.get(ii).apply(skeleton, animationLast, animationTime, events, mix, setupPose.get(ii), false); timelines.get(ii).apply(skeleton, animationLast, animationTime, events, 1, false, false);
} else {
boolean[] timelinesFirst = current.timelinesFirst.items;
for (int ii = 0, n = timelines.size; ii < n; ii++)
timelines.get(ii).apply(skeleton, animationLast, animationTime, events, mix, timelinesFirst[ii], false);
}
queueEvents(current, animationTime); queueEvents(current, animationTime);
current.animationLast = animationTime; current.animationLast = animationTime;
current.trackLast = current.trackTime; current.trackLast = current.trackTime;
@ -154,12 +172,12 @@ public class AnimationState {
float animationLast = entry.animationLast, animationTime = entry.getAnimationTime(); float animationLast = entry.animationLast, animationTime = entry.getAnimationTime();
Array<Timeline> timelines = entry.animation.timelines; Array<Timeline> timelines = entry.animation.timelines;
BooleanArray setupPose = entry.setupPose; boolean[] timelinesFirst = entry.timelinesFirst.items;
float alphaFull = entry.alpha, alphaMix = entry.alpha * (1 - mix); float alphaFull = entry.alpha, alphaMix = entry.alpha * (1 - mix);
if (attachments && drawOrder) { if (attachments && drawOrder) {
for (int i = 0, n = timelines.size; i < n; i++) { for (int i = 0, n = timelines.size; i < n; i++) {
Timeline timeline = timelines.get(i); Timeline timeline = timelines.get(i);
if (setupPose.get(i)) if (timelinesFirst[i])
timeline.apply(skeleton, animationLast, animationTime, events, alphaMix, true, true); timeline.apply(skeleton, animationLast, animationTime, events, alphaMix, true, true);
else else
timeline.apply(skeleton, animationLast, animationTime, events, alphaFull, false, false); timeline.apply(skeleton, animationLast, animationTime, events, alphaFull, false, false);
@ -167,12 +185,13 @@ public class AnimationState {
} else { } else {
for (int i = 0, n = timelines.size; i < n; i++) { for (int i = 0, n = timelines.size; i < n; i++) {
Timeline timeline = timelines.get(i); Timeline timeline = timelines.get(i);
if (!attachments && timeline instanceof AttachmentTimeline) continue; if (timelinesFirst[i])
if (!drawOrder && timeline instanceof DrawOrderTimeline) continue;
if (setupPose.get(i))
timeline.apply(skeleton, animationLast, animationTime, events, alphaMix, true, true); timeline.apply(skeleton, animationLast, animationTime, events, alphaMix, true, true);
else else {
if (!attachments && timeline instanceof AttachmentTimeline) continue;
if (!drawOrder && timeline instanceof DrawOrderTimeline) continue;
timeline.apply(skeleton, animationLast, animationTime, events, alphaFull, false, false); timeline.apply(skeleton, animationLast, animationTime, events, alphaFull, false, false);
}
} }
} }
@ -217,6 +236,7 @@ public class AnimationState {
tracks.clear(); tracks.clear();
} }
// BOZO - This leaves the skeleton in the last pose, with no easy way of resetting.
public void clearTrack (int trackIndex) { public void clearTrack (int trackIndex) {
if (trackIndex >= tracks.size) return; if (trackIndex >= tracks.size) return;
TrackEntry current = tracks.get(trackIndex); TrackEntry current = tracks.get(trackIndex);
@ -268,60 +288,50 @@ public class AnimationState {
if (mixingFrom != null) queue.end(mixingFrom); if (mixingFrom != null) queue.end(mixingFrom);
} }
queue.drain(); animationsChanged = true;
updateSetupPose();
} }
private void updateSetupPose () { private void animationsChanged () {
usage.clear(); animationsChanged = false;
propertyIDs.clear();
int i = 0, n = tracks.size; int i = 0, n = tracks.size;
for (; i < n; i++) { for (; i < n; i++) {
TrackEntry entry = tracks.get(i); TrackEntry entry = tracks.get(i);
if (entry == null) continue; if (entry == null) continue;
if (entry.mixingFrom != null) { if (entry.mixingFrom != null) {
updateFirstSetupPose(entry.mixingFrom); setTimelinesFirst(entry.mixingFrom);
updateSetupPose(entry); checkTimelinesFirst(entry);
} else } else
updateFirstSetupPose(entry); setTimelinesFirst(entry);
i++; i++;
break; break;
} }
for (; i < n; i++) { for (; i < n; i++) {
TrackEntry entry = tracks.get(i); TrackEntry entry = tracks.get(i);
if (entry == null) continue; if (entry == null) continue;
if (entry.mixingFrom != null) updateSetupPose(entry.mixingFrom); if (entry.mixingFrom != null) checkTimelinesFirst(entry.mixingFrom);
updateSetupPose(entry); checkTimelinesFirst(entry);
} }
} }
private void updateFirstSetupPose (TrackEntry entry) { private void setTimelinesFirst (TrackEntry entry) {
IntSet usage = this.usage; IntSet propertyIDs = this.propertyIDs;
BooleanArray setupPose = entry.setupPose;
setupPose.clear();
Array<Timeline> timelines = entry.animation.timelines; Array<Timeline> timelines = entry.animation.timelines;
for (int ii = 0, nn = timelines.size; ii < nn; ii++) { int n = timelines.size;
Timeline timeline = timelines.get(ii); boolean[] timelinesFirst = entry.timelinesFirst.setSize(n);
usage.add(timeline.getId()); for (int i = 0; i < n; i++) {
setupPose.add(true); propertyIDs.add(timelines.get(i).getPropertyId());
timelinesFirst[i] = true;
} }
} }
private void updateSetupPose (TrackEntry entry) { private void checkTimelinesFirst (TrackEntry entry) {
IntSet usage = this.usage; IntSet propertyIDs = this.propertyIDs;
BooleanArray setupPose = entry.setupPose;
setupPose.clear();
Array<Timeline> timelines = entry.animation.timelines; Array<Timeline> timelines = entry.animation.timelines;
for (int ii = 0, nn = timelines.size; ii < nn; ii++) { int n = timelines.size;
Timeline timeline = timelines.get(ii); boolean[] timelinesFirst = entry.timelinesFirst.setSize(n);
int id = timeline.getId(); for (int i = 0; i < n; i++)
if (usage.contains(id)) timelinesFirst[i] = propertyIDs.add(timelines.get(i).getPropertyId());
setupPose.add(false);
else {
usage.add(id);
setupPose.add(true);
}
}
} }
/** @see #setAnimation(int, Animation, boolean) */ /** @see #setAnimation(int, Animation, boolean) */
@ -340,13 +350,15 @@ public class AnimationState {
if (animation == null) throw new IllegalArgumentException("animation cannot be null."); if (animation == null) throw new IllegalArgumentException("animation cannot be null.");
TrackEntry current = expandToIndex(trackIndex); TrackEntry current = expandToIndex(trackIndex);
TrackEntry entry = trackEntry(trackIndex, animation, loop, current); TrackEntry entry = trackEntry(trackIndex, animation, loop, current);
if (current == null) if (current == null) {
setCurrent(trackIndex, entry); setCurrent(trackIndex, entry);
else { queue.drain();
} else {
freeAll(current.next); freeAll(current.next);
if (current.trackLast == -1) // If current was never applied, replace it. if (current.trackLast == -1) { // If current was never applied, replace it.
setCurrent(trackIndex, entry); setCurrent(trackIndex, entry);
else { queue.drain();
} else {
current.next = entry; current.next = entry;
entry.delay = current.trackLast; entry.delay = current.trackLast;
} }
@ -377,9 +389,10 @@ public class AnimationState {
TrackEntry entry = trackEntry(trackIndex, animation, loop, last); TrackEntry entry = trackEntry(trackIndex, animation, loop, last);
if (last == null) if (last == null) {
setCurrent(trackIndex, entry); setCurrent(trackIndex, entry);
else { queue.drain();
} else {
last.next = entry; last.next = entry;
if (delay <= 0) { if (delay <= 0) {
float duration = last.animationEnd - last.animationStart; float duration = last.animationEnd - last.animationStart;
@ -402,7 +415,7 @@ public class AnimationState {
entry.loop = loop; entry.loop = loop;
entry.eventThreshold = 0; entry.eventThreshold = 0;
entry.attachmentThreshold = 1; entry.attachmentThreshold = 0;
entry.drawOrderThreshold = 0; entry.drawOrderThreshold = 0;
entry.delay = 0; entry.delay = 0;
@ -496,14 +509,14 @@ public class AnimationState {
float delay, trackTime, trackLast, trackEnd, animationStart, animationEnd, animationLast, timeScale; float delay, trackTime, trackLast, trackEnd, animationStart, animationEnd, animationLast, timeScale;
float alpha; float alpha;
float mixTime, mixDuration; float mixTime, mixDuration;
final BooleanArray setupPose = new BooleanArray(); final BooleanArray timelinesFirst = new BooleanArray();
public void reset () { public void reset () {
next = null; next = null;
mixingFrom = null; mixingFrom = null;
animation = null; animation = null;
listener = null; listener = null;
setupPose.clear(); timelinesFirst.clear();
} }
public int getTrackIndex () { public int getTrackIndex () {
@ -538,7 +551,8 @@ public class AnimationState {
} }
/** Current time in seconds this track entry has been the current track entry. The track time determines /** Current time in seconds this track entry has been the current track entry. The track time determines
* {@link #getAnimationTime()} and can be set to start the animation at a time other than 0. */ * {@link #getAnimationTime()}. The track time can be set to start the animation at a time other than 0, without affecting
* looping. */
public float getTrackTime () { public float getTrackTime () {
return trackTime; return trackTime;
} }
@ -548,8 +562,8 @@ public class AnimationState {
} }
/** The track time in seconds when this animation will be removed from the track. If the track end time is reached and no /** The track time in seconds when this animation will be removed from the track. If the track end time is reached and no
* other animations are queued for playback, the track is cleared. Defaults to the animation duration for non-looping * other animations are queued for playback, the track is cleared, leaving the skeleton in the last applied pose. Defaults
* animations and to {@link Integer#MAX_VALUE} for looping animations. */ * to the animation duration for non-looping animations and to {@link Integer#MAX_VALUE} for looping animations. */
public float getTrackEnd () { public float getTrackEnd () {
return trackEnd; return trackEnd;
} }
@ -560,8 +574,8 @@ public class AnimationState {
/** Seconds when this animation starts, both initially and after looping. Defaults to 0. /** Seconds when this animation starts, both initially and after looping. Defaults to 0.
* <p> * <p>
* When changing the animation start time, it often makes sense to also change {@link #getAnimationLast()} to control when * When changing the animation start time, it often makes sense to also change {@link #getAnimationLast()} to control which
* timelines will trigger. */ * timeline keys will trigger. */
public float getAnimationStart () { public float getAnimationStart () {
return animationStart; return animationStart;
} }
@ -581,8 +595,8 @@ public class AnimationState {
} }
/** The time in seconds this animation was last applied. Some timelines use this for one-time triggers. Eg, when this /** The time in seconds this animation was last applied. Some timelines use this for one-time triggers. Eg, when this
* animation is applied, event timelines will fire all events between lastTime (exclusive) and time (inclusive). Defaults to * animation is applied, event timelines will fire all events between the animation last time (exclusive) and animation time
* -1 to ensure triggers on frame 0 happen the first time this animation is applied. */ * (inclusive). Defaults to -1 to ensure triggers on frame 0 happen the first time this animation is applied. */
public float getAnimationLast () { public float getAnimationLast () {
return animationLast; return animationLast;
} }