From 5540b47f21a943e778aab25bee2f2e901c7fe55f Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Mon, 12 Jun 2017 02:48:58 +0200 Subject: [PATCH] Changed Timeline#apply to enum parameters, AnimationState special cases track 0 to apply setup pose before first key. --- .../esotericsoftware/spine/BonePlotting.java | 4 +- .../esotericsoftware/spine/Box2DExample.java | 4 +- .../spine/EventTimelineTests.java | 11 +- .../com/esotericsoftware/spine/MixTest.java | 18 +- .../esotericsoftware/spine/NormalMapTest.java | 4 +- .../com/esotericsoftware/spine/Animation.java | 282 ++++++++++++------ .../spine/AnimationState.java | 54 ++-- .../src/com/esotericsoftware/spine/Event.java | 3 +- 8 files changed, 245 insertions(+), 135 deletions(-) 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 aa9f08b8e..df9247baf 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 @@ -31,6 +31,8 @@ package com.esotericsoftware.spine; import com.badlogic.gdx.files.FileHandle; +import com.esotericsoftware.spine.Animation.MixDirection; +import com.esotericsoftware.spine.Animation.MixPose; import com.esotericsoftware.spine.attachments.AttachmentLoader; import com.esotericsoftware.spine.attachments.BoundingBoxAttachment; import com.esotericsoftware.spine.attachments.ClippingAttachment; @@ -74,7 +76,7 @@ public class BonePlotting { for (Animation animation : skeletonData.getAnimations()) { float time = 0; while (time < animation.getDuration()) { - animation.apply(skeleton, time, time, false, null, 1, false, false); + animation.apply(skeleton, time, time, false, null, 1, MixPose.current, MixDirection.in); 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 1793a1c38..d41e909e1 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 @@ -30,6 +30,8 @@ package com.esotericsoftware.spine; +import com.esotericsoftware.spine.Animation.MixDirection; +import com.esotericsoftware.spine.Animation.MixPose; import com.esotericsoftware.spine.attachments.AtlasAttachmentLoader; import com.esotericsoftware.spine.attachments.RegionAttachment; @@ -144,7 +146,7 @@ public class Box2DExample extends ApplicationAdapter { batch.setTransformMatrix(camera.view); batch.begin(); - animation.apply(skeleton, time, time, true, events, 1, false, false); + animation.apply(skeleton, time, time, true, events, 1, MixPose.current, MixDirection.in); 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 3e251df7c..0a0530340 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 @@ -31,7 +31,8 @@ package com.esotericsoftware.spine; import com.esotericsoftware.spine.Animation.EventTimeline; - +import com.esotericsoftware.spine.Animation.MixDirection; +import com.esotericsoftware.spine.Animation.MixPose; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.StringBuilder; @@ -176,7 +177,7 @@ public class EventTimelineTests { int beforeCount = firedEvents.size; Array original = new Array(firedEvents); - timeline.apply(skeleton, lastTimeLooped, timeLooped, firedEvents, 1, false, false); + timeline.apply(skeleton, lastTimeLooped, timeLooped, firedEvents, 1, MixPose.current, MixDirection.in); while (beforeCount < firedEvents.size) { char fired = firedEvents.get(beforeCount).getData().getName().charAt(0); @@ -185,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, false, false); + timeline.apply(skeleton, lastTimeLooped, timeLooped, original, 1, MixPose.current, MixDirection.in); fail("Too many events fired."); } } @@ -193,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, false, false); + timeline.apply(skeleton, lastTimeLooped, timeLooped, original, 1, MixPose.current, MixDirection.in); fail("Wrong event fired."); } eventIndex++; @@ -205,7 +206,7 @@ public class EventTimelineTests { i++; } if (firedEvents.size < eventsCount) { - timeline.apply(skeleton, lastTimeLooped, timeLooped, firedEvents, 1, false, false); + timeline.apply(skeleton, lastTimeLooped, timeLooped, firedEvents, 1, MixPose.current, MixDirection.in); 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 59fab4067..ebab89140 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 @@ -37,6 +37,8 @@ import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.utils.Array; +import com.esotericsoftware.spine.Animation.MixDirection; +import com.esotericsoftware.spine.Animation.MixPose; public class MixTest extends ApplicationAdapter { SpriteBatch batch; @@ -103,21 +105,23 @@ public class MixTest extends ApplicationAdapter { skeleton.setX(-50); } else if (time > beforeJump + jump) { // just walk after jump - walkAnimation.apply(skeleton, time, time, true, events, 1, false, false); + walkAnimation.apply(skeleton, time, time, true, events, 1, MixPose.current, MixDirection.in); } else if (time > blendOutStart) { // blend out jump - walkAnimation.apply(skeleton, time, time, true, events, 1, false, false); - jumpAnimation.apply(skeleton, time - beforeJump, time - beforeJump, false, events, 1 - (time - blendOutStart) / blendOut, false, false); + walkAnimation.apply(skeleton, time, time, true, events, 1, MixPose.current, MixDirection.in); + jumpAnimation.apply(skeleton, time - beforeJump, time - beforeJump, false, events, 1 - (time - blendOutStart) / blendOut, + MixPose.current, MixDirection.in); } else if (time > beforeJump + blendIn) { // just jump - jumpAnimation.apply(skeleton, time - beforeJump, time - beforeJump, false, events, 1, false, false); + jumpAnimation.apply(skeleton, time - beforeJump, time - beforeJump, false, events, 1, MixPose.current, MixDirection.in); } else if (time > beforeJump) { // blend in jump - walkAnimation.apply(skeleton, time, time, true, events, 1, false, false); - jumpAnimation.apply(skeleton, time - beforeJump, time - beforeJump, false, events, (time - beforeJump) / blendIn, false, false); + walkAnimation.apply(skeleton, time, time, true, events, 1, MixPose.current, MixDirection.in); + jumpAnimation.apply(skeleton, time - beforeJump, time - beforeJump, false, events, (time - beforeJump) / blendIn, + MixPose.current, MixDirection.in); } else { // just walk before jump - walkAnimation.apply(skeleton, time, time, true, events, 1, false, false); + walkAnimation.apply(skeleton, time, time, true, events, 1, MixPose.current, MixDirection.in); } 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 eb3956e7c..c1d6805fd 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 @@ -56,6 +56,8 @@ import com.badlogic.gdx.scenes.scene2d.ui.TextButton; import com.badlogic.gdx.scenes.scene2d.ui.Window; import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener; import com.badlogic.gdx.utils.Align; +import com.esotericsoftware.spine.Animation.MixDirection; +import com.esotericsoftware.spine.Animation.MixPose; public class NormalMapTest extends ApplicationAdapter { String skeletonPath, animationName; @@ -130,7 +132,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, 1, false, false); + if (animation != null) animation.apply(skeleton, lastTime, time, true, null, 1, MixPose.current, MixDirection.in); 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 59407a64e..1e60a842a 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -30,6 +30,9 @@ package com.esotericsoftware.spine; +import static com.esotericsoftware.spine.Animation.MixDirection.*; +import static com.esotericsoftware.spine.Animation.MixPose.*; + import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.utils.Array; @@ -66,9 +69,9 @@ public class Animation { /** Applies all the animation's timelines to the specified skeleton. *

