diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java index 49ee0e2bf..fa14f4814 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java @@ -33,7 +33,6 @@ package com.esotericsoftware.spine; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.BooleanArray; -import com.badlogic.gdx.utils.IntArray; import com.badlogic.gdx.utils.IntSet; import com.badlogic.gdx.utils.Pool; import com.badlogic.gdx.utils.Pool.Poolable; @@ -52,16 +51,17 @@ public class AnimationState { private AnimationStateData data; private final Array tracks = new Array(); private final Array events = new Array(); - private final Array listeners = new Array(); - private final Pool trackEntryPool = new Pool() { + final Array listeners = new Array(); + private final EventQueue queue = new EventQueue(); + private final IntSet propertyIDs = new IntSet(); + boolean animationsChanged; + private float timeScale = 1; + + final Pool trackEntryPool = new Pool() { protected Object newObject () { 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. */ public AnimationState () { @@ -117,7 +117,6 @@ public class AnimationState { if (current.mixTime >= current.mixDuration && current.mixTime > 0) { current.mixingFrom = null; queue.end(mixingFrom); - animationsChanged = true; } else { mixingFrom.animationLast = mixingFrom.nextAnimationLast; mixingFrom.trackLast = mixingFrom.nextTrackLast; @@ -135,7 +134,6 @@ public class AnimationState { * animation state can be applied to multiple skeletons to pose them identically. */ public void apply (Skeleton skeleton) { if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); - if (animationsChanged) animationsChanged(); Array events = this.events; @@ -180,27 +178,17 @@ public class AnimationState { float animationLast = entry.animationLast, animationTime = entry.getAnimationTime(); Array timelines = entry.animation.timelines; - boolean[] timelinesFirst = entry.timelinesFirst.items; - float alphaFull = entry.alpha, alphaMix = entry.alpha * (1 - mix); - if (attachments && drawOrder) { - 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 - 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); - } + boolean[] timelinesFirst = entry.timelinesFirst.items, timelinesLast = entry.timelinesLast.items; + float alphaFull = entry.alpha, alphaMix = alphaFull * (1 - mix); + for (int i = 0, n = timelines.size; i < n; i++) { + Timeline timeline = timelines.get(i); + boolean setupPose = timelinesFirst[i]; + if (!setupPose) { + if (!attachments && timeline instanceof AttachmentTimeline) continue; + if (!drawOrder && timeline instanceof DrawOrderTimeline) continue; } + timeline.apply(skeleton, animationLast, animationTime, events, timelinesLast[i] ? alphaMix : alphaFull, setupPose, + setupPose); } queueEvents(entry, animationTime); @@ -296,8 +284,6 @@ public class AnimationState { } queue.start(entry); - - animationsChanged = true; } /** @see #setAnimation(int, Animation, boolean) */ @@ -371,8 +357,7 @@ public class AnimationState { return entry; } - /** Sets an empty animation for a track, discarding any queued animations, and mixes to it over the specified mix duration. The - * empty animation's pose is the setup pose. */ + /** Sets an empty animation for a track, discarding any queued animations, and mixes to it over the specified mix duration. */ public TrackEntry setEmptyAnimation (int trackIndex, float mixDuration) { TrackEntry entry = setAnimation(trackIndex, emptyAnimation, false); 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 - * 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 * 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 * after {@link AnimationStateListener#dispose(TrackEntry)}. */ public TrackEntry addEmptyAnimation (int trackIndex, float mixDuration, float delay) { + if (delay <= 0) delay -= mixDuration; TrackEntry entry = addAnimation(trackIndex, emptyAnimation, false, delay); entry.mixDuration = mixDuration; entry.trackEnd = mixDuration; return entry; } - /** Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix duration. - * The empty animation's pose is the setup pose. */ + /** Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix + * duration. */ public void setEmptyAnimations (float mixDuration) { queue.drainDisabled = true; for (int i = 0, n = tracks.size; i < n; i++) { @@ -423,15 +409,16 @@ public class AnimationState { entry.attachmentThreshold = 0; entry.drawOrderThreshold = 0; - entry.delay = 0; entry.animationStart = 0; entry.animationEnd = animation.getDuration(); entry.animationLast = -1; entry.nextAnimationLast = -1; + + entry.delay = 0; entry.trackTime = 0; - entry.trackEnd = loop ? Integer.MAX_VALUE : entry.animationEnd; entry.trackLast = -1; entry.nextTrackLast = -1; + entry.trackEnd = loop ? Integer.MAX_VALUE : entry.animationEnd; entry.timeScale = 1; entry.alpha = 1; @@ -451,43 +438,65 @@ public class AnimationState { private void animationsChanged () { animationsChanged = false; - propertyIDs.clear(); + + // Compute timelinesFirst. int i = 0, n = tracks.size; + propertyIDs.clear(); for (; i < n; i++) { TrackEntry entry = tracks.get(i); if (entry == null) continue; if (entry.mixingFrom != null) { - setTimelinesFirst(entry.mixingFrom); - checkTimelinesFirst(entry); + setTimelineUsage(entry.mixingFrom, entry.mixingFrom.timelinesFirst); + checkTimelineUsage(entry, entry.timelinesFirst); } else - setTimelinesFirst(entry); + setTimelineUsage(entry, entry.timelinesFirst); i++; break; } for (; i < n; i++) { TrackEntry entry = tracks.get(i); if (entry == null) continue; - if (entry.mixingFrom != null) checkTimelinesFirst(entry.mixingFrom); - checkTimelinesFirst(entry); + if (entry.mixingFrom != null) checkTimelineUsage(entry.mixingFrom, entry.mixingFrom.timelinesFirst); + 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; Array timelines = entry.animation.timelines; int n = timelines.size; - boolean[] timelinesFirst = entry.timelinesFirst.setSize(n); + boolean[] usage = usageArray.setSize(n); for (int i = 0; i < n; i++) { 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; Array timelines = entry.animation.timelines; int n = timelines.size; - boolean[] timelinesFirst = entry.timelinesFirst.setSize(n); + boolean[] timelinesFirst = usageArray.setSize(n); for (int i = 0; i < n; i++) timelinesFirst[i] = propertyIDs.add(timelines.get(i).getPropertyId()); } @@ -564,10 +573,10 @@ public class AnimationState { int trackIndex; boolean loop; float eventThreshold, attachmentThreshold, drawOrderThreshold; - float delay, trackTime, trackLast, trackEnd, animationStart, animationEnd, animationLast, timeScale; - float nextTrackLast, nextAnimationLast; + float animationStart, animationEnd, animationLast, nextAnimationLast; + float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale; float alpha, mixTime, mixDuration; - final BooleanArray timelinesFirst = new BooleanArray(); + final BooleanArray timelinesFirst = new BooleanArray(), timelinesLast = new BooleanArray(); public void reset () { next = null; @@ -575,6 +584,7 @@ public class AnimationState { animation = null; listener = null; timelinesFirst.clear(); + timelinesLast.clear(); } public int getTrackIndex () { @@ -786,49 +796,41 @@ public class AnimationState { } } - static private class EventQueue { - static private final int START = 0, EVENT = 1, COMPLETE = 2, INTERRUPT = 3, END = 4, DISPOSE = 5; - - private final Array listeners; - private final Pool trackEntryPool; + class EventQueue { private final Array objects = new Array(); - private final IntArray eventTypes = new IntArray(); // If > 0 it's loop count for a complete event. boolean drainDisabled; - public EventQueue (Array listeners, Pool trackEntryPool) { - this.listeners = listeners; - this.trackEntryPool = trackEntryPool; - } - public void start (TrackEntry entry) { + objects.add(EventType.start); objects.add(entry); - eventTypes.add(START); - } - - 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); + animationsChanged = true; } public void interrupt (TrackEntry entry) { + objects.add(EventType.interrupt); objects.add(entry); - eventTypes.add(INTERRUPT); } public void end (TrackEntry entry) { + objects.add(EventType.end); objects.add(entry); - eventTypes.add(END); + animationsChanged = true; } public void dispose (TrackEntry entry) { + objects.add(EventType.dispose); 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 () { @@ -836,43 +838,43 @@ public class AnimationState { drainDisabled = true; Array objects = this.objects; - IntArray eventTypes = this.eventTypes; - Array listeners = this.listeners; - for (int e = 0, o = 0; e < eventTypes.size; e++, o++) { - TrackEntry entry = (TrackEntry)objects.get(o); - int eventType = eventTypes.get(e); - switch (eventType) { - case START: + Array listeners = AnimationState.this.listeners; + for (int i = 0; i < objects.size; i += 2) { + EventType type = (EventType)objects.get(i); + TrackEntry entry = (TrackEntry)objects.get(i + 1); + switch (type) { + case start: if (entry.listener != null) entry.listener.end(entry); - for (int i = 0; i < listeners.size; i++) - listeners.get(i).start(entry); + for (int ii = 0; ii < listeners.size; ii++) + listeners.get(ii).start(entry); break; - case EVENT: - 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: + case interrupt: if (entry.listener != null) entry.listener.end(entry); - for (int i = 0; i < listeners.size; i++) - listeners.get(i).interrupt(entry); + for (int ii = 0; ii < listeners.size; ii++) + listeners.get(ii).interrupt(entry); break; - case END: + case end: if (entry.listener != null) entry.listener.end(entry); - for (int i = 0; i < listeners.size; i++) - listeners.get(i).end(entry); + for (int ii = 0; ii < listeners.size; ii++) + listeners.get(ii).end(entry); // Fall through. - case DISPOSE: + case dispose: if (entry.listener != null) entry.listener.end(entry); - for (int i = 0; i < listeners.size; i++) - listeners.get(i).dispose(entry); + for (int ii = 0; ii < listeners.size; ii++) + listeners.get(ii).dispose(entry); trackEntryPool.free(entry); break; - default: + case complete: if (entry.listener != null) entry.listener.complete(entry); - for (int i = 0; i < listeners.size; i++) - listeners.get(i).complete(entry); + for (int ii = 0; ii < listeners.size; ii++) + 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(); @@ -882,15 +884,18 @@ public class AnimationState { public void clear () { objects.clear(); - eventTypes.clear(); } } + static private enum EventType { + start, interrupt, end, dispose, complete, event + } + static public interface AnimationStateListener { /** Invoked when this entry has been set as the current 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. */ public void interrupt (TrackEntry entry); @@ -921,10 +926,10 @@ public class AnimationState { 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) { } } } diff --git a/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java b/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java index 01280a876..b0c287c0d 100644 --- a/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java +++ b/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java @@ -283,7 +283,7 @@ public class SkeletonViewer extends ApplicationAdapter { shapes.setColor(Color.CYAN); 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; shapes.setColor(Color.RED); shapes.line(x, 0, x, 20);