Added timelinesLast to determine when to fade out. Clean up, fixes.

This commit is contained in:
NathanSweet 2016-08-24 21:04:25 +02:00
parent 5edcd916cc
commit a840f4f959
2 changed files with 115 additions and 110 deletions

View File

@ -33,7 +33,6 @@ package com.esotericsoftware.spine;
import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.BooleanArray; import com.badlogic.gdx.utils.BooleanArray;
import com.badlogic.gdx.utils.IntArray;
import com.badlogic.gdx.utils.IntSet; import com.badlogic.gdx.utils.IntSet;
import com.badlogic.gdx.utils.Pool; import com.badlogic.gdx.utils.Pool;
import com.badlogic.gdx.utils.Pool.Poolable; import com.badlogic.gdx.utils.Pool.Poolable;
@ -52,16 +51,17 @@ public class AnimationState {
private AnimationStateData data; private AnimationStateData data;
private final Array<TrackEntry> tracks = new Array(); private final Array<TrackEntry> tracks = new Array();
private final Array<Event> events = new Array(); private final Array<Event> events = new Array();
private final Array<AnimationStateListener> listeners = new Array(); final Array<AnimationStateListener> listeners = new Array();
private final Pool<TrackEntry> trackEntryPool = new Pool() { private final EventQueue queue = new EventQueue();
private final IntSet propertyIDs = new IntSet();
boolean animationsChanged;
private float timeScale = 1;
final Pool<TrackEntry> trackEntryPool = new Pool() {
protected Object newObject () { protected Object newObject () {
return new TrackEntry(); return new TrackEntry();
} }
}; };
private final EventQueue queue = new EventQueue(listeners, trackEntryPool);
private final IntSet propertyIDs = new IntSet();
private boolean animationsChanged;
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. */
public AnimationState () { public AnimationState () {
@ -117,7 +117,6 @@ public class AnimationState {
if (current.mixTime >= current.mixDuration && current.mixTime > 0) { if (current.mixTime >= current.mixDuration && current.mixTime > 0) {
current.mixingFrom = null; current.mixingFrom = null;
queue.end(mixingFrom); queue.end(mixingFrom);
animationsChanged = true;
} else { } else {
mixingFrom.animationLast = mixingFrom.nextAnimationLast; mixingFrom.animationLast = mixingFrom.nextAnimationLast;
mixingFrom.trackLast = mixingFrom.nextTrackLast; mixingFrom.trackLast = mixingFrom.nextTrackLast;
@ -135,7 +134,6 @@ public class AnimationState {
* animation state can be applied to multiple skeletons to pose them identically. */ * animation state can be applied to multiple skeletons to pose them identically. */
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(); if (animationsChanged) animationsChanged();
Array<Event> events = this.events; Array<Event> events = this.events;
@ -180,27 +178,17 @@ 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;
boolean[] timelinesFirst = entry.timelinesFirst.items; boolean[] timelinesFirst = entry.timelinesFirst.items, timelinesLast = entry.timelinesLast.items;
float alphaFull = entry.alpha, alphaMix = entry.alpha * (1 - mix); float alphaFull = entry.alpha, alphaMix = alphaFull * (1 - mix);
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); boolean setupPose = timelinesFirst[i];
if (timelinesFirst[i]) if (!setupPose) {
timeline.apply(skeleton, animationLast, animationTime, events, alphaMix, true, true); if (!attachments && timeline instanceof AttachmentTimeline) continue;
else if (!drawOrder && timeline instanceof DrawOrderTimeline) continue;
timeline.apply(skeleton, animationLast, animationTime, events, alphaFull, false, false);
}
} else {
for (int i = 0, n = timelines.size; i < n; i++) {
Timeline timeline = timelines.get(i);
if (timelinesFirst[i])
timeline.apply(skeleton, animationLast, animationTime, events, alphaMix, true, true);
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, timelinesLast[i] ? alphaMix : alphaFull, setupPose,
setupPose);
} }
queueEvents(entry, animationTime); queueEvents(entry, animationTime);
@ -296,8 +284,6 @@ public class AnimationState {
} }
queue.start(entry); queue.start(entry);
animationsChanged = true;
} }
/** @see #setAnimation(int, Animation, boolean) */ /** @see #setAnimation(int, Animation, boolean) */
@ -371,8 +357,7 @@ public class AnimationState {
return entry; return entry;
} }
/** Sets an empty animation for a track, discarding any queued animations, and mixes to it over the specified mix duration. The /** Sets an empty animation for a track, discarding any queued animations, and mixes to it over the specified mix duration. */
* empty animation's pose is the setup pose. */
public TrackEntry setEmptyAnimation (int trackIndex, float mixDuration) { public TrackEntry setEmptyAnimation (int trackIndex, float mixDuration) {
TrackEntry entry = setAnimation(trackIndex, emptyAnimation, false); TrackEntry entry = setAnimation(trackIndex, emptyAnimation, false);
entry.mixDuration = mixDuration; entry.mixDuration = mixDuration;
@ -381,20 +366,21 @@ public class AnimationState {
} }
/** Adds an empty animation to be played after the current or last queued animation for a track, and mixes to it over the /** Adds an empty animation to be played after the current or last queued animation for a track, and mixes to it over the
* specified mix duration. The empty animation's pose is the setup pose. * specified mix duration.
* @param delay Seconds to begin this animation after the start of the previous animation. May be <= 0 to use the animation * @param delay Seconds to begin this animation after the start of the previous animation. May be <= 0 to use the animation
* duration of the previous track minus any mix duration plus the negative delay. * duration of the previous track minus any mix duration plus the negative delay.
* @return A track entry to allow further customization of animation playback. References to the track entry must not be kept * @return A track entry to allow further customization of animation playback. References to the track entry must not be kept
* after {@link AnimationStateListener#dispose(TrackEntry)}. */ * after {@link AnimationStateListener#dispose(TrackEntry)}. */
public TrackEntry addEmptyAnimation (int trackIndex, float mixDuration, float delay) { public TrackEntry addEmptyAnimation (int trackIndex, float mixDuration, float delay) {
if (delay <= 0) delay -= mixDuration;
TrackEntry entry = addAnimation(trackIndex, emptyAnimation, false, delay); TrackEntry entry = addAnimation(trackIndex, emptyAnimation, false, delay);
entry.mixDuration = mixDuration; entry.mixDuration = mixDuration;
entry.trackEnd = mixDuration; entry.trackEnd = mixDuration;
return entry; return entry;
} }
/** Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix duration. /** Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix
* The empty animation's pose is the setup pose. */ * duration. */
public void setEmptyAnimations (float mixDuration) { public void setEmptyAnimations (float mixDuration) {
queue.drainDisabled = true; queue.drainDisabled = true;
for (int i = 0, n = tracks.size; i < n; i++) { for (int i = 0, n = tracks.size; i < n; i++) {
@ -423,15 +409,16 @@ public class AnimationState {
entry.attachmentThreshold = 0; entry.attachmentThreshold = 0;
entry.drawOrderThreshold = 0; entry.drawOrderThreshold = 0;
entry.delay = 0;
entry.animationStart = 0; entry.animationStart = 0;
entry.animationEnd = animation.getDuration(); entry.animationEnd = animation.getDuration();
entry.animationLast = -1; entry.animationLast = -1;
entry.nextAnimationLast = -1; entry.nextAnimationLast = -1;
entry.delay = 0;
entry.trackTime = 0; entry.trackTime = 0;
entry.trackEnd = loop ? Integer.MAX_VALUE : entry.animationEnd;
entry.trackLast = -1; entry.trackLast = -1;
entry.nextTrackLast = -1; entry.nextTrackLast = -1;
entry.trackEnd = loop ? Integer.MAX_VALUE : entry.animationEnd;
entry.timeScale = 1; entry.timeScale = 1;
entry.alpha = 1; entry.alpha = 1;
@ -451,43 +438,65 @@ public class AnimationState {
private void animationsChanged () { private void animationsChanged () {
animationsChanged = false; animationsChanged = false;
propertyIDs.clear();
// Compute timelinesFirst.
int i = 0, n = tracks.size; int i = 0, n = tracks.size;
propertyIDs.clear();
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) {
setTimelinesFirst(entry.mixingFrom); setTimelineUsage(entry.mixingFrom, entry.mixingFrom.timelinesFirst);
checkTimelinesFirst(entry); checkTimelineUsage(entry, entry.timelinesFirst);
} else } else
setTimelinesFirst(entry); setTimelineUsage(entry, entry.timelinesFirst);
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) checkTimelinesFirst(entry.mixingFrom); if (entry.mixingFrom != null) checkTimelineUsage(entry.mixingFrom, entry.mixingFrom.timelinesFirst);
checkTimelinesFirst(entry); checkTimelineUsage(entry, entry.timelinesFirst);
}
// Compute timelinesLast. Find lowest track with mixing.
propertyIDs.clear();
for (i = n - 1; i >= 0; i--) {
TrackEntry entry = tracks.get(i);
if (entry == null) continue;
if (entry.mixingFrom != null) {
setTimelineUsage(entry, entry.timelinesLast);
checkTimelineUsage(entry.mixingFrom, entry.mixingFrom.timelinesLast);
} else
setTimelineUsage(entry, entry.timelinesLast);
i--;
break;
}
for (; i >= 0; i--) {
TrackEntry entry = tracks.get(i);
if (entry == null) continue;
checkTimelineUsage(entry, entry.timelinesLast);
if (entry.mixingFrom != null) checkTimelineUsage(entry.mixingFrom, entry.mixingFrom.timelinesLast);
} }
} }
private void setTimelinesFirst (TrackEntry entry) { private void setTimelineUsage (TrackEntry entry, BooleanArray usageArray) {
IntSet propertyIDs = this.propertyIDs; IntSet propertyIDs = this.propertyIDs;
Array<Timeline> timelines = entry.animation.timelines; Array<Timeline> timelines = entry.animation.timelines;
int n = timelines.size; int n = timelines.size;
boolean[] timelinesFirst = entry.timelinesFirst.setSize(n); boolean[] usage = usageArray.setSize(n);
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
propertyIDs.add(timelines.get(i).getPropertyId()); propertyIDs.add(timelines.get(i).getPropertyId());
timelinesFirst[i] = true; usage[i] = true;
} }
} }
private void checkTimelinesFirst (TrackEntry entry) { private void checkTimelineUsage (TrackEntry entry, BooleanArray usageArray) {
IntSet propertyIDs = this.propertyIDs; IntSet propertyIDs = this.propertyIDs;
Array<Timeline> timelines = entry.animation.timelines; Array<Timeline> timelines = entry.animation.timelines;
int n = timelines.size; int n = timelines.size;
boolean[] timelinesFirst = entry.timelinesFirst.setSize(n); boolean[] timelinesFirst = usageArray.setSize(n);
for (int i = 0; i < n; i++) for (int i = 0; i < n; i++)
timelinesFirst[i] = propertyIDs.add(timelines.get(i).getPropertyId()); timelinesFirst[i] = propertyIDs.add(timelines.get(i).getPropertyId());
} }
@ -564,10 +573,10 @@ public class AnimationState {
int trackIndex; int trackIndex;
boolean loop; boolean loop;
float eventThreshold, attachmentThreshold, drawOrderThreshold; float eventThreshold, attachmentThreshold, drawOrderThreshold;
float delay, trackTime, trackLast, trackEnd, animationStart, animationEnd, animationLast, timeScale; float animationStart, animationEnd, animationLast, nextAnimationLast;
float nextTrackLast, nextAnimationLast; float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale;
float alpha, mixTime, mixDuration; float alpha, mixTime, mixDuration;
final BooleanArray timelinesFirst = new BooleanArray(); final BooleanArray timelinesFirst = new BooleanArray(), timelinesLast = new BooleanArray();
public void reset () { public void reset () {
next = null; next = null;
@ -575,6 +584,7 @@ public class AnimationState {
animation = null; animation = null;
listener = null; listener = null;
timelinesFirst.clear(); timelinesFirst.clear();
timelinesLast.clear();
} }
public int getTrackIndex () { public int getTrackIndex () {
@ -786,49 +796,41 @@ public class AnimationState {
} }
} }
static private class EventQueue { class EventQueue {
static private final int START = 0, EVENT = 1, COMPLETE = 2, INTERRUPT = 3, END = 4, DISPOSE = 5;
private final Array<AnimationStateListener> listeners;
private final Pool<TrackEntry> trackEntryPool;
private final Array objects = new Array(); private final Array objects = new Array();
private final IntArray eventTypes = new IntArray(); // If > 0 it's loop count for a complete event.
boolean drainDisabled; boolean drainDisabled;
public EventQueue (Array<AnimationStateListener> listeners, Pool<TrackEntry> trackEntryPool) {
this.listeners = listeners;
this.trackEntryPool = trackEntryPool;
}
public void start (TrackEntry entry) { public void start (TrackEntry entry) {
objects.add(EventType.start);
objects.add(entry); objects.add(entry);
eventTypes.add(START); animationsChanged = true;
}
public void event (TrackEntry entry, Event event) {
objects.add(entry);
objects.add(event);
eventTypes.add(EVENT);
}
public void complete (TrackEntry entry) {
objects.add(entry);
eventTypes.add(COMPLETE);
} }
public void interrupt (TrackEntry entry) { public void interrupt (TrackEntry entry) {
objects.add(EventType.interrupt);
objects.add(entry); objects.add(entry);
eventTypes.add(INTERRUPT);
} }
public void end (TrackEntry entry) { public void end (TrackEntry entry) {
objects.add(EventType.end);
objects.add(entry); objects.add(entry);
eventTypes.add(END); animationsChanged = true;
} }
public void dispose (TrackEntry entry) { public void dispose (TrackEntry entry) {
objects.add(EventType.dispose);
objects.add(entry); objects.add(entry);
eventTypes.add(DISPOSE); }
public void complete (TrackEntry entry) {
objects.add(EventType.complete);
objects.add(entry);
}
public void event (TrackEntry entry, Event event) {
objects.add(EventType.event);
objects.add(entry);
objects.add(event);
} }
public void drain () { public void drain () {
@ -836,43 +838,43 @@ public class AnimationState {
drainDisabled = true; drainDisabled = true;
Array objects = this.objects; Array objects = this.objects;
IntArray eventTypes = this.eventTypes; Array<AnimationStateListener> listeners = AnimationState.this.listeners;
Array<AnimationStateListener> listeners = this.listeners; for (int i = 0; i < objects.size; i += 2) {
for (int e = 0, o = 0; e < eventTypes.size; e++, o++) { EventType type = (EventType)objects.get(i);
TrackEntry entry = (TrackEntry)objects.get(o); TrackEntry entry = (TrackEntry)objects.get(i + 1);
int eventType = eventTypes.get(e); switch (type) {
switch (eventType) { case start:
case START:
if (entry.listener != null) entry.listener.end(entry); if (entry.listener != null) entry.listener.end(entry);
for (int i = 0; i < listeners.size; i++) for (int ii = 0; ii < listeners.size; ii++)
listeners.get(i).start(entry); listeners.get(ii).start(entry);
break; break;
case EVENT: case interrupt:
Event event = (Event)objects.get(++o);
if (entry.listener != null) entry.listener.event(entry, event);
for (int i = 0; i < listeners.size; i++)
listeners.get(i).event(entry, event);
break;
case INTERRUPT:
if (entry.listener != null) entry.listener.end(entry); if (entry.listener != null) entry.listener.end(entry);
for (int i = 0; i < listeners.size; i++) for (int ii = 0; ii < listeners.size; ii++)
listeners.get(i).interrupt(entry); listeners.get(ii).interrupt(entry);
break; break;
case END: case end:
if (entry.listener != null) entry.listener.end(entry); if (entry.listener != null) entry.listener.end(entry);
for (int i = 0; i < listeners.size; i++) for (int ii = 0; ii < listeners.size; ii++)
listeners.get(i).end(entry); listeners.get(ii).end(entry);
// Fall through. // Fall through.
case DISPOSE: case dispose:
if (entry.listener != null) entry.listener.end(entry); if (entry.listener != null) entry.listener.end(entry);
for (int i = 0; i < listeners.size; i++) for (int ii = 0; ii < listeners.size; ii++)
listeners.get(i).dispose(entry); listeners.get(ii).dispose(entry);
trackEntryPool.free(entry); trackEntryPool.free(entry);
break; break;
default: case complete:
if (entry.listener != null) entry.listener.complete(entry); if (entry.listener != null) entry.listener.complete(entry);
for (int i = 0; i < listeners.size; i++) for (int ii = 0; ii < listeners.size; ii++)
listeners.get(i).complete(entry); listeners.get(ii).complete(entry);
break;
case event:
Event event = (Event)objects.get(i++ + 2);
if (entry.listener != null) entry.listener.event(entry, event);
for (int ii = 0; ii < listeners.size; ii++)
listeners.get(ii).event(entry, event);
break;
} }
} }
clear(); clear();
@ -882,15 +884,18 @@ public class AnimationState {
public void clear () { public void clear () {
objects.clear(); objects.clear();
eventTypes.clear();
} }
} }
static private enum EventType {
start, interrupt, end, dispose, complete, event
}
static public interface AnimationStateListener { static public interface AnimationStateListener {
/** Invoked when this entry has been set as the current entry. */ /** Invoked when this entry has been set as the current entry. */
public void start (TrackEntry entry); public void start (TrackEntry entry);
/** Invoked when another entry replaces this entry as the current entry. This entry may continue being applied for /** Invoked when another entry has replaced this entry as the current entry. This entry may continue being applied for
* mixing. */ * mixing. */
public void interrupt (TrackEntry entry); public void interrupt (TrackEntry entry);
@ -921,10 +926,10 @@ public class AnimationState {
public void dispose (TrackEntry entry) { public void dispose (TrackEntry entry) {
} }
public void event (TrackEntry entry, Event event) { public void complete (TrackEntry entry) {
} }
public void complete (TrackEntry entry) { public void event (TrackEntry entry, Event event) {
} }
} }
} }

View File

@ -283,7 +283,7 @@ public class SkeletonViewer extends ApplicationAdapter {
shapes.setColor(Color.CYAN); shapes.setColor(Color.CYAN);
shapes.line(x, 0, x, 20); shapes.line(x, 0, x, 20);
percent = entry.getMixTime() / entry.getMixDuration(); percent = entry.getMixDuration() == 0 ? 1 : Math.min(1, entry.getMixTime() / entry.getMixDuration());
x = ui.window.getRight() + (Gdx.graphics.getWidth() - ui.window.getRight()) * percent; x = ui.window.getRight() + (Gdx.graphics.getWidth() - ui.window.getRight()) * percent;
shapes.setColor(Color.RED); shapes.setColor(Color.RED);
shapes.line(x, 0, x, 20); shapes.line(x, 0, x, 20);