- * See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, boolean, boolean)}. */ - public void apply (Skeleton skeleton, float lastTime, float time, boolean loop, Array events, float alpha, - boolean setupPose, boolean mixingOut) { + * See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixPose, MixDirection)}. */ + public void apply (Skeleton skeleton, float lastTime, float time, boolean loop, Array events, float alpha, MixPose pose, + MixDirection direction) { if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); if (loop && duration != 0) { @@ -78,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, setupPose, mixingOut); + timelines.get(i).apply(skeleton, lastTime, time, events, alpha, pose, direction); } /** The animation's name, which is unique within the skeleton. */ @@ -142,23 +145,43 @@ public class Animation { * interpolate between the keys. * @param events If any events are fired, they are added to this list. Can be null to ignore firing events or if the * timeline does not fire events. - * @param alpha 0 results in the value of the current or setup pose (depending on setupPose). 1 results in the - * value from the timeline. Between 0 and 1 results in a value mixed between the current or setup pose and the - * value from the timeline. By adjusting alpha over time, an animation can be mixed in or out. - * alpha can also be useful to apply animations on top of each other. - * @param setupPose Controls mixing when alpha < 1. When true the value from the timeline is mixed with the - * value from the setup pose. When false the value from the timeline is mixed with the value from the current - * pose. Passing true when alpha is 1 is slightly more efficient for most timelines. - * @param mixingOut True when changing alpha over time toward 0 (the setup or current pose), false when - * changing alpha toward 1 (the timeline's pose). Used for timelines which perform instant - * transitions, such as {@link DrawOrderTimeline} or {@link AttachmentTimeline}. */ - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, boolean setupPose, - boolean mixingOut); + * @param alpha 0 applies the current or setup pose value (depending on setupPose). 1 applies the timeline + * value. Between 0 and 1 applies a value between the current or setup pose and the timeline value. By adjusting + * alpha over time, an animation can be mixed in or out. alpha can also be useful to + * apply animations on top of each other (layered). + * @param pose Controls how mixing is applied when alpha < 1. + * @param direction Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions, + * such as {@link DrawOrderTimeline} or {@link AttachmentTimeline}. */ + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixPose pose, + MixDirection direction); /** Uniquely encodes both the type of this timeline and the skeleton property that it affects. */ public int getPropertyId (); } + /** Controls how a timeline is mixed with the setup or current pose. + *

+ * See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixPose, MixDirection)}. */ + static public enum MixPose { + /** The timeline value is mixed with the setup pose (the current pose is not used). */ + setup, + /** The timeline value is mixed with the current pose. The setup pose is used as the timeline value before the first key, + * except for timelines which perform instant transitions, such as {@link DrawOrderTimeline} or + * {@link AttachmentTimeline}. */ + current, + /** The timeline value is mixed with the current pose. No change is made before the first key (the current pose is kept + * until the first key). */ + currentLayered + } + + /** Indicates whether a timeline's alpha is mixing out over time toward 0 (the setup or current pose) or mixing in + * toward 1 (the timeline's pose). + *

+ * See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixPose, MixDirection)}. */ + static public enum MixDirection { + in, out + } + static private enum TimelineType { rotate, translate, scale, shear, // attachment, color, deform, // @@ -295,18 +318,26 @@ public class Animation { frames[frameIndex + ROTATION] = degrees; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, boolean setupPose, - boolean mixingOut) { + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixPose pose, + MixDirection direction) { Bone bone = skeleton.bones.get(boneIndex); float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. - if (setupPose) bone.rotation = bone.data.rotation; + switch (pose) { + case setup: + bone.rotation = bone.data.rotation; + return; + case current: + float r = bone.data.rotation - bone.rotation; + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; + bone.rotation += r * alpha; + } return; } if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. - if (setupPose) + if (pose == setup) bone.rotation = bone.data.rotation + frames[frames.length + PREV_ROTATION] * alpha; else { float r = bone.data.rotation + frames[frames.length + PREV_ROTATION] - bone.rotation; @@ -325,7 +356,7 @@ public class Animation { float r = frames[frame + ROTATION] - prevRotation; r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; r = prevRotation + r * percent; - if (setupPose) { + if (pose == setup) { r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; bone.rotation = bone.data.rotation + r * alpha; } else { @@ -377,15 +408,20 @@ public class Animation { frames[frameIndex + Y] = y; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, boolean setupPose, - boolean mixingOut) { + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixPose pose, + MixDirection direction) { Bone bone = skeleton.bones.get(boneIndex); float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. - if (setupPose) { + switch (pose) { + case setup: bone.x = bone.data.x; bone.y = bone.data.y; + return; + case current: + bone.x += (bone.data.x - bone.x) * alpha; + bone.y += (bone.data.y - bone.y) * alpha; } return; } @@ -406,7 +442,7 @@ public class Animation { x += (frames[frame + X] - x) * percent; y += (frames[frame + Y] - y) * percent; } - if (setupPose) { + if (pose == setup) { bone.x = bone.data.x + x * alpha; bone.y = bone.data.y + y * alpha; } else { @@ -426,15 +462,20 @@ public class Animation { return (TimelineType.scale.ordinal() << 24) + boneIndex; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, boolean setupPose, - boolean mixingOut) { + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixPose pose, + MixDirection direction) { Bone bone = skeleton.bones.get(boneIndex); float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. - if (setupPose) { + switch (pose) { + case setup: bone.scaleX = bone.data.scaleX; bone.scaleY = bone.data.scaleY; + return; + case current: + bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha; + bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha; } return; } @@ -460,7 +501,7 @@ public class Animation { bone.scaleY = y; } else { float bx, by; - if (setupPose) { + if (pose == setup) { bx = bone.data.scaleX; by = bone.data.scaleY; } else { @@ -468,7 +509,7 @@ public class Animation { by = bone.scaleY; } // Mixing out uses sign of setup or current pose, else use sign of key. - if (mixingOut) { + if (direction == out) { x = Math.abs(x) * Math.signum(bx); y = Math.abs(y) * Math.signum(by); } else { @@ -491,15 +532,20 @@ public class Animation { return (TimelineType.shear.ordinal() << 24) + boneIndex; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, boolean setupPose, - boolean mixingOut) { + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixPose pose, + MixDirection direction) { Bone bone = skeleton.bones.get(boneIndex); float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. - if (setupPose) { + switch (pose) { + case setup: bone.shearX = bone.data.shearX; bone.shearY = bone.data.shearY; + return; + case current: + bone.shearX += (bone.data.shearX - bone.shearX) * alpha; + bone.shearY += (bone.data.shearY - bone.shearY) * alpha; } return; } @@ -520,7 +566,7 @@ public class Animation { x = x + (frames[frame + X] - x) * percent; y = y + (frames[frame + Y] - y) * percent; } - if (setupPose) { + if (pose == setup) { bone.shearX = bone.data.shearX + x * alpha; bone.shearY = bone.data.shearY + y * alpha; } else { @@ -573,13 +619,21 @@ public class Animation { frames[frameIndex + A] = a; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, boolean setupPose, - boolean mixingOut) { + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixPose pose, + MixDirection direction) { Slot slot = skeleton.slots.get(slotIndex); float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. - if (setupPose) slot.color.set(slot.data.color); + switch (pose) { + case setup: + slot.color.set(slot.data.color); + return; + case current: + Color color = slot.color, setup = slot.data.color; + color.add((setup.r - color.r) * alpha, (setup.g - color.g) * alpha, (setup.b - color.b) * alpha, + (setup.a - color.a) * alpha); + } return; } @@ -610,7 +664,7 @@ public class Animation { slot.color.set(r, g, b, a); else { Color color = slot.color; - if (setupPose) color.set(slot.data.color); + if (pose == setup) color.set(slot.data.color); color.add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha); } } @@ -663,15 +717,22 @@ public class Animation { frames[frameIndex + B2] = b2; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, boolean setupPose, - boolean mixingOut) { + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixPose pose, + MixDirection direction) { Slot slot = skeleton.slots.get(slotIndex); float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. - if (setupPose) { + switch (pose) { + case setup: slot.color.set(slot.data.color); slot.darkColor.set(slot.data.darkColor); + return; + case current: + Color light = slot.color, dark = slot.darkColor, setupLight = slot.data.color, setupDark = slot.data.darkColor; + light.add((setupLight.r - light.r) * alpha, (setupLight.g - light.g) * alpha, (setupLight.b - light.b) * alpha, + (setupLight.a - light.a) * alpha); + dark.add((setupDark.r - dark.r) * alpha, (setupDark.g - dark.g) * alpha, (setupDark.b - dark.b) * alpha, 0); } return; } @@ -712,9 +773,8 @@ public class Animation { slot.color.set(r, g, b, a); slot.darkColor.set(r2, g2, b2, 1); } else { - Color light = slot.color; - Color dark = slot.darkColor; - if (setupPose) { + Color light = slot.color, dark = slot.darkColor; + if (pose == setup) { light.set(slot.data.color); dark.set(slot.data.darkColor); } @@ -770,11 +830,11 @@ public class Animation { attachmentNames[frameIndex] = attachmentName; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, boolean setupPose, - boolean mixingOut) { + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixPose pose, + MixDirection direction) { Slot slot = skeleton.slots.get(slotIndex); - if (mixingOut && setupPose) { + if (direction == out && pose == setup) { String attachmentName = slot.data.attachmentName; slot.setAttachment(attachmentName == null ? null : skeleton.getAttachment(slotIndex, attachmentName)); return; @@ -782,7 +842,7 @@ public class Animation { float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. - if (setupPose) { + if (pose == setup) { String attachmentName = slot.data.attachmentName; slot.setAttachment(attachmentName == null ? null : skeleton.getAttachment(slotIndex, attachmentName)); } @@ -853,31 +913,39 @@ public class Animation { frameVertices[frameIndex] = vertices; } - public void apply (Skeleton skeleton, float lastTime, float time, Array firedEvents, float alpha, boolean setupPose, - boolean mixingOut) { + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixPose pose, + MixDirection direction) { Slot slot = skeleton.slots.get(slotIndex); Attachment slotAttachment = slot.attachment; if (!(slotAttachment instanceof VertexAttachment) || !((VertexAttachment)slotAttachment).applyDeform(attachment)) return; FloatArray verticesArray = slot.getAttachmentVertices(); - float[] frames = this.frames; - if (time < frames[0]) { // Time is before first frame. - if (setupPose) verticesArray.size = 0; - return; - } - float[][] frameVertices = this.frameVertices; int vertexCount = frameVertices[0].length; - if (verticesArray.size != vertexCount && !setupPose) alpha = 1; // Don't mix from uninitialized slot vertices. + if (verticesArray.size != vertexCount && pose != setup) alpha = 1; // Don't mix from uninitialized slot vertices. float[] vertices = verticesArray.setSize(vertexCount); + float[] frames = this.frames; + if (time < frames[0]) { // Time is before first frame. + switch (pose) { + case setup: + verticesArray.size = 0; + return; + case current: + alpha = 1 - alpha; + for (int i = 0; i < vertexCount; i++) + vertices[i] *= alpha; + } + return; + } + if (time >= frames[frames.length - 1]) { // Time is after last frame. float[] lastVertices = frameVertices[frames.length - 1]; if (alpha == 1) { // Vertex positions or deform offsets, no alpha. System.arraycopy(lastVertices, 0, vertices, 0, vertexCount); - } else if (setupPose) { + } else if (pose == setup) { VertexAttachment vertexAttachment = (VertexAttachment)slotAttachment; if (vertexAttachment.getBones() == null) { // Unweighted vertex positions, with alpha. @@ -912,7 +980,7 @@ public class Animation { float prev = prevVertices[i]; vertices[i] = prev + (nextVertices[i] - prev) * percent; } - } else if (setupPose) { + } else if (pose == setup) { VertexAttachment vertexAttachment = (VertexAttachment)slotAttachment; if (vertexAttachment.getBones() == null) { // Unweighted vertex positions, with alpha. @@ -974,15 +1042,15 @@ public class Animation { } /** Fires events for frames > lastTime and <= time. */ - public void apply (Skeleton skeleton, float lastTime, float time, Array firedEvents, float alpha, boolean setupPose, - boolean mixingOut) { + public void apply (Skeleton skeleton, float lastTime, float time, Array firedEvents, float alpha, MixPose pose, + MixDirection direction) { 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, mixingOut); + apply(skeleton, lastTime, Integer.MAX_VALUE, firedEvents, alpha, pose, direction); lastTime = -1f; } else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. return; @@ -1041,19 +1109,19 @@ public class Animation { drawOrders[frameIndex] = drawOrder; } - public void apply (Skeleton skeleton, float lastTime, float time, Array firedEvents, float alpha, boolean setupPose, - boolean mixingOut) { + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixPose pose, + MixDirection direction) { Array drawOrder = skeleton.drawOrder; Array slots = skeleton.slots; - if (mixingOut && setupPose) { + if (direction == out && pose == setup) { System.arraycopy(slots.items, 0, drawOrder.items, 0, slots.size); return; } float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. - if (setupPose) System.arraycopy(slots.items, 0, drawOrder.items, 0, slots.size); + if (pose == setup) System.arraycopy(slots.items, 0, drawOrder.items, 0, slots.size); return; } @@ -1114,27 +1182,32 @@ public class Animation { frames[frameIndex + BEND_DIRECTION] = bendDirection; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, boolean setupPose, - boolean mixingOut) { + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixPose pose, + MixDirection direction) { IkConstraint constraint = skeleton.ikConstraints.get(ikConstraintIndex); float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. - if (setupPose) { + switch (pose) { + case setup: constraint.mix = constraint.data.mix; constraint.bendDirection = constraint.data.bendDirection; + return; + case current: + constraint.mix += (constraint.data.mix - constraint.mix) * alpha; + constraint.bendDirection = constraint.data.bendDirection; } return; } if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. - if (setupPose) { + if (pose == setup) { constraint.mix = constraint.data.mix + (frames[frames.length + PREV_MIX] - constraint.data.mix) * alpha; - constraint.bendDirection = mixingOut ? constraint.data.bendDirection + constraint.bendDirection = direction == out ? constraint.data.bendDirection : (int)frames[frames.length + PREV_BEND_DIRECTION]; } else { constraint.mix += (frames[frames.length + PREV_MIX] - constraint.mix) * alpha; - if (!mixingOut) constraint.bendDirection = (int)frames[frames.length + PREV_BEND_DIRECTION]; + if (direction == in) constraint.bendDirection = (int)frames[frames.length + PREV_BEND_DIRECTION]; } return; } @@ -1145,12 +1218,13 @@ public class Animation { float frameTime = frames[frame]; float percent = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - if (setupPose) { + if (pose == setup) { constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha; - constraint.bendDirection = mixingOut ? constraint.data.bendDirection : (int)frames[frame + PREV_BEND_DIRECTION]; + constraint.bendDirection = direction == out ? constraint.data.bendDirection + : (int)frames[frame + PREV_BEND_DIRECTION]; } else { constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha; - if (!mixingOut) constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION]; + if (direction == in) constraint.bendDirection = (int)frames[frame + PREV_BEND_DIRECTION]; } } } @@ -1198,18 +1272,25 @@ public class Animation { frames[frameIndex + SHEAR] = shearMix; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, boolean setupPose, - boolean mixingOut) { + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixPose pose, + MixDirection direction) { TransformConstraint constraint = skeleton.transformConstraints.get(transformConstraintIndex); float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. - if (setupPose) { - TransformConstraintData data = constraint.data; + TransformConstraintData data = constraint.data; + switch (pose) { + case setup: constraint.rotateMix = data.rotateMix; constraint.translateMix = data.translateMix; constraint.scaleMix = data.scaleMix; constraint.shearMix = data.shearMix; + return; + case current: + constraint.rotateMix += (data.rotateMix - constraint.rotateMix) * alpha; + constraint.translateMix += (data.translateMix - constraint.translateMix) * alpha; + constraint.scaleMix += (data.scaleMix - constraint.scaleMix) * alpha; + constraint.shearMix += (data.shearMix - constraint.shearMix) * alpha; } return; } @@ -1237,7 +1318,7 @@ public class Animation { scale += (frames[frame + SCALE] - scale) * percent; shear += (frames[frame + SHEAR] - shear) * percent; } - if (setupPose) { + if (pose == setup) { TransformConstraintData data = constraint.data; constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha; constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha; @@ -1293,13 +1374,19 @@ public class Animation { frames[frameIndex + VALUE] = position; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, boolean setupPose, - boolean mixingOut) { + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixPose pose, + MixDirection direction) { PathConstraint constraint = skeleton.pathConstraints.get(pathConstraintIndex); float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. - if (setupPose) constraint.position = constraint.data.position; + switch (pose) { + case setup: + constraint.position = constraint.data.position; + return; + case current: + constraint.position += (constraint.data.position - constraint.position) * alpha; + } return; } @@ -1316,7 +1403,7 @@ public class Animation { position += (frames[frame + VALUE] - position) * percent; } - if (setupPose) + if (pose == setup) constraint.position = constraint.data.position + (position - constraint.data.position) * alpha; else constraint.position += (position - constraint.position) * alpha; @@ -1333,13 +1420,19 @@ public class Animation { return (TimelineType.pathConstraintSpacing.ordinal() << 24) + pathConstraintIndex; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, boolean setupPose, - boolean mixingOut) { + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixPose pose, + MixDirection direction) { PathConstraint constraint = skeleton.pathConstraints.get(pathConstraintIndex); float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. - if (setupPose) constraint.spacing = constraint.data.spacing; + switch (pose) { + case setup: + constraint.spacing = constraint.data.spacing; + return; + case current: + constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha; + } return; } @@ -1357,7 +1450,7 @@ public class Animation { spacing += (frames[frame + VALUE] - spacing) * percent; } - if (setupPose) + if (pose == setup) constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha; else constraint.spacing += (spacing - constraint.spacing) * alpha; @@ -1406,15 +1499,20 @@ public class Animation { frames[frameIndex + TRANSLATE] = translateMix; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, boolean setupPose, - boolean mixingOut) { + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixPose pose, + MixDirection direction) { PathConstraint constraint = skeleton.pathConstraints.get(pathConstraintIndex); float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. - if (setupPose) { + switch (pose) { + case setup: constraint.rotateMix = constraint.data.rotateMix; constraint.translateMix = constraint.data.translateMix; + return; + case current: + constraint.rotateMix += (constraint.data.rotateMix - constraint.rotateMix) * alpha; + constraint.translateMix += (constraint.data.translateMix - constraint.translateMix) * alpha; } return; } @@ -1436,7 +1534,7 @@ public class Animation { translate += (frames[frame + TRANSLATE] - translate) * percent; } - if (setupPose) { + if (pose == setup) { constraint.rotateMix = constraint.data.rotateMix + (rotate - constraint.data.rotateMix) * alpha; constraint.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha; } else { 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 991ef0274..29efee526 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java @@ -40,6 +40,8 @@ import com.badlogic.gdx.utils.Pool; import com.badlogic.gdx.utils.Pool.Poolable; import com.esotericsoftware.spine.Animation.AttachmentTimeline; import com.esotericsoftware.spine.Animation.DrawOrderTimeline; +import com.esotericsoftware.spine.Animation.MixDirection; +import com.esotericsoftware.spine.Animation.MixPose; import com.esotericsoftware.spine.Animation.RotateTimeline; import com.esotericsoftware.spine.Animation.Timeline; @@ -170,11 +172,12 @@ public class AnimationState { TrackEntry current = tracks.get(i); if (current == null || current.delay > 0) continue; applied = true; + MixPose currentPose = i == 0 ? MixPose.current : MixPose.currentLayered; // Apply mixing from entries first. float mix = current.alpha; if (current.mixingFrom != null) - mix *= applyMixingFrom(current, skeleton); + mix *= applyMixingFrom(current, skeleton, currentPose); else if (current.trackTime >= current.trackEnd && current.next == null) // mix = 0; // Set to setup pose the last time the entry will be applied. @@ -184,7 +187,7 @@ public class AnimationState { Object[] timelines = current.animation.timelines.items; if (mix == 1) { for (int ii = 0; ii < timelineCount; ii++) - ((Timeline)timelines[ii]).apply(skeleton, animationLast, animationTime, events, 1, true, false); + ((Timeline)timelines[ii]).apply(skeleton, animationLast, animationTime, events, 1, MixPose.setup, MixDirection.in); } else { int[] timelineData = current.timelineData.items; @@ -194,11 +197,11 @@ public class AnimationState { for (int ii = 0; ii < timelineCount; ii++) { Timeline timeline = (Timeline)timelines[ii]; - if (timeline instanceof RotateTimeline) { - applyRotateTimeline(timeline, skeleton, animationTime, mix, timelineData[ii] >= FIRST, timelinesRotation, - ii << 1, firstFrame); - } else - timeline.apply(skeleton, animationLast, animationTime, events, mix, timelineData[ii] >= FIRST, false); + MixPose pose = timelineData[ii] >= FIRST ? MixPose.setup : currentPose; + if (timeline instanceof RotateTimeline) + applyRotateTimeline(timeline, skeleton, animationTime, mix, pose, timelinesRotation, ii << 1, firstFrame); + else + timeline.apply(skeleton, animationLast, animationTime, events, mix, pose, MixDirection.in); } } queueEvents(current, animationTime); @@ -211,9 +214,9 @@ public class AnimationState { return applied; } - private float applyMixingFrom (TrackEntry to, Skeleton skeleton) { + private float applyMixingFrom (TrackEntry to, Skeleton skeleton, MixPose currentPose) { TrackEntry from = to.mixingFrom; - if (from.mixingFrom != null) applyMixingFrom(from, skeleton); + if (from.mixingFrom != null) applyMixingFrom(from, skeleton, currentPose); float mix; if (to.mixDuration == 0) // Single frame mix to undo mixingFrom changes. @@ -235,26 +238,28 @@ public class AnimationState { if (firstFrame) from.timelinesRotation.setSize(timelineCount << 1); float[] timelinesRotation = from.timelinesRotation.items; - boolean first; + MixPose pose; float alphaDip = from.alpha * to.interruptAlpha, alphaMix = alphaDip * (1 - mix), alpha; from.totalAlpha = 0; for (int i = 0; i < timelineCount; i++) { Timeline timeline = (Timeline)timelines[i]; switch (timelineData[i]) { case SUBSEQUENT: - first = false; + if (!attachments && timeline instanceof AttachmentTimeline) continue; + if (!drawOrder && timeline instanceof DrawOrderTimeline) continue; + pose = currentPose; alpha = alphaMix; break; case FIRST: - first = true; + pose = MixPose.setup; alpha = alphaMix; break; case DIP: - first = true; + pose = MixPose.setup; alpha = alphaDip; break; default: - first = true; + pose = MixPose.setup; alpha = alphaDip; TrackEntry dipMix = (TrackEntry)timelineDipMix[i]; alpha *= Math.max(0, 1 - dipMix.mixTime / dipMix.mixDuration); @@ -262,14 +267,9 @@ public class AnimationState { } from.totalAlpha += alpha; if (timeline instanceof RotateTimeline) - applyRotateTimeline(timeline, skeleton, animationTime, alpha, first, timelinesRotation, i << 1, firstFrame); - else { - if (!first) { - if (!attachments && timeline instanceof AttachmentTimeline) continue; - if (!drawOrder && timeline instanceof DrawOrderTimeline) continue; - } - timeline.apply(skeleton, animationLast, animationTime, events, alpha, first, true); - } + applyRotateTimeline(timeline, skeleton, animationTime, alpha, pose, timelinesRotation, i << 1, firstFrame); + else + timeline.apply(skeleton, animationLast, animationTime, events, alpha, pose, MixDirection.out); } if (to.mixDuration > 0) queueEvents(from, animationTime); @@ -280,13 +280,13 @@ public class AnimationState { return mix; } - private void applyRotateTimeline (Timeline timeline, Skeleton skeleton, float time, float alpha, boolean setupPose, + private void applyRotateTimeline (Timeline timeline, Skeleton skeleton, float time, float alpha, MixPose pose, float[] timelinesRotation, int i, boolean firstFrame) { if (firstFrame) timelinesRotation[i] = 0; if (alpha == 1) { - timeline.apply(skeleton, 0, time, null, 1, setupPose, false); + timeline.apply(skeleton, 0, time, null, 1, pose, MixDirection.in); return; } @@ -294,7 +294,7 @@ public class AnimationState { Bone bone = skeleton.bones.get(rotateTimeline.boneIndex); float[] frames = rotateTimeline.frames; if (time < frames[0]) { // Time is before first frame. - if (setupPose) bone.rotation = bone.data.rotation; + if (pose == MixPose.setup) bone.rotation = bone.data.rotation; return; } @@ -315,8 +315,8 @@ public class AnimationState { r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360; } - // Mix between rotations using the direction of the shortest route on the first frame while detecting crosses. - float r1 = setupPose ? bone.data.rotation : bone.rotation; + // Mix between rotations using the direction of the shortest route on the first frame. + float r1 = pose == MixPose.setup ? bone.data.rotation : bone.rotation; float total, diff = r2 - r1; if (diff == 0) total = timelinesRotation[i]; diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Event.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Event.java index 70b59777c..77c9451c3 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Event.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Event.java @@ -35,7 +35,8 @@ import com.esotericsoftware.spine.AnimationState.AnimationStateListener; /** Stores the current pose values for an {@link Event}. *

- * See Timeline {@link Timeline#apply(Skeleton, float, float, com.badlogic.gdx.utils.Array, float, boolean, boolean)}, + * See Timeline + * {@link Timeline#apply(Skeleton, float, float, com.badlogic.gdx.utils.Array, float, com.esotericsoftware.spine.Animation.MixPose, com.esotericsoftware.spine.Animation.MixDirection)}, * AnimationStateListener {@link AnimationStateListener#event(com.esotericsoftware.spine.AnimationState.TrackEntry, Event)}, and * Events in the Spine User Guide. */ public class Event {