From 0a8896a3e2ece9f43f7ca13351e85be2e001f887 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Sat, 20 Aug 2016 11:51:56 +0200 Subject: [PATCH] More AnimationState refactoring. - Added `animationStart` so a portion of an animation can be looped. - Changed timing fields to: trackTime, trackLast, trackEnd, animationStart, animationEnd, animationLast - Removed default *Threshold fields. People can just set it on the track entry if they don't like the defaults. - Removed `loopCount` from complete event. People can calculate it based on track time. #621 closes #535 closes #593 --- .../spine/AnimationStateTest.java | 188 +++++++++----- .../esotericsoftware/spine/SimpleTest2.java | 4 +- .../spine/AnimationState.java | 237 +++++++++--------- .../spine/SkeletonViewer.java | 2 +- 4 files changed, 244 insertions(+), 187 deletions(-) diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AnimationStateTest.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AnimationStateTest.java index 441277d52..84583682d 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AnimationStateTest.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/AnimationStateTest.java @@ -75,8 +75,8 @@ public class AnimationStateTest { add(actual("interrupt", entry)); } - public void complete (TrackEntry entry, int loopCount) { - add(actual("complete " + loopCount, entry)); + public void complete (TrackEntry entry) { + add(actual("complete", entry)); } public void end (TrackEntry entry) { @@ -84,17 +84,16 @@ public class AnimationStateTest { } private void add (Result result) { - String error = "PASS"; + String message = result.toString(); if (actual.size >= expected.size) { - error = "FAIL: "; + message += "FAIL: "; fail = true; } else if (!expected.get(actual.size).equals(result)) { - error = "FAIL: " + expected.get(actual.size); + message += "FAIL: " + expected.get(actual.size); fail = true; - } - buffer.append(result.toString()); - buffer.append(error); - buffer.append('\n'); + } else + message += "PASS"; + log(message); actual.add(result); } }; @@ -102,7 +101,6 @@ public class AnimationStateTest { final SkeletonData skeletonData; final Array actual = new Array(); final Array expected = new Array(); - final StringBuilder buffer = new StringBuilder(512); AnimationStateData stateData; AnimationState state; @@ -113,12 +111,14 @@ public class AnimationStateTest { AnimationStateTest () { skeletonData = json.readSkeletonData(new LwjglFileHandle("test/test.json", FileType.Internal)); + TrackEntry entry; + setup("0.1 time step", // 1 expect(0, "start", 0, 0), // expect(0, "event 0", 0, 0), // expect(0, "event 14", 0.5f, 0.5f), // expect(0, "event 30", 1, 1), // - expect(0, "complete 1", 1, 1), // + expect(0, "complete", 1, 1), // expect(0, "end", 1, 1.1f) // ); state.setAnimation(0, "events1", false); @@ -128,9 +128,9 @@ public class AnimationStateTest { expect(0, "start", 0, 0), // expect(0, "event 0", 0, 0), // expect(0, "event 14", 0.467f, 0.467f), // - expect(0, "event 30", 1.017f, 1.017f), // - expect(0, "complete 1", 1.017f, 1.017f), // - expect(0, "end", 1.017f, 1.033f) // + expect(0, "event 30", 1, 1), // + expect(0, "complete", 1, 1), // + expect(0, "end", 1, 1.017f) // ); state.setAnimation(0, "events1", false); run(1 / 60f, 1000); @@ -140,7 +140,7 @@ public class AnimationStateTest { expect(0, "event 0", 0, 0), // expect(0, "event 14", 30, 30), // expect(0, "event 30", 30, 30), // - expect(0, "complete 1", 30, 30), // + expect(0, "complete", 30, 30), // expect(0, "end", 30, 60) // ); state.setAnimation(0, "events1", false); @@ -151,7 +151,7 @@ public class AnimationStateTest { expect(0, "event 0", 0, 0), // expect(0, "event 14", 1, 1), // expect(0, "event 30", 1, 1), // - expect(0, "complete 1", 1, 1), // + expect(0, "complete", 1, 1), // expect(0, "end", 1, 2) // ); state.setAnimation(0, "events1", false); @@ -162,7 +162,7 @@ public class AnimationStateTest { expect(0, "event 0", 0, 0), // expect(0, "event 14", 0.5f, 0.5f), // expect(0, "event 30", 1, 1), // - expect(0, "complete 1", 1, 1), // + expect(0, "complete", 1, 1), // expect(1, "start", 0.1f, 1.1f), // @@ -172,7 +172,7 @@ public class AnimationStateTest { expect(1, "event 0", 0.1f, 1.1f), // expect(1, "event 14", 0.5f, 1.5f), // expect(1, "event 30", 1, 2), // - expect(1, "complete 1", 1, 2), // + expect(1, "complete", 1, 2), // expect(0, "start", 0.1f, 2.1f), // @@ -182,7 +182,7 @@ public class AnimationStateTest { expect(0, "event 0", 0.1f, 2.1f), // expect(0, "event 14", 0.5f, 2.5f), // expect(0, "event 30", 1, 3), // - expect(0, "complete 1", 1, 3), // + expect(0, "complete", 1, 3), // expect(0, "end", 1, 3.1f) // ); state.setAnimation(0, "events1", false); @@ -203,7 +203,7 @@ public class AnimationStateTest { expect(1, "event 0", 0.1f, 0.6f), // expect(1, "event 14", 0.5f, 1.0f), // expect(1, "event 30", 1, 1.5f), // - expect(1, "complete 1", 1, 1.5f), // + expect(1, "complete", 1, 1.5f), // expect(1, "end", 1, 1.6f) // ); state.setAnimation(0, "events1", false); @@ -218,7 +218,7 @@ public class AnimationStateTest { expect(1, "start", 0.1f, 1), // expect(0, "interrupt", 1, 1), // - expect(0, "complete 1", 1, 1), // + expect(0, "complete", 1, 1), // expect(1, "event 0", 0.1f, 1), // expect(1, "event 14", 0.5f, 1.4f), // @@ -226,7 +226,7 @@ public class AnimationStateTest { expect(0, "end", 1.6f, 1.6f), // expect(1, "event 30", 1, 1.9f), // - expect(1, "complete 1", 1, 1.9f), // + expect(1, "complete", 1, 1.9f), // expect(1, "end", 1, 2) // ); stateData.setMix("events1", "events2", 0.7f); @@ -245,11 +245,11 @@ public class AnimationStateTest { expect(1, "event 0", 0.1f, 0.5f), // expect(1, "event 14", 0.5f, 0.9f), // - expect(0, "complete 1", 1, 1), // + expect(0, "complete", 1, 1), // expect(0, "end", 1.1f, 1.1f), // expect(1, "event 30", 1, 1.4f), // - expect(1, "complete 1", 1, 1.4f), // + expect(1, "complete", 1, 1.4f), // expect(1, "end", 1, 1.5f) // ); stateData.setDefaultMix(0.7f); @@ -269,43 +269,43 @@ public class AnimationStateTest { expect(1, "event 0", 0.1f, 0.5f), // expect(1, "event 14", 0.5f, 0.9f), // - expect(0, "complete 1", 1, 1), // + expect(0, "complete", 1, 1), // expect(0, "end", 1.1f, 1.1f), // expect(1, "event 30", 1, 1.4f), // - expect(1, "complete 1", 1, 1.4f), // + expect(1, "complete", 1, 1.4f), // expect(1, "end", 1, 1.5f) // ); stateData.setMix("events1", "events2", 0.7f); - state.setEventThreshold(0.5f); - state.setAnimation(0, "events1", false); + state.setAnimation(0, "events1", false).setEventThreshold(0.5f); state.addAnimation(0, "events2", false, 0.4f); run(0.1f, 1000); setup("event threshold, all animation 0 events fire during mix", // 10 expect(0, "start", 0, 0), // expect(0, "event 0", 0, 0), // - - expect(1, "start", 0.1f, 0.5f), // - - expect(0, "interrupt", 0.5f, 0.5f), // expect(0, "event 14", 0.5f, 0.5f), // - expect(1, "event 0", 0.1f, 0.5f), // - expect(1, "event 14", 0.5f, 0.9f), // + expect(1, "start", 0.1f, 0.9f), // + + expect(0, "interrupt", 0.9f, 0.9f), // + + expect(1, "event 0", 0.1f, 0.9f), // expect(0, "event 30", 1, 1), // - expect(0, "complete 1", 1, 1), // - expect(0, "end", 1.1f, 1.1f), // + expect(0, "complete", 1, 1), // + expect(0, "event 0", 1, 1), // - expect(1, "event 30", 1, 1.4f), // - expect(1, "complete 1", 1, 1.4f), // - expect(1, "end", 1, 1.5f) // + expect(1, "event 14", 0.5f, 1.3f), // + + expect(0, "end", 1.5f, 1.5f), // + + expect(1, "event 30", 1, 1.8f), // + expect(1, "complete", 1, 1.8f), // + expect(1, "end", 1, 1.9f) // ); - stateData.setMix("events1", "events2", 0.7f); - state.setEventThreshold(1); - state.setAnimation(0, "events1", false); - state.addAnimation(0, "events2", false, 0.4f); + state.setAnimation(0, "events1", true).setEventThreshold(1); + state.addAnimation(0, "events2", false, 0.8f).setMixDuration(0.7f); run(0.1f, 1000); setup("looping", // 11 @@ -313,22 +313,30 @@ public class AnimationStateTest { expect(0, "event 0", 0, 0), // expect(0, "event 14", 0.5f, 0.5f), // expect(0, "event 30", 1, 1), // - expect(0, "complete 1", 1, 1), // + expect(0, "complete", 1, 1), // expect(0, "event 0", 1, 1), // expect(0, "event 14", 1.5f, 1.5f), // expect(0, "event 30", 2, 2), // - expect(0, "complete 2", 2, 2), // - expect(0, "event 0", 2, 2) // + expect(0, "complete", 2, 2), // + expect(0, "event 0", 2, 2), // + expect(0, "event 14", 2.5f, 2.5f), // + expect(0, "event 30", 3, 3), // + expect(0, "complete", 3, 3), // + expect(0, "event 0", 3, 3), // + expect(0, "event 14", 3.5f, 3.5f), // + expect(0, "event 30", 4, 4), // + expect(0, "complete", 4, 4), // + expect(0, "event 0", 4, 4) // ); state.setAnimation(0, "events1", true); - run(0.1f, 2); + run(0.1f, 4); setup("not looping, update past animation 0 duration", // 12 expect(0, "start", 0, 0), // expect(0, "event 0", 0, 0), // expect(0, "event 14", 0.5f, 0.5f), // expect(0, "event 30", 1, 1), // - expect(0, "complete 1", 1, 1), // + expect(0, "complete", 1, 1), // expect(1, "start", 0.1f, 2.1f), // @@ -338,7 +346,7 @@ public class AnimationStateTest { expect(1, "event 0", 0.1f, 2.1f), // expect(1, "event 14", 0.5f, 2.5f), // expect(1, "event 30", 1, 3), // - expect(1, "complete 1", 1, 3), // + expect(1, "complete", 1, 3), // expect(1, "end", 1, 3.1f) // ); state.setAnimation(0, "events1", false); @@ -350,11 +358,11 @@ public class AnimationStateTest { expect(0, "event 0", 0, 0), // expect(0, "event 14", 0.5f, 0.5f), // expect(0, "event 30", 1, 1), // - expect(0, "complete 1", 1, 1), // + expect(0, "complete", 1, 1), // expect(0, "event 0", 1, 1), // expect(0, "event 14", 1.5f, 1.5f), // expect(0, "event 30", 2, 2), // - expect(0, "complete 2", 2, 2), // + expect(0, "complete", 2, 2), // expect(0, "event 0", 2, 2), // expect(1, "start", 0.1f, 2.1f), // @@ -365,7 +373,7 @@ public class AnimationStateTest { expect(1, "event 0", 0.1f, 2.1f), // expect(1, "event 14", 0.5f, 2.5f), // expect(1, "event 30", 1, 3), // - expect(1, "complete 1", 1, 3), // + expect(1, "complete", 1, 3), // expect(1, "end", 1, 3.1f) // ); state.setAnimation(0, "events1", true); @@ -376,11 +384,11 @@ public class AnimationStateTest { }); setup("add animation on empty track", // 14 - expect(0, "start", 0, 0), // s + expect(0, "start", 0, 0), // expect(0, "event 0", 0, 0), // expect(0, "event 14", 0.5f, 0.5f), // expect(0, "event 30", 1, 1), // - expect(0, "complete 1", 1, 1), // + expect(0, "complete", 1, 1), // expect(0, "end", 1, 1.1f) // ); state.addAnimation(0, "events1", false, 0); @@ -391,12 +399,52 @@ public class AnimationStateTest { expect(0, "event 0", 0, 0), // expect(0, "event 14", 0.5f, 0.5f), // expect(0, "event 30", 1, 1), // - expect(0, "complete 1", 9.1f, 9.1f), // - expect(0, "end", 9.1f, 9.2f) // + expect(0, "complete", 1, 1), // + expect(0, "end", 9f, 9.1f) // ); - state.setAnimation(0, "events1", false).endTime = 9; + state.setAnimation(0, "events1", false).setTrackEnd(9); run(0.1f, 10); + setup("looping with animation start", // 16 + expect(0, "start", 0, 0), // + expect(0, "event 30", 0.4f, 0.4f), // + expect(0, "complete", 0.4f, 0.4f), // + expect(0, "event 30", 0.8f, 0.8f), // + expect(0, "complete", 0.8f, 0.8f), // + expect(0, "event 30", 1.2f, 1.2f), // + expect(0, "complete", 1.2f, 1.2f) // + ); + entry = state.setAnimation(0, "events1", true); + entry.setAnimationLast(0.6f); + entry.setAnimationStart(0.6f); + run(0.1f, 1.4f); + + setup("looping with animation start and end", // 17 + expect(0, "start", 0, 0), // + expect(0, "event 14", 0.3f, 0.3f), // + expect(0, "complete", 0.6f, 0.6f), // + expect(0, "event 14", 0.9f, 0.9f), // + expect(0, "complete", 1.2f, 1.2f), // + expect(0, "event 14", 1.5f, 1.5f) // + ); + entry = state.setAnimation(0, "events1", true); + entry.setAnimationStart(0.2f); + entry.setAnimationLast(0.2f); + entry.setAnimationEnd(0.8f); + run(0.1f, 1.8f); + + setup("non-looping with animation start and end", // 18 + expect(0, "start", 0, 0), // + expect(0, "event 14", 0.3f, 0.3f), // + expect(0, "complete", 0.6f, 0.6f), // + expect(0, "end", 1, 1.1f) // + ); + entry = state.setAnimation(0, "events1", false); + entry.setAnimationStart(0.2f); + entry.setAnimationLast(0.2f); + entry.setAnimationEnd(0.8f); + run(0.1f, 1.8f); + System.out.println("AnimationState tests passed."); } @@ -408,9 +456,8 @@ public class AnimationStateTest { state.addListener(stateListener); time = 0; fail = false; - buffer.setLength(0); - buffer.append(test + ": " + description + "\n"); - buffer.append(String.format("%-3s%-12s%-7s%-7s%-7s\n", "#", "EVENT", "TRACK", "TOTAL", "RESULT")); + log(test + ": " + description); + log(String.format("%-3s%-12s%-7s%-7s%-7s", "#", "EVENT", "TRACK", "TOTAL", "RESULT")); } void run (float incr, float endTime) { @@ -425,22 +472,29 @@ public class AnimationStateTest { if (listener != null) listener.frame(time); skeleton.update(incr); state.update(incr); + + // Reduce float error for tests. + for (TrackEntry entry : state.getTracks()) { + if (entry == null) continue; + entry.trackTime = Math.round(entry.trackTime * 1000000) / 1000000f; + if (entry.mixingFrom != null) + entry.mixingFrom.trackTime = Math.round(entry.mixingFrom.trackTime * 1000000) / 1000000f; + } + state.apply(skeleton); } // Expecting more than actual is a failure. for (int i = actual.size, n = expected.size; i < n; i++) { - buffer.append(String.format("%-29s", "")); - buffer.append("FAIL: " + expected.get(i) + "\n"); + log(String.format("%-29s", "") + "FAIL: " + expected.get(i)); fail = true; } actual.clear(); expected.clear(); if (fail) { - System.out.println(buffer); System.out.println("TEST " + test + " FAILED!"); System.exit(0); } - System.out.println(buffer); + System.out.println(); } Result expect (int animationIndex, String name, float trackTime, float totalTime) { @@ -456,11 +510,15 @@ public class AnimationStateTest { Result result = new Result(); result.name = name; result.animationIndex = skeletonData.getAnimations().indexOf(entry.animation, true); - result.trackTime = Math.round(entry.time * 1000) / 1000f; + result.trackTime = Math.round(entry.trackTime * 1000) / 1000f; result.totalTime = Math.round(time * 1000) / 1000f; return result; } + void log (String message) { + System.out.println(message); + } + class Result { String name; int animationIndex; diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/SimpleTest2.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/SimpleTest2.java index e907e751f..a7f1c8b24 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/SimpleTest2.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/SimpleTest2.java @@ -88,8 +88,8 @@ public class SimpleTest2 extends ApplicationAdapter { .println(entry.getTrackIndex() + " event: " + entry + ", " + event.getData().getName() + ", " + event.getInt()); } - public void complete (TrackEntry entry, int loopCount) { - System.out.println(entry.getTrackIndex() + " complete: " + entry + ", " + loopCount); + public void complete (TrackEntry entry) { + System.out.println(entry.getTrackIndex() + " complete: " + entry); } public void interrupt (TrackEntry entry) { 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 093c0a2e2..115afa53e 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java @@ -46,7 +46,6 @@ public class AnimationState { private final Array events = new Array(); final Array listeners = new Array(); private float timeScale = 1; - private float eventThreshold, attachmentThreshold, drawOrderThreshold; final Pool trackEntryPool = new Pool() { protected Object newObject () { return new TrackEntry(); @@ -75,24 +74,24 @@ public class AnimationState { TrackEntry next = current.next; if (next != null) { // When the next entry's delay is passed, change to the next entry. - float nextTime = current.lastTime - next.delay; + float nextTime = current.trackLast - next.delay; if (nextTime >= 0) { - next.time = nextTime + delta * next.timeScale; - current.time += currentDelta; + next.trackTime = nextTime + delta * next.timeScale; + current.trackTime += currentDelta; setCurrent(i, next); if (next.mixingFrom != null) next.mixTime += currentDelta; continue; } - } else if (!current.loop && current.lastTime >= current.endTime) { - // End non-looping animation when it reaches its end time and there is no next entry. + } else if (!current.loop && current.trackLast >= current.trackEnd) { + // Clear a non-looping animation when it reaches its end time and there is no next entry. clearTrack(i); continue; } - current.time += currentDelta; + current.trackTime += currentDelta; if (current.mixingFrom != null) { float mixingFromDelta = delta * current.mixingFrom.timeScale; - current.mixingFrom.time += mixingFromDelta; + current.mixingFrom.trackTime += mixingFromDelta; current.mixTime += mixingFromDelta; } } @@ -108,12 +107,7 @@ public class AnimationState { TrackEntry current = tracks.get(i); if (current == null) continue; - float time = current.time, lastTime = current.lastTime, endTime = current.endTime, alpha = current.alpha; - if (!current.loop) { - if (time > endTime) time = endTime; - if (lastTime > endTime) lastTime = endTime; - } - + float alpha = current.alpha; if (current.mixingFrom != null) { alpha *= current.mixTime / current.mixDuration; applyMixingFrom(current.mixingFrom, skeleton, alpha); @@ -123,71 +117,70 @@ public class AnimationState { current.mixingFrom = null; } } - current.animation.mix(skeleton, lastTime, time, current.loop, events, alpha); - queueEvents(current, lastTime, time, endTime); - current.lastTime = current.time; + float animationLast = current.animationLast, animationTime = current.getAnimationTime(); + Array timelines = current.animation.timelines; + for (int ii = 0, n = timelines.size; ii < n; ii++) + timelines.get(ii).apply(skeleton, animationLast, animationTime, events, alpha); + queueEvents(current, animationTime); + current.animationLast = animationTime; + current.trackLast = current.trackTime; } queue.drain(); } private void applyMixingFrom (TrackEntry entry, Skeleton skeleton, float mix) { - float time = entry.time, lastTime = entry.lastTime, endTime = entry.endTime, alpha = entry.alpha; - float animationTime; - if (entry.loop) { - float duration = entry.animation.duration; - if (duration != 0) { - animationTime = time % duration; - if (lastTime > 0) lastTime %= duration; - } else - animationTime = time; - } else { - if (time > endTime) time = endTime; - if (lastTime > endTime) lastTime = endTime; - animationTime = time; - } - Array events = mix < entry.eventThreshold ? this.events : null; boolean attachments = mix < entry.attachmentThreshold, drawOrder = mix < entry.drawOrderThreshold; + float animationLast = entry.animationLast, animationTime = entry.getAnimationTime(); Array timelines = entry.animation.timelines; + float alpha = entry.alpha; if (attachments && drawOrder) { for (int i = 0, n = timelines.size; i < n; i++) - timelines.get(i).apply(skeleton, lastTime, animationTime, events, alpha); + timelines.get(i).apply(skeleton, animationLast, animationTime, events, alpha); } else { for (int i = 0, n = timelines.size; i < n; i++) { Timeline timeline = timelines.get(i); if (!attachments && timeline instanceof AttachmentTimeline) continue; if (!drawOrder && timeline instanceof DrawOrderTimeline) continue; - timeline.apply(skeleton, lastTime, animationTime, events, alpha); + timeline.apply(skeleton, animationLast, animationTime, events, alpha); } } - queueEvents(entry, entry.lastTime, time, entry.endTime); - entry.lastTime = entry.time; + queueEvents(entry, animationTime); + entry.animationLast = animationTime; + entry.trackLast = entry.trackTime; } - private void queueEvents (TrackEntry entry, float lastTime, float time, float endTime) { - Array events = this.events; - int n = events.size; + private void queueEvents (TrackEntry entry, float animationTime) { + float animationStart = entry.animationStart, animationEnd = entry.animationEnd; + float duration = animationEnd - animationStart; + float trackLastWrapped = entry.trackLast % duration; // Queue events before complete. - float lastTimeWrapped = lastTime % endTime; - int i = 0; + Array events = this.events; + int i = 0, n = events.size; for (; i < n; i++) { Event event = events.get(i); - if (events.get(i).time < lastTimeWrapped) break; + if (event.time < trackLastWrapped) break; + if (event.time > animationEnd) continue; // Discard events out of animation start/end. queue.event(entry, event); } - // Queue complete if completed the animation or a loop iteration. - if (entry.loop ? (lastTime % endTime > time % endTime) : (lastTime < endTime && time >= endTime)) - queue.complete(entry, (int)(time / endTime)); + // Queue complete if completed a loop iteration or the animation. + if (entry.loop ? (trackLastWrapped > entry.trackTime % duration) + : (animationTime >= animationEnd && entry.animationLast < animationEnd)) { + queue.complete(entry); + } // Queue events after complete. - for (; i < n; i++) + for (; i < n; i++) { + Event event = events.get(i); + if (event.time < animationStart) continue; // Discard events out of animation start/end. queue.event(entry, events.get(i)); + } events.clear(); } @@ -266,8 +259,11 @@ public class AnimationState { * after {@link AnimationStateListener#end(TrackEntry)}. */ public TrackEntry setAnimation (int trackIndex, Animation animation, boolean loop) { if (animation == null) throw new IllegalArgumentException("animation cannot be null."); - clearTrack(trackIndex); - TrackEntry entry = trackEntry(trackIndex, animation, loop, null); + + TrackEntry current = expandToIndex(trackIndex); + if (current != null) freeAll(current.next); + + TrackEntry entry = trackEntry(trackIndex, animation, loop, current); setCurrent(trackIndex, entry); return entry; } @@ -297,7 +293,11 @@ public class AnimationState { if (last != null) { last.next = entry; - if (delay <= 0) delay += last.endTime * (1 + (int)(last.time / last.endTime)) - data.getMix(last.animation, animation); + if (delay <= 0) { + float duration = last.animationEnd - last.animationStart; + if (duration != 0) + delay += duration * (1 + (int)(last.trackTime / duration)) - data.getMix(last.animation, animation); + } } else { setCurrent(trackIndex, entry); if (delay <= 0) delay = 0; @@ -314,14 +314,17 @@ public class AnimationState { entry.animation = animation; entry.loop = loop; - entry.eventThreshold = eventThreshold; - entry.attachmentThreshold = attachmentThreshold; - entry.drawOrderThreshold = drawOrderThreshold; + entry.eventThreshold = 0; + entry.attachmentThreshold = 0; + entry.drawOrderThreshold = 0; entry.delay = 0; - entry.endTime = animation.getDuration(); - entry.time = 0; - entry.lastTime = -1; + entry.trackTime = 0; + entry.trackEnd = animation.getDuration(); + entry.trackLast = -1; + entry.animationStart = 0; + entry.animationEnd = entry.trackEnd; + entry.animationLast = -1; entry.timeScale = 1; entry.alpha = 1; @@ -360,7 +363,7 @@ public class AnimationState { } /** Multiplier for the delta time when the animation state is updated, causing time for all animations to move slower or - * faster. */ + * faster. Defaults to 1. */ public float getTimeScale () { return timeScale; } @@ -369,38 +372,12 @@ public class AnimationState { this.timeScale = timeScale; } - /** @see TrackEntry#getEventThreshold() */ - public float getEventThreshold () { - return eventThreshold; - } - - public void setEventThreshold (float eventThreshold) { - this.eventThreshold = eventThreshold; - } - - /** @see TrackEntry#getAttachmentThreshold() */ - public float getAttachmentThreshold () { - return attachmentThreshold; - } - - public void setAttachmentThreshold (float attachmentThreshold) { - this.attachmentThreshold = attachmentThreshold; - } - - /** @see TrackEntry#getDrawOrderThreshold() */ - public float getDrawOrderThreshold () { - return drawOrderThreshold; - } - - public void setDrawOrderThreshold (float drawOrderThreshold) { - this.drawOrderThreshold = drawOrderThreshold; - } - public AnimationStateData getData () { return data; } public void setData (AnimationStateData data) { + if (data == null) throw new IllegalArgumentException("data cannot be null."); this.data = data; } @@ -429,8 +406,7 @@ public class AnimationState { int trackIndex; boolean loop; float eventThreshold, attachmentThreshold, drawOrderThreshold; - /** Merow. */ - float delay, endTime, time, lastTime, timeScale; + float delay, trackTime, trackLast, trackEnd, animationStart, animationEnd, animationLast, timeScale; float alpha; float mixTime, mixDuration; @@ -441,6 +417,15 @@ public class AnimationState { listener = null; } + public float getAnimationTime () { + if (loop) { + float duration = animationEnd - animationStart; + if (duration == 0) return 0; + return (trackTime % duration) + animationStart; + } + return Math.min(trackTime + animationStart, animationEnd); + } + public int getTrackIndex () { return trackIndex; } @@ -470,42 +455,56 @@ public class AnimationState { this.delay = delay; } + /** Current time in seconds for this track entry. When changing the track time, it often makes sense to also change + * {@link #getAnimationLast()} to control when timelines will trigger. Defaults to 0. */ + public float getTrackTime () { + return trackTime; + } + + public void setTrackTime (float trackTime) { + this.trackTime = trackTime; + } + + public float getTrackEnd () { + return trackEnd; + } + + public void setTrackEnd (float trackEnd) { + this.trackEnd = trackEnd; + } + + /** Seconds when this animation starts, both initially and after looping. Defaults to 0. */ + public float getAnimationStart () { + return animationStart; + } + + public void setAnimationStart (float animationStart) { + this.animationStart = animationStart; + } + /** Seconds when this animation ends, causing the next animation to start or this animation to loop. Defaults to the - * animation duration. - *

- * When a non-looping animation reaches the end time and no more animations are queued, it is removed from the track. The - * end time may be set beyond the animation duration to keep applying the animation instead of removing it. */ - public float getEndTime () { - return endTime; + * animation duration. */ + public float getAnimationEnd () { + return animationEnd; } - public void setEndTime (float endTime) { - this.endTime = endTime; - } - - /** Current time in seconds for this animation. When changing the time, it often makes sense to also change last time to - * control when timelines will trigger. */ - public float getTime () { - return time; - } - - public void setTime (float time) { - this.time = time; + public void setAnimationEnd (float animationEnd) { + this.animationEnd = animationEnd; } /** 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 * -1 to ensure triggers on frame 0 happen the first time this animation is applied. */ - public float getLastTime () { - return lastTime; + public float getAnimationLast () { + return animationLast; } - public void setLastTime (float lastTime) { - this.lastTime = lastTime; + public void setAnimationLast (float animationLast) { + this.animationLast = animationLast; } /** Multiplier for the delta time when the animation state is updated, causing time for this animation to move slower or - * faster. */ + * faster. Defaults to 1. */ public float getTimeScale () { return timeScale; } @@ -579,7 +578,7 @@ public class AnimationState { /** Returns true if at least one loop has been completed (ie time >= end time). */ public boolean isComplete () { - return time >= endTime; + return trackTime >= trackEnd; } /** Seconds from zero to the mix duration when mixing from the previous animation to this animation. */ @@ -591,7 +590,8 @@ public class AnimationState { this.mixTime = mixTime; } - /** Seconds for mixing from the previous animation to this animation. */ + /** Seconds for mixing from the previous animation to this animation. Defaults to the value provided by + * {@link AnimationStateData} based on the animation before this animation (if any). */ public float getMixDuration () { return mixDuration; } @@ -612,7 +612,7 @@ public class AnimationState { } static private class EventQueue { - static private final int START = -3, EVENT = -2, INTERRUPT = -1, END = 0; + static private final int START = 0, EVENT = 1, COMPLETE = 2, INTERRUPT = 3, END = 4; private final Array listeners; private final Pool trackEntryPool; @@ -636,9 +636,9 @@ public class AnimationState { eventTypes.add(EVENT); } - public void complete (TrackEntry entry, int loopCount) { + public void complete (TrackEntry entry) { objects.add(entry); - eventTypes.add(loopCount); + eventTypes.add(COMPLETE); } public void interrupt (TrackEntry entry) { @@ -685,9 +685,9 @@ public class AnimationState { trackEntryPool.free(entry); break; default: - if (entry.listener != null) entry.listener.complete(entry, eventType); + if (entry.listener != null) entry.listener.complete(entry); for (int i = 0; i < listeners.size; i++) - listeners.get(i).complete(entry, eventType); + listeners.get(i).complete(entry); } } clear(); @@ -713,9 +713,8 @@ public class AnimationState { * should be kept because it may be reused. */ public void end (TrackEntry entry); - /** Invoked every time this animation completes a loop. - * @param loopCount The number of times the animation reached the end. */ - public void complete (TrackEntry entry, int loopCount); + /** Invoked every time this animation completes a loop. */ + public void complete (TrackEntry entry); /** Invoked when this animation triggers an event. */ public void event (TrackEntry entry, Event event); @@ -725,7 +724,7 @@ public class AnimationState { public void event (TrackEntry entry, Event event) { } - public void complete (TrackEntry entry, int loopCount) { + public void complete (TrackEntry entry) { } public void start (TrackEntry entry) { 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 6a51a36c9..a9239904e 100644 --- a/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java +++ b/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java @@ -264,7 +264,7 @@ public class SkeletonViewer extends ApplicationAdapter { ShapeRenderer shapes = debugRenderer.getShapeRenderer(); TrackEntry entry = state.getCurrent(0); if (entry != null) { - float percent = entry.getTime() / entry.getEndTime(); + float percent = entry.getTrackTime() / entry.getTrackEnd(); if (entry.getLoop()) percent %= 1; float x = ui.window.getRight() + (Gdx.graphics.getWidth() - ui.window.getRight()) * percent; shapes.setColor(Color.CYAN);