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 3e931078b..36a073bdd 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 @@ -530,6 +530,33 @@ public class AnimationStateTest { state.addAnimation(0, "events1", false, 5); run(0.1f, 10, null); + setup("setAnimation during AnimationStateListener"); // 23 + state.addListener(new AnimationStateListener() { + public void start (TrackEntry entry) { + if (entry.getAnimation().getName().equals("events1")) state.setAnimation(1, "events2", false); + } + + public void interrupt (TrackEntry entry) { + state.addAnimation(3, "events2", false, 0); + } + + public void event (TrackEntry entry, Event event) { + if (entry.getTrackIndex() != 2) state.setAnimation(2, "events2", false); + } + + public void end (TrackEntry entry) { + if (entry.getAnimation().getName().equals("events1")) state.setAnimation(0, "events2", false); + } + + public void complete (TrackEntry entry) { + if (entry.getAnimation().getName().equals("events1")) state.setAnimation(1, "events2", false); + } + }); + state.addAnimation(0, "events1", false, 0); + state.addAnimation(0, "events2", false, 0); + state.setAnimation(1, "events2", false); + run(0.1f, 10, null); + System.out.println("AnimationState tests passed."); } @@ -538,11 +565,13 @@ public class AnimationStateTest { expected.addAll(expectedArray); stateData = new AnimationStateData(skeletonData); state = new AnimationState(stateData); - state.addListener(stateListener); time = 0; fail = false; log(test + ": " + description); - log(String.format("%-3s%-12s%-7s%-7s%-7s", "#", "EVENT", "TRACK", "TOTAL", "RESULT")); + if (expectedArray.length > 0) { + state.addListener(stateListener); + log(String.format("%-3s%-12s%-7s%-7s%-7s", "#", "EVENT", "TRACK", "TOTAL", "RESULT")); + } } void run (float incr, float endTime, TestListener listener) { diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/BonePlotting.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/BonePlotting.java index b96161237..d6a56e548 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/BonePlotting.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/BonePlotting.java @@ -65,7 +65,7 @@ public class BonePlotting { for (Animation animation : skeletonData.getAnimations()) { float time = 0; while (time < animation.getDuration()) { - animation.apply(skeleton, time, time, false, null); + animation.apply(skeleton, time, time, false, null, 1, false); skeleton.updateWorldTransform(); System.out .println(animation.getName() + "," + bone.getWorldX() + "," + bone.getWorldY() + "," + bone.getWorldRotationX()); diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/Box2DExample.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/Box2DExample.java index b1b1c6b73..22254da97 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/Box2DExample.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/Box2DExample.java @@ -116,9 +116,8 @@ public class Box2DExample extends ApplicationAdapter { Box2dAttachment attachment = (Box2dAttachment)slot.getAttachment(); PolygonShape boxPoly = new PolygonShape(); - boxPoly.setAsBox(attachment.getWidth() / 2 * attachment.getScaleX(), - attachment.getHeight() / 2 * attachment.getScaleY(), vector.set(attachment.getX(), attachment.getY()), - attachment.getRotation() * MathUtils.degRad); + boxPoly.setAsBox(attachment.getWidth() / 2 * attachment.getScaleX(), attachment.getHeight() / 2 * attachment.getScaleY(), + vector.set(attachment.getX(), attachment.getY()), attachment.getRotation() * MathUtils.degRad); BodyDef boxBodyDef = new BodyDef(); boxBodyDef.type = BodyType.StaticBody; @@ -146,7 +145,7 @@ public class Box2DExample extends ApplicationAdapter { batch.setTransformMatrix(camera.view); batch.begin(); - animation.apply(skeleton, time, time, true, events); + animation.apply(skeleton, time, time, true, events, 1, false); skeleton.x += 8 * delta; skeleton.updateWorldTransform(); skeletonRenderer.draw(batch, skeleton); diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/EventTimelineTests.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/EventTimelineTests.java index 9c146485e..de61efff3 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/EventTimelineTests.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/EventTimelineTests.java @@ -177,7 +177,7 @@ public class EventTimelineTests { int beforeCount = firedEvents.size; Array original = new Array(firedEvents); - timeline.apply(skeleton, lastTimeLooped, timeLooped, firedEvents, 1); + timeline.apply(skeleton, lastTimeLooped, timeLooped, firedEvents, 1, false); while (beforeCount < firedEvents.size) { char fired = firedEvents.get(beforeCount).getData().getName().charAt(0); @@ -186,7 +186,7 @@ public class EventTimelineTests { } else { if (firedEvents.size > eventsCount) { if (print) System.out.println(lastTimeLooped + "->" + timeLooped + ": " + fired + " == ?"); - timeline.apply(skeleton, lastTimeLooped, timeLooped, original, 1); + timeline.apply(skeleton, lastTimeLooped, timeLooped, original, 1, false); fail("Too many events fired."); } } @@ -194,7 +194,7 @@ public class EventTimelineTests { System.out.println(lastTimeLooped + "->" + timeLooped + ": " + fired + " == " + events[eventIndex]); } if (fired != events[eventIndex]) { - timeline.apply(skeleton, lastTimeLooped, timeLooped, original, 1); + timeline.apply(skeleton, lastTimeLooped, timeLooped, original, 1, false); fail("Wrong event fired."); } eventIndex++; @@ -206,7 +206,7 @@ public class EventTimelineTests { i++; } if (firedEvents.size < eventsCount) { - timeline.apply(skeleton, lastTimeLooped, timeLooped, firedEvents, 1); + timeline.apply(skeleton, lastTimeLooped, timeLooped, firedEvents, 1, false); if (print) System.out.println(firedEvents); fail("Event not fired: " + events[eventIndex] + ", " + frames[eventIndex]); } diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/MixTest.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/MixTest.java index b8a4db77c..db0d80c9f 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/MixTest.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/MixTest.java @@ -104,21 +104,22 @@ public class MixTest extends ApplicationAdapter { skeleton.setX(-50); } else if (time > beforeJump + jump) { // just walk after jump - walkAnimation.apply(skeleton, time, time, true, events); + walkAnimation.apply(skeleton, time, time, true, events, 1, false); } else if (time > blendOutStart) { // blend out jump - walkAnimation.apply(skeleton, time, time, true, events); - jumpAnimation.mix(skeleton, time - beforeJump, time - beforeJump, false, events, 1 - (time - blendOutStart) / blendOut); + walkAnimation.apply(skeleton, time, time, true, events, 1, false); + jumpAnimation.apply(skeleton, time - beforeJump, time - beforeJump, false, events, 1 - (time - blendOutStart) / blendOut, + false); } else if (time > beforeJump + blendIn) { // just jump - jumpAnimation.apply(skeleton, time - beforeJump, time - beforeJump, false, events); + jumpAnimation.apply(skeleton, time - beforeJump, time - beforeJump, false, events, 1, false); } else if (time > beforeJump) { // blend in jump - walkAnimation.apply(skeleton, time, time, true, events); - jumpAnimation.mix(skeleton, time - beforeJump, time - beforeJump, false, events, (time - beforeJump) / blendIn); + walkAnimation.apply(skeleton, time, time, true, events, 1, false); + jumpAnimation.apply(skeleton, time - beforeJump, time - beforeJump, false, events, (time - beforeJump) / blendIn, false); } else { // just walk before jump - walkAnimation.apply(skeleton, time, time, true, events); + walkAnimation.apply(skeleton, time, time, true, events, 1, false); } skeleton.updateWorldTransform(); diff --git a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/NormalMapTest.java b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/NormalMapTest.java index 6372ecc92..6986e2876 100644 --- a/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/NormalMapTest.java +++ b/spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/NormalMapTest.java @@ -131,7 +131,7 @@ public class NormalMapTest extends ApplicationAdapter { public void render () { float lastTime = time; time += Gdx.graphics.getDeltaTime(); - if (animation != null) animation.apply(skeleton, lastTime, time, true, null); + if (animation != null) animation.apply(skeleton, lastTime, time, true, null, 1, false); skeleton.updateWorldTransform(); skeleton.update(Gdx.graphics.getDeltaTime()); diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java index 9b9b22e32..e3d257694 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -64,27 +64,14 @@ public class Animation { this.duration = duration; } - /** Poses the skeleton at the specified time for this animation. - * @param lastTime The last time the animation was applied. - * @param events Any triggered events are added. May be null. */ - public void apply (Skeleton skeleton, float lastTime, float time, boolean loop, Array events) { - if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); - - if (loop && duration != 0) { - time %= duration; - if (lastTime > 0) lastTime %= duration; - } - - Array timelines = this.timelines; - for (int i = 0, n = timelines.size; i < n; i++) - timelines.get(i).apply(skeleton, lastTime, time, events, 1); - } - - /** Poses the skeleton at the specified time for this animation mixed with the current pose. + /** Poses the skeleton at the specified time for this animation mixed with the current or setup pose. * @param lastTime The last time the animation was applied. * @param events Any triggered events are added. May be null. - * @param alpha The amount of this animation that affects the current pose. */ - public void mix (Skeleton skeleton, float lastTime, float time, boolean loop, Array events, float alpha) { + * @param alpha The amount of this animation that affects the current pose. + * @param setupPose If true, the animation is mixed with the setup pose, else it is mixed with the current pose. Pass true when + * alpha is 1. */ + public void apply (Skeleton skeleton, float lastTime, float time, boolean loop, Array events, float alpha, + boolean setupPose) { if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); if (loop && duration != 0) { @@ -94,7 +81,7 @@ public class Animation { Array timelines = this.timelines; for (int i = 0, n = timelines.size; i < n; i++) - timelines.get(i).apply(skeleton, lastTime, time, events, alpha); + timelines.get(i).apply(skeleton, lastTime, time, events, alpha, setupPose); } public String getName () { @@ -147,8 +134,20 @@ public class Animation { static public interface Timeline { /** Sets the value(s) for the specified time. - * @param events May be null to not collect fired events. */ - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha); + * @param events May be null to not collect fired events. + * @param setupPose If true, the timeline is mixed with the setup pose, else it is mixed with the current pose. Pass true + * when alpha is 1. */ + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, boolean setupPose); + + public int getId (); + } + + static private enum TimelineType { + rotate, translate, scale, shear, // + attachment, color, deform, // + event, drawOrder, // + ikConstraint, transformConstraint, // + pathConstraintPosition, pathConstraintSpacing, pathConstraintMix } /** Base class for frames that use an interpolation bezier curve. */ @@ -251,6 +250,10 @@ public class Animation { frames = new float[frameCount << 1]; } + public int getId () { + return (TimelineType.rotate.ordinal() << 24) + boneIndex; + } + public void setBoneIndex (int index) { if (index < 0) throw new IllegalArgumentException("index must be >= 0."); this.boneIndex = index; @@ -271,19 +274,19 @@ public class Animation { frames[frameIndex + ROTATION] = degrees; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha) { + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, boolean setupPose) { float[] frames = this.frames; if (time < frames[0]) return; // Time is before first frame. - Bone bone = skeleton.bones.get(boneIndex); if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. - float amount = bone.data.rotation + frames[frames.length + PREV_ROTATION] - bone.rotation; - while (amount > 180) - amount -= 360; - while (amount < -180) - amount += 360; - bone.rotation += amount * alpha; + if (setupPose) + bone.rotation = bone.data.rotation + frames[frames.length + PREV_ROTATION] * alpha; + else { + float amount = bone.data.rotation + frames[frames.length + PREV_ROTATION] - bone.rotation; + amount -= (16384 - (int)(16384.499999999996 - amount / 360)) * 360; + bone.rotation += amount * alpha; + } return; } @@ -294,16 +297,16 @@ public class Animation { float percent = getCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); float amount = frames[frame + ROTATION] - prevRotation; - while (amount > 180) - amount -= 360; - while (amount < -180) - amount += 360; - amount = bone.data.rotation + (prevRotation + amount * percent) - bone.rotation; - while (amount > 180) - amount -= 360; - while (amount < -180) - amount += 360; - bone.rotation += amount * alpha; + amount = amount - (16384 - (int)(16384.499999999996 - amount / 360)) * 360; + if (setupPose) { + amount = prevRotation + amount * percent; + amount = amount - (16384 - (int)(16384.499999999996 - amount / 360)) * 360; + bone.rotation = bone.data.rotation + amount * alpha; + } else { + amount = bone.data.rotation + (prevRotation + amount * percent) - bone.rotation; + amount -= (16384 - (int)(16384.499999999996 - amount / 360)) * 360; + bone.rotation += amount * alpha; + } } } @@ -320,6 +323,10 @@ public class Animation { frames = new float[frameCount * ENTRIES]; } + public int getId () { + return (TimelineType.translate.ordinal() << 24) + boneIndex; + } + public void setBoneIndex (int index) { if (index < 0) throw new IllegalArgumentException("index must be >= 0."); this.boneIndex = index; @@ -341,15 +348,20 @@ public class Animation { frames[frameIndex + Y] = y; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha) { + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, boolean setupPose) { float[] frames = this.frames; if (time < frames[0]) return; // Time is before first frame. Bone bone = skeleton.bones.get(boneIndex); if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. - bone.x += (bone.data.x + frames[frames.length + PREV_X] - bone.x) * alpha; - bone.y += (bone.data.y + frames[frames.length + PREV_Y] - bone.y) * alpha; + if (setupPose) { + bone.x = bone.data.x + frames[frames.length + PREV_X] * alpha; + bone.y = bone.data.y + frames[frames.length + PREV_Y] * alpha; + } else { + bone.x += (bone.data.x + frames[frames.length + PREV_X] - bone.x) * alpha; + bone.y += (bone.data.y + frames[frames.length + PREV_Y] - bone.y) * alpha; + } return; } @@ -360,8 +372,13 @@ public class Animation { float frameTime = frames[frame]; float percent = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - bone.x += (bone.data.x + prevX + (frames[frame + X] - prevX) * percent - bone.x) * alpha; - bone.y += (bone.data.y + prevY + (frames[frame + Y] - prevY) * percent - bone.y) * alpha; + if (setupPose) { + bone.x = bone.data.x + (prevX + (frames[frame + X] - prevX) * percent) * alpha; + bone.y = bone.data.y + (prevY + (frames[frame + Y] - prevY) * percent) * alpha; + } else { + bone.x += (bone.data.x + prevX + (frames[frame + X] - prevX) * percent - bone.x) * alpha; + bone.y += (bone.data.y + prevY + (frames[frame + Y] - prevY) * percent - bone.y) * alpha; + } } } @@ -370,7 +387,11 @@ public class Animation { super(frameCount); } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha) { + public int getId () { + return (TimelineType.scale.ordinal() << 24) + boneIndex; + } + + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, boolean setupPose) { float[] frames = this.frames; if (time < frames[0]) return; // Time is before first frame. @@ -409,7 +430,11 @@ public class Animation { super(frameCount); } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha) { + public int getId () { + return (TimelineType.shear.ordinal() << 24) + boneIndex; + } + + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, boolean setupPose) { float[] frames = this.frames; if (time < frames[0]) return; // Time is before first frame. @@ -445,6 +470,10 @@ public class Animation { frames = new float[frameCount * ENTRIES]; } + public int getId () { + return (TimelineType.color.ordinal() << 24) + slotIndex; + } + public void setSlotIndex (int index) { if (index < 0) throw new IllegalArgumentException("index must be >= 0."); this.slotIndex = index; @@ -468,7 +497,7 @@ public class Animation { frames[frameIndex + A] = a; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha) { + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, boolean setupPose) { float[] frames = this.frames; if (time < frames[0]) return; // Time is before first frame. @@ -513,6 +542,10 @@ public class Animation { attachmentNames = new String[frameCount]; } + public int getId () { + return (TimelineType.attachment.ordinal() << 24) + slotIndex; + } + public int getFrameCount () { return frames.length; } @@ -540,7 +573,7 @@ public class Animation { attachmentNames[frameIndex] = attachmentName; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha) { + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, boolean setupPose) { float[] frames = this.frames; if (time < frames[0]) return; // Time is before first frame. @@ -556,117 +589,11 @@ public class Animation { } } - static public class EventTimeline implements Timeline { - private final float[] frames; // time, ... - private final Event[] events; - - public EventTimeline (int frameCount) { - frames = new float[frameCount]; - events = new Event[frameCount]; - } - - public int getFrameCount () { - return frames.length; - } - - public float[] getFrames () { - return frames; - } - - public Event[] getEvents () { - return events; - } - - /** Sets the time of the specified keyframe. */ - public void setFrame (int frameIndex, Event event) { - frames[frameIndex] = event.time; - events[frameIndex] = event; - } - - /** Fires events for frames > lastTime and <= time. */ - public void apply (Skeleton skeleton, float lastTime, float time, Array firedEvents, float alpha) { - if (firedEvents == null) return; - float[] frames = this.frames; - int frameCount = frames.length; - - if (lastTime > time) { // Fire events after last time for looped animations. - apply(skeleton, lastTime, Integer.MAX_VALUE, firedEvents, alpha); - lastTime = -1f; - } else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. - return; - if (time < frames[0]) return; // Time is before first frame. - - int frame; - if (lastTime < frames[0]) - frame = 0; - else { - frame = binarySearch(frames, lastTime); - float frameTime = frames[frame]; - while (frame > 0) { // Fire multiple events with the same frame. - if (frames[frame - 1] != frameTime) break; - frame--; - } - } - for (; frame < frameCount && time >= frames[frame]; frame++) - firedEvents.add(events[frame]); - } - } - - static public class DrawOrderTimeline implements Timeline { - private final float[] frames; // time, ... - private final int[][] drawOrders; - - public DrawOrderTimeline (int frameCount) { - frames = new float[frameCount]; - drawOrders = new int[frameCount][]; - } - - public int getFrameCount () { - return frames.length; - } - - public float[] getFrames () { - return frames; - } - - public int[][] getDrawOrders () { - return drawOrders; - } - - /** Sets the time of the specified keyframe. - * @param drawOrder May be null to use bind pose draw order. */ - public void setFrame (int frameIndex, float time, int[] drawOrder) { - frames[frameIndex] = time; - drawOrders[frameIndex] = drawOrder; - } - - public void apply (Skeleton skeleton, float lastTime, float time, Array firedEvents, float alpha) { - float[] frames = this.frames; - if (time < frames[0]) return; // Time is before first frame. - - int frame; - if (time >= frames[frames.length - 1]) // Time is after last frame. - frame = frames.length - 1; - else - frame = binarySearch(frames, time) - 1; - - Array drawOrder = skeleton.drawOrder; - Array slots = skeleton.slots; - int[] drawOrderToSetupIndex = drawOrders[frame]; - if (drawOrderToSetupIndex == null) - System.arraycopy(slots.items, 0, drawOrder.items, 0, slots.size); - else { - for (int i = 0, n = drawOrderToSetupIndex.length; i < n; i++) - drawOrder.set(i, slots.get(drawOrderToSetupIndex[i])); - } - } - } - static public class DeformTimeline extends CurveTimeline { - private final float[] frames; // time, ... - private final float[][] frameVertices; int slotIndex; VertexAttachment attachment; + private final float[] frames; // time, ... + private final float[][] frameVertices; public DeformTimeline (int frameCount) { super(frameCount); @@ -674,6 +601,10 @@ public class Animation { frameVertices = new float[frameCount][]; } + public int getId () { + return (TimelineType.deform.ordinal() << 24) + slotIndex; + } + public void setSlotIndex (int index) { if (index < 0) throw new IllegalArgumentException("index must be >= 0."); this.slotIndex = index; @@ -687,7 +618,7 @@ public class Animation { this.attachment = attachment; } - public Attachment getAttachment () { + public VertexAttachment getAttachment () { return attachment; } @@ -705,7 +636,8 @@ public class Animation { frameVertices[frameIndex] = vertices; } - public void apply (Skeleton skeleton, float lastTime, float time, Array firedEvents, float alpha) { + public void apply (Skeleton skeleton, float lastTime, float time, Array firedEvents, float alpha, + boolean setupPose) { Slot slot = skeleton.slots.get(slotIndex); Attachment slotAttachment = slot.attachment; if (!(slotAttachment instanceof VertexAttachment) || !((VertexAttachment)slotAttachment).applyDeform(attachment)) return; @@ -751,6 +683,122 @@ public class Animation { } } + static public class EventTimeline implements Timeline { + private final float[] frames; // time, ... + private final Event[] events; + + public EventTimeline (int frameCount) { + frames = new float[frameCount]; + events = new Event[frameCount]; + } + + public int getId () { + return TimelineType.event.ordinal() << 24; + } + + public int getFrameCount () { + return frames.length; + } + + public float[] getFrames () { + return frames; + } + + public Event[] getEvents () { + return events; + } + + /** Sets the time of the specified keyframe. */ + public void setFrame (int frameIndex, Event event) { + frames[frameIndex] = event.time; + events[frameIndex] = event; + } + + /** Fires events for frames > lastTime and <= time. */ + public void apply (Skeleton skeleton, float lastTime, float time, Array firedEvents, float alpha, + boolean setupPose) { + if (firedEvents == null) return; + float[] frames = this.frames; + int frameCount = frames.length; + + if (lastTime > time) { // Fire events after last time for looped animations. + apply(skeleton, lastTime, Integer.MAX_VALUE, firedEvents, alpha, setupPose); + lastTime = -1f; + } else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. + return; + if (time < frames[0]) return; // Time is before first frame. + + int frame; + if (lastTime < frames[0]) + frame = 0; + else { + frame = binarySearch(frames, lastTime); + float frameTime = frames[frame]; + while (frame > 0) { // Fire multiple events with the same frame. + if (frames[frame - 1] != frameTime) break; + frame--; + } + } + for (; frame < frameCount && time >= frames[frame]; frame++) + firedEvents.add(events[frame]); + } + } + + static public class DrawOrderTimeline implements Timeline { + private final float[] frames; // time, ... + private final int[][] drawOrders; + + public DrawOrderTimeline (int frameCount) { + frames = new float[frameCount]; + drawOrders = new int[frameCount][]; + } + + public int getId () { + return TimelineType.drawOrder.ordinal() << 24; + } + + public int getFrameCount () { + return frames.length; + } + + public float[] getFrames () { + return frames; + } + + public int[][] getDrawOrders () { + return drawOrders; + } + + /** Sets the time of the specified keyframe. + * @param drawOrder May be null to use bind pose draw order. */ + public void setFrame (int frameIndex, float time, int[] drawOrder) { + frames[frameIndex] = time; + drawOrders[frameIndex] = drawOrder; + } + + public void apply (Skeleton skeleton, float lastTime, float time, Array firedEvents, float alpha, + boolean setupPose) { + float[] frames = this.frames; + if (time < frames[0]) return; // Time is before first frame. + + int frame; + if (time >= frames[frames.length - 1]) // Time is after last frame. + frame = frames.length - 1; + else + frame = binarySearch(frames, time) - 1; + + Array drawOrder = skeleton.drawOrder; + Array slots = skeleton.slots; + int[] drawOrderToSetupIndex = drawOrders[frame]; + if (drawOrderToSetupIndex == null) + System.arraycopy(slots.items, 0, drawOrder.items, 0, slots.size); + else { + for (int i = 0, n = drawOrderToSetupIndex.length; i < n; i++) + drawOrder.set(i, slots.get(drawOrderToSetupIndex[i])); + } + } + } + static public class IkConstraintTimeline extends CurveTimeline { static public final int ENTRIES = 3; static private final int PREV_TIME = -3, PREV_MIX = -2, PREV_BEND_DIRECTION = -1; @@ -764,6 +812,10 @@ public class Animation { frames = new float[frameCount * ENTRIES]; } + public int getId () { + return (TimelineType.ikConstraint.ordinal() << 24) + ikConstraintIndex; + } + public void setIkConstraintIndex (int index) { if (index < 0) throw new IllegalArgumentException("index must be >= 0."); this.ikConstraintIndex = index; @@ -785,7 +837,7 @@ public class Animation { frames[frameIndex + BEND_DIRECTION] = bendDirection; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha) { + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, boolean setupPose) { float[] frames = this.frames; if (time < frames[0]) return; // Time is before first frame. @@ -821,6 +873,10 @@ public class Animation { frames = new float[frameCount * ENTRIES]; } + public int getId () { + return (TimelineType.transformConstraint.ordinal() << 24) + transformConstraintIndex; + } + public void setTransformConstraintIndex (int index) { if (index < 0) throw new IllegalArgumentException("index must be >= 0."); this.transformConstraintIndex = index; @@ -844,7 +900,7 @@ public class Animation { frames[frameIndex + SHEAR] = shearMix; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha) { + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, boolean setupPose) { float[] frames = this.frames; if (time < frames[0]) return; // Time is before first frame. @@ -890,6 +946,10 @@ public class Animation { frames = new float[frameCount * ENTRIES]; } + public int getId () { + return (TimelineType.pathConstraintPosition.ordinal() << 24) + pathConstraintIndex; + } + public void setPathConstraintIndex (int index) { if (index < 0) throw new IllegalArgumentException("index must be >= 0."); this.pathConstraintIndex = index; @@ -910,7 +970,7 @@ public class Animation { frames[frameIndex + VALUE] = value; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha) { + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, boolean setupPose) { float[] frames = this.frames; if (time < frames[0]) return; // Time is before first frame. @@ -937,7 +997,11 @@ public class Animation { super(frameCount); } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha) { + public int getId () { + return (TimelineType.pathConstraintSpacing.ordinal() << 24) + pathConstraintIndex; + } + + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, boolean setupPose) { float[] frames = this.frames; if (time < frames[0]) return; // Time is before first frame. @@ -973,6 +1037,10 @@ public class Animation { frames = new float[frameCount * ENTRIES]; } + public int getId () { + return (TimelineType.pathConstraintMix.ordinal() << 24) + pathConstraintIndex; + } + public void setPathConstraintIndex (int index) { if (index < 0) throw new IllegalArgumentException("index must be >= 0."); this.pathConstraintIndex = index; @@ -994,7 +1062,7 @@ public class Animation { frames[frameIndex + TRANSLATE] = translateMix; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha) { + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, boolean setupPose) { float[] frames = this.frames; if (time < frames[0]) return; // Time is before first frame. 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 b5acfeba1..eba5800df 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java @@ -32,7 +32,9 @@ 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; import com.esotericsoftware.spine.Animation.AttachmentTimeline; @@ -51,6 +53,7 @@ public class AnimationState { } }; private final EventQueue queue = new EventQueue(listeners, trackEntryPool); + private final IntSet usage = new IntSet(); private float timeScale = 1; /** Creates an uninitialized AnimationState. The animation state data must be set before use. */ @@ -116,12 +119,12 @@ public class AnimationState { if (current == null) continue; if (current.delay > 0) continue; - float alpha = current.alpha; + float mix = current.alpha; if (current.mixingFrom != null) { - alpha *= current.mixTime / current.mixDuration; - applyMixingFrom(current.mixingFrom, skeleton, alpha); - if (alpha >= 1) { - alpha = 1; + mix *= current.mixTime / current.mixDuration; + applyMixingFrom(current.mixingFrom, skeleton, mix); + if (mix >= 1) { + mix = 1; queue.end(current.mixingFrom); current.mixingFrom = null; } @@ -129,8 +132,9 @@ public class AnimationState { float animationLast = current.animationLast, animationTime = current.getAnimationTime(); Array timelines = current.animation.timelines; + BooleanArray setupPose = current.setupPose; for (int ii = 0, n = timelines.size; ii < n; ii++) - timelines.get(ii).apply(skeleton, animationLast, animationTime, events, alpha); + timelines.get(ii).apply(skeleton, animationLast, animationTime, events, mix, setupPose.get(ii)); queueEvents(current, animationTime); current.animationLast = animationTime; current.trackLast = current.trackTime; @@ -145,16 +149,25 @@ public class AnimationState { float animationLast = entry.animationLast, animationTime = entry.getAnimationTime(); Array timelines = entry.animation.timelines; - float alpha = entry.alpha; + BooleanArray setupPose = entry.setupPose; + float alphaFull = entry.alpha, alphaMix = entry.alpha * (1 - mix); if (attachments && drawOrder) { - for (int i = 0, n = timelines.size; i < n; i++) - timelines.get(i).apply(skeleton, animationLast, animationTime, events, alpha); + for (int i = 0, n = timelines.size; i < n; i++) { + Timeline timeline = timelines.get(i); + if (setupPose.get(i)) + timeline.apply(skeleton, animationLast, animationTime, events, alphaMix, true); + else + timeline.apply(skeleton, animationLast, animationTime, events, alphaFull, false); + } } 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, animationLast, animationTime, events, alpha); + if (setupPose.get(i)) + timeline.apply(skeleton, animationLast, animationTime, events, alphaMix, true); + else + timeline.apply(skeleton, animationLast, animationTime, events, alphaFull, false); } } @@ -254,6 +267,58 @@ public class AnimationState { } queue.drain(); + + updateSetupPose(); + } + + private void updateSetupPose () { + usage.clear(); + int i = 0, n = tracks.size; + for (; i < n; i++) { + TrackEntry entry = tracks.get(i); + if (entry == null) continue; + if (entry.mixingFrom != null) { + updateFirstSetupPose(entry.mixingFrom); + updateSetupPose(entry); + } else + updateFirstSetupPose(entry); + break; + } + for (i++; i < n; i++) { + TrackEntry entry = tracks.get(i); + if (entry == null) continue; + if (entry.mixingFrom != null) updateSetupPose(entry.mixingFrom); + updateSetupPose(entry); + } + } + + private void updateFirstSetupPose (TrackEntry entry) { + IntSet usage = this.usage; + BooleanArray setupPose = entry.setupPose; + setupPose.clear(); + Array timelines = entry.animation.timelines; + for (int ii = 0, nn = timelines.size; ii < nn; ii++) { + Timeline timeline = timelines.get(ii); + usage.add(timeline.getId()); + setupPose.add(true); + } + } + + private void updateSetupPose (TrackEntry entry) { + IntSet usage = this.usage; + BooleanArray setupPose = entry.setupPose; + setupPose.clear(); + Array timelines = entry.animation.timelines; + for (int ii = 0, nn = timelines.size; ii < nn; ii++) { + Timeline timeline = timelines.get(ii); + int id = timeline.getId(); + if (usage.contains(id)) + setupPose.add(false); + else { + usage.add(id); + setupPose.add(true); + } + } } /** @see #setAnimation(int, Animation, boolean) */ @@ -426,12 +491,14 @@ public class AnimationState { float delay, trackTime, trackLast, trackEnd, animationStart, animationEnd, animationLast, timeScale; float alpha; float mixTime, mixDuration; + final BooleanArray setupPose = new BooleanArray(); public void reset () { next = null; mixingFrom = null; animation = null; listener = null; + setupPose.clear(); } public int getTrackIndex () { 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 a9239904e..255f1ade7 100644 --- a/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java +++ b/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java @@ -264,8 +264,7 @@ public class SkeletonViewer extends ApplicationAdapter { ShapeRenderer shapes = debugRenderer.getShapeRenderer(); TrackEntry entry = state.getCurrent(0); if (entry != null) { - float percent = entry.getTrackTime() / entry.getTrackEnd(); - if (entry.getLoop()) percent %= 1; + float percent = entry.getAnimationTime() / entry.getAnimationEnd(); float x = ui.window.getRight() + (Gdx.graphics.getWidth() - ui.window.getRight()) * percent; shapes.setColor(Color.CYAN); shapes.begin(ShapeType.Line);