From 406a8fba134f1096fd718057429fe2b2dda70b46 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Sat, 21 Oct 2017 21:48:04 +0200 Subject: [PATCH 01/10] [libgdx] Fixed off by 1 errors. --- .../src/com/esotericsoftware/spine/NormalMapTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 c1d6805fd..376b6f333 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 @@ -96,7 +96,7 @@ public class NormalMapTest extends ApplicationAdapter { atlasTexture = atlas.getRegions().first().getTexture(); normalMapTexture = new Texture(Gdx.files.internal(skeletonPath + "-normal.png")); - + SkeletonJson json = new SkeletonJson(atlas); skeletonData = json.readSkeletonData(Gdx.files.internal(skeletonPath + ".json")); if (animationName != null) animation = skeletonData.findAnimation(animationName); @@ -116,7 +116,7 @@ public class NormalMapTest extends ApplicationAdapter { } public boolean touchDragged (int screenX, int screenY, int pointer) { - skeleton.setPosition(screenX, Gdx.graphics.getHeight() - screenY); + skeleton.setPosition(screenX, Gdx.graphics.getHeight() - 1 - screenY); return true; } @@ -137,7 +137,7 @@ public class NormalMapTest extends ApplicationAdapter { skeleton.update(Gdx.graphics.getDeltaTime()); lightPosition.x = Gdx.input.getX(); - lightPosition.y = (Gdx.graphics.getHeight() - Gdx.input.getY()); + lightPosition.y = (Gdx.graphics.getHeight() - 1 - Gdx.input.getY()); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); From 1ae9fb955bb7ff79f1f13e2439ecfca2a9ed7b29 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Mon, 23 Oct 2017 18:02:06 +0200 Subject: [PATCH 02/10] [libgdx] Documented track entry timeline mix types. --- .../spine/AnimationState.java | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) 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 257809d9a..376f613ae 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java @@ -51,7 +51,31 @@ import com.esotericsoftware.spine.Animation.Timeline; * See Applying Animations in the Spine Runtimes Guide. */ public class AnimationState { static private final Animation emptyAnimation = new Animation("", new Array(0), 0); - static private final int SUBSEQUENT = 0, FIRST = 1, DIP = 2, DIP_MIX = 3; + + /** 1) A previously applied timeline has set this property.
+ * Result: Mix from the current pose to the timeline pose. */ + static private final int SUBSEQUENT = 0; + /** 1) This is the first timeline to set this property.
+ * 2) The next track entry applied after this one does not have a timeline to set this property.
+ * Result: Mix from the setup pose to the timeline pose. */ + static private final int FIRST = 1; + /** 1) This is the first timeline to set this property.
+ * 2) The next track entry to be applied does have a timeline to set this property.
+ * 3) The next track entry after that one does not have a timeline to set this property.
+ * Result: Mix from the setup pose to the timeline pose, but avoid the "dipping" problem by not using the mix percentage. This + * means the timeline pose won't mix out toward the setup pose. A subsequent timeline will set this property using a mix. */ + static private final int DIP = 2; + /** 1) This is the first timeline to set this property.
+ * 2) The next track entry to be applied does have a timeline to set this property.
+ * 3) The next track entry after that one does have a timeline to set this property.
+ * 4) timelineDipMix stores the first subsequent track entry that does not have a timeline to set this property.
+ * Result: This is the same as DIP except the mix percentage from the timelineDipMix track entry is used. This handles when + * more than 2 track entries in a row have a timeline which sets the same property.
+ * Eg, A -> B -> C -> D where A, B, and C have a timeline to set the same property, but D does not. When A is applied, A's mix + * percentage is not used to avoid dipping, however a later track entry (D, the first entry without a timeline which sets the + * property) is actually mixing out A (which affects B and C). Without using D's mix percentage, A would be applied fully until + * mixed out, causing snapping. */ + static private final int DIP_MIX = 3; private AnimationStateData data; final Array tracks = new Array(); From bda035e8362022d642057cb6cdcb1e8a5215ba9d Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Fri, 27 Oct 2017 00:23:41 +0200 Subject: [PATCH 03/10] Added additive mixing, still a WIP. --- .../esotericsoftware/spine/BonePlotting.java | 4 +- .../esotericsoftware/spine/Box2DExample.java | 4 +- .../spine/EventTimelineTests.java | 10 +- .../com/esotericsoftware/spine/MixTest.java | 16 +- .../esotericsoftware/spine/NormalMapTest.java | 4 +- .../com/esotericsoftware/spine/Animation.java | 193 ++++++++++-------- .../spine/AnimationState.java | 151 ++++++++------ .../src/com/esotericsoftware/spine/Event.java | 2 +- .../spine/SkeletonViewer.java | 22 +- 9 files changed, 236 insertions(+), 170 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 e755b7a6c..2e2582a17 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 @@ -32,7 +32,7 @@ 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.Animation.MixBlend; import com.esotericsoftware.spine.attachments.AttachmentLoader; import com.esotericsoftware.spine.attachments.BoundingBoxAttachment; import com.esotericsoftware.spine.attachments.ClippingAttachment; @@ -76,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, MixPose.current, MixDirection.in); + animation.apply(skeleton, time, time, false, null, 1, MixBlend.first, 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 141c3d26a..9761a3225 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 @@ -31,7 +31,7 @@ package com.esotericsoftware.spine; import com.esotericsoftware.spine.Animation.MixDirection; -import com.esotericsoftware.spine.Animation.MixPose; +import com.esotericsoftware.spine.Animation.MixBlend; import com.esotericsoftware.spine.attachments.AtlasAttachmentLoader; import com.esotericsoftware.spine.attachments.RegionAttachment; @@ -146,7 +146,7 @@ public class Box2DExample extends ApplicationAdapter { batch.setTransformMatrix(camera.view); batch.begin(); - animation.apply(skeleton, time, time, true, events, 1, MixPose.current, MixDirection.in); + animation.apply(skeleton, time, time, true, events, 1, MixBlend.first, 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 0a0530340..e7232077a 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 @@ -32,7 +32,7 @@ package com.esotericsoftware.spine; import com.esotericsoftware.spine.Animation.EventTimeline; import com.esotericsoftware.spine.Animation.MixDirection; -import com.esotericsoftware.spine.Animation.MixPose; +import com.esotericsoftware.spine.Animation.MixBlend; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.StringBuilder; @@ -177,7 +177,7 @@ public class EventTimelineTests { int beforeCount = firedEvents.size; Array original = new Array(firedEvents); - timeline.apply(skeleton, lastTimeLooped, timeLooped, firedEvents, 1, MixPose.current, MixDirection.in); + timeline.apply(skeleton, lastTimeLooped, timeLooped, firedEvents, 1, MixBlend.first, MixDirection.in); 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, MixPose.current, MixDirection.in); + timeline.apply(skeleton, lastTimeLooped, timeLooped, original, 1, MixBlend.first, MixDirection.in); 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, MixPose.current, MixDirection.in); + timeline.apply(skeleton, lastTimeLooped, timeLooped, original, 1, MixBlend.first, MixDirection.in); fail("Wrong event fired."); } eventIndex++; @@ -206,7 +206,7 @@ public class EventTimelineTests { i++; } if (firedEvents.size < eventsCount) { - timeline.apply(skeleton, lastTimeLooped, timeLooped, firedEvents, 1, MixPose.current, MixDirection.in); + timeline.apply(skeleton, lastTimeLooped, timeLooped, firedEvents, 1, MixBlend.first, 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 4fb6ea5aa..b2f4b8ad9 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 @@ -38,7 +38,7 @@ 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; +import com.esotericsoftware.spine.Animation.MixBlend; public class MixTest extends ApplicationAdapter { SpriteBatch batch; @@ -105,23 +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, MixPose.current, MixDirection.in); + walkAnimation.apply(skeleton, time, time, true, events, 1, MixBlend.first, MixDirection.in); } else if (time > blendOutStart) { // blend out jump - walkAnimation.apply(skeleton, time, time, true, events, 1, MixPose.current, MixDirection.in); + walkAnimation.apply(skeleton, time, time, true, events, 1, MixBlend.first, MixDirection.in); jumpAnimation.apply(skeleton, time - beforeJump, time - beforeJump, false, events, 1 - (time - blendOutStart) / blendOut, - MixPose.current, MixDirection.in); + MixBlend.first, MixDirection.in); } else if (time > beforeJump + blendIn) { // just jump - jumpAnimation.apply(skeleton, time - beforeJump, time - beforeJump, false, events, 1, MixPose.current, MixDirection.in); + jumpAnimation.apply(skeleton, time - beforeJump, time - beforeJump, false, events, 1, MixBlend.first, MixDirection.in); } else if (time > beforeJump) { // blend in jump - walkAnimation.apply(skeleton, time, time, true, events, 1, MixPose.current, MixDirection.in); + walkAnimation.apply(skeleton, time, time, true, events, 1, MixBlend.first, MixDirection.in); jumpAnimation.apply(skeleton, time - beforeJump, time - beforeJump, false, events, (time - beforeJump) / blendIn, - MixPose.current, MixDirection.in); + MixBlend.first, MixDirection.in); } else { // just walk before jump - walkAnimation.apply(skeleton, time, time, true, events, 1, MixPose.current, MixDirection.in); + walkAnimation.apply(skeleton, time, time, true, events, 1, MixBlend.first, 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 376b6f333..5853d261f 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 @@ -57,7 +57,7 @@ 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; +import com.esotericsoftware.spine.Animation.MixBlend; public class NormalMapTest extends ApplicationAdapter { String skeletonPath, animationName; @@ -132,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, MixPose.current, MixDirection.in); + if (animation != null) animation.apply(skeleton, lastTime, time, true, null, 1, MixBlend.first, 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 5daefe0a7..ecadc4105 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -30,8 +30,8 @@ package com.esotericsoftware.spine; +import static com.esotericsoftware.spine.Animation.MixBlend.*; 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; @@ -69,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, MixPose, MixDirection)}. */ - public void apply (Skeleton skeleton, float lastTime, float time, boolean loop, Array events, float alpha, MixPose pose, - MixDirection direction) { + * See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixBlend, MixDirection)}. */ + public void apply (Skeleton skeleton, float lastTime, float time, boolean loop, Array events, float alpha, + MixBlend blend, MixDirection direction) { if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); if (loop && duration != 0) { @@ -81,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, pose, direction); + timelines.get(i).apply(skeleton, lastTime, time, events, alpha, blend, direction); } /** The animation's name, which is unique within the skeleton. */ @@ -145,39 +145,45 @@ 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 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 + * @param alpha 0 applies the current or setup value (depending on blend). 1 applies the timeline value. + * Between 0 and 1 applies a value between the current or setup value 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 blend 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, + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixBlend blend, 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. + /** Controls how a timeline value is mixed with the setup pose value or current pose value. *

- * 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). */ + * See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixBlend, MixDirection)}. */ + static public enum MixBlend { + /** Transitions from the setup value to the timeline value (the current value is not used). Before the first key, the setup + * value is set. */ 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 + /** Transitions from the current value to the timeline value. Before the first key, transitions from the current value to + * the setup value, except for timelines which perform instant transitions, such as {@link DrawOrderTimeline} or + * {@link AttachmentTimeline}. + *

+ * first is intended for the first animations applied, not for animations layered on top of those. */ + first, + /** Transitions from the current value to the timeline value. No change is made before the first key (the current value is + * kept until the first key). */ + replace, + /** Transitions from the current value to the current value plus the timeline value. No change is made before the first key + * (the current value is kept until the first key). */ + add } - /** 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). + /** Indicates whether a timeline's alpha is mixing out over time toward 0 (the setup or current pose value) or + * mixing in toward 1 (the timeline's value). *

- * See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixPose, MixDirection)}. */ + * See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixBlend, MixDirection)}. */ static public enum MixDirection { in, out } @@ -318,17 +324,17 @@ public class Animation { frames[frameIndex + ROTATION] = degrees; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixPose pose, + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixBlend blend, MixDirection direction) { Bone bone = skeleton.bones.get(boneIndex); float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. - switch (pose) { + switch (blend) { case setup: bone.rotation = bone.data.rotation; return; - case current: + case first: float r = bone.data.rotation - bone.rotation; r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; bone.rotation += r * alpha; @@ -337,11 +343,17 @@ public class Animation { } if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. - 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; + float r = frames[frames.length + PREV_ROTATION]; + switch (blend) { + case setup: + bone.rotation = bone.data.rotation + r * alpha; + break; + case first: + case replace: + r += bone.data.rotation - bone.rotation; r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; // Wrap within -180 and 180. + // Fall through. + case add: bone.rotation += r * alpha; } return; @@ -356,11 +368,16 @@ public class Animation { float r = frames[frame + ROTATION] - prevRotation; r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; r = prevRotation + r * percent; - if (pose == setup) { + switch (blend) { + case setup: r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; bone.rotation = bone.data.rotation + r * alpha; - } else { - r = bone.data.rotation + r - bone.rotation; + break; + case first: + case replace: + r += bone.data.rotation - bone.rotation; + // Fall through. + case add: r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; bone.rotation += r * alpha; } @@ -408,18 +425,18 @@ public class Animation { frames[frameIndex + Y] = y; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixPose pose, + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixBlend blend, MixDirection direction) { Bone bone = skeleton.bones.get(boneIndex); float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. - switch (pose) { + switch (blend) { case setup: bone.x = bone.data.x; bone.y = bone.data.y; return; - case current: + case first: bone.x += (bone.data.x - bone.x) * alpha; bone.y += (bone.data.y - bone.y) * alpha; } @@ -442,7 +459,7 @@ public class Animation { x += (frames[frame + X] - x) * percent; y += (frames[frame + Y] - y) * percent; } - if (pose == setup) { + if (blend == setup) { bone.x = bone.data.x + x * alpha; bone.y = bone.data.y + y * alpha; } else { @@ -462,18 +479,18 @@ public class Animation { return (TimelineType.scale.ordinal() << 24) + boneIndex; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixPose pose, + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixBlend blend, MixDirection direction) { Bone bone = skeleton.bones.get(boneIndex); float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. - switch (pose) { + switch (blend) { case setup: bone.scaleX = bone.data.scaleX; bone.scaleY = bone.data.scaleY; return; - case current: + case first: bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha; bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha; } @@ -501,7 +518,7 @@ public class Animation { bone.scaleY = y; } else { float bx, by; - if (pose == setup) { + if (blend == setup) { bx = bone.data.scaleX; by = bone.data.scaleY; } else { @@ -532,18 +549,18 @@ public class Animation { return (TimelineType.shear.ordinal() << 24) + boneIndex; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixPose pose, + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixBlend blend, MixDirection direction) { Bone bone = skeleton.bones.get(boneIndex); float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. - switch (pose) { + switch (blend) { case setup: bone.shearX = bone.data.shearX; bone.shearY = bone.data.shearY; return; - case current: + case first: bone.shearX += (bone.data.shearX - bone.shearX) * alpha; bone.shearY += (bone.data.shearY - bone.shearY) * alpha; } @@ -566,7 +583,7 @@ public class Animation { x = x + (frames[frame + X] - x) * percent; y = y + (frames[frame + Y] - y) * percent; } - if (pose == setup) { + if (blend == setup) { bone.shearX = bone.data.shearX + x * alpha; bone.shearY = bone.data.shearY + y * alpha; } else { @@ -619,17 +636,17 @@ public class Animation { frames[frameIndex + A] = a; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixPose pose, + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixBlend blend, MixDirection direction) { Slot slot = skeleton.slots.get(slotIndex); float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. - switch (pose) { + switch (blend) { case setup: slot.color.set(slot.data.color); return; - case current: + case first: 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); @@ -664,7 +681,7 @@ public class Animation { slot.color.set(r, g, b, a); else { Color color = slot.color; - if (pose == setup) color.set(slot.data.color); + if (blend == setup) color.set(slot.data.color); color.add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha); } } @@ -717,18 +734,18 @@ public class Animation { frames[frameIndex + B2] = b2; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixPose pose, + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixBlend blend, MixDirection direction) { Slot slot = skeleton.slots.get(slotIndex); float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. - switch (pose) { + switch (blend) { case setup: slot.color.set(slot.data.color); slot.darkColor.set(slot.data.darkColor); return; - case current: + case first: 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); @@ -774,7 +791,7 @@ public class Animation { slot.darkColor.set(r2, g2, b2, 1); } else { Color light = slot.color, dark = slot.darkColor; - if (pose == setup) { + if (blend == setup) { light.set(slot.data.color); dark.set(slot.data.darkColor); } @@ -830,11 +847,11 @@ public class Animation { attachmentNames[frameIndex] = attachmentName; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixPose pose, + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixBlend blend, MixDirection direction) { Slot slot = skeleton.slots.get(slotIndex); - if (direction == out && pose == setup) { + if (direction == out && blend == setup) { String attachmentName = slot.data.attachmentName; slot.setAttachment(attachmentName == null ? null : skeleton.getAttachment(slotIndex, attachmentName)); return; @@ -842,7 +859,7 @@ public class Animation { float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. - if (pose == setup) { + if (blend == setup) { String attachmentName = slot.data.attachmentName; slot.setAttachment(attachmentName == null ? null : skeleton.getAttachment(slotIndex, attachmentName)); } @@ -913,7 +930,7 @@ public class Animation { frameVertices[frameIndex] = vertices; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixPose pose, + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixBlend blend, MixDirection direction) { Slot slot = skeleton.slots.get(slotIndex); @@ -921,7 +938,7 @@ public class Animation { if (!(slotAttachment instanceof VertexAttachment) || !((VertexAttachment)slotAttachment).applyDeform(attachment)) return; FloatArray verticesArray = slot.getAttachmentVertices(); - if (verticesArray.size == 0) alpha = 1; + if (verticesArray.size == 0) blend = setup; float[][] frameVertices = this.frameVertices; int vertexCount = frameVertices[0].length; @@ -929,11 +946,11 @@ public class Animation { float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. VertexAttachment vertexAttachment = (VertexAttachment)slotAttachment; - switch (pose) { + switch (blend) { case setup: verticesArray.clear(); return; - case current: + case first: if (alpha == 1) { verticesArray.clear(); return; @@ -961,7 +978,7 @@ public class Animation { if (alpha == 1) { // Vertex positions or deform offsets, no alpha. System.arraycopy(lastVertices, 0, vertices, 0, vertexCount); - } else if (pose == setup) { + } else if (blend == setup) { VertexAttachment vertexAttachment = (VertexAttachment)slotAttachment; if (vertexAttachment.getBones() == null) { // Unweighted vertex positions, with alpha. @@ -996,7 +1013,7 @@ public class Animation { float prev = prevVertices[i]; vertices[i] = prev + (nextVertices[i] - prev) * percent; } - } else if (pose == setup) { + } else if (blend == setup) { VertexAttachment vertexAttachment = (VertexAttachment)slotAttachment; if (vertexAttachment.getBones() == null) { // Unweighted vertex positions, with alpha. @@ -1058,7 +1075,7 @@ public class Animation { } /** Fires events for frames > lastTime and <= time. */ - public void apply (Skeleton skeleton, float lastTime, float time, Array firedEvents, float alpha, MixPose pose, + public void apply (Skeleton skeleton, float lastTime, float time, Array firedEvents, float alpha, MixBlend blend, MixDirection direction) { if (firedEvents == null) return; @@ -1066,7 +1083,7 @@ public class Animation { int frameCount = frames.length; if (lastTime > time) { // Fire events after last time for looped animations. - apply(skeleton, lastTime, Integer.MAX_VALUE, firedEvents, alpha, pose, direction); + apply(skeleton, lastTime, Integer.MAX_VALUE, firedEvents, alpha, blend, direction); lastTime = -1f; } else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. return; @@ -1125,19 +1142,19 @@ public class Animation { drawOrders[frameIndex] = drawOrder; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixPose pose, + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixBlend blend, MixDirection direction) { Array drawOrder = skeleton.drawOrder; Array slots = skeleton.slots; - if (direction == out && pose == setup) { + if (direction == out && blend == 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 (pose == setup) System.arraycopy(slots.items, 0, drawOrder.items, 0, slots.size); + if (blend == setup) System.arraycopy(slots.items, 0, drawOrder.items, 0, slots.size); return; } @@ -1198,18 +1215,18 @@ public class Animation { frames[frameIndex + BEND_DIRECTION] = bendDirection; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixPose pose, + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixBlend blend, MixDirection direction) { IkConstraint constraint = skeleton.ikConstraints.get(ikConstraintIndex); float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. - switch (pose) { + switch (blend) { case setup: constraint.mix = constraint.data.mix; constraint.bendDirection = constraint.data.bendDirection; return; - case current: + case first: constraint.mix += (constraint.data.mix - constraint.mix) * alpha; constraint.bendDirection = constraint.data.bendDirection; } @@ -1217,7 +1234,7 @@ public class Animation { } if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. - if (pose == setup) { + if (blend == setup) { constraint.mix = constraint.data.mix + (frames[frames.length + PREV_MIX] - constraint.data.mix) * alpha; constraint.bendDirection = direction == out ? constraint.data.bendDirection : (int)frames[frames.length + PREV_BEND_DIRECTION]; @@ -1234,7 +1251,7 @@ public class Animation { float frameTime = frames[frame]; float percent = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - if (pose == setup) { + if (blend == setup) { constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha; constraint.bendDirection = direction == out ? constraint.data.bendDirection : (int)frames[frame + PREV_BEND_DIRECTION]; @@ -1288,21 +1305,21 @@ public class Animation { frames[frameIndex + SHEAR] = shearMix; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixPose pose, + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixBlend blend, MixDirection direction) { TransformConstraint constraint = skeleton.transformConstraints.get(transformConstraintIndex); float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. TransformConstraintData data = constraint.data; - switch (pose) { + switch (blend) { case setup: constraint.rotateMix = data.rotateMix; constraint.translateMix = data.translateMix; constraint.scaleMix = data.scaleMix; constraint.shearMix = data.shearMix; return; - case current: + case first: constraint.rotateMix += (data.rotateMix - constraint.rotateMix) * alpha; constraint.translateMix += (data.translateMix - constraint.translateMix) * alpha; constraint.scaleMix += (data.scaleMix - constraint.scaleMix) * alpha; @@ -1334,7 +1351,7 @@ public class Animation { scale += (frames[frame + SCALE] - scale) * percent; shear += (frames[frame + SHEAR] - shear) * percent; } - if (pose == setup) { + if (blend == setup) { TransformConstraintData data = constraint.data; constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha; constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha; @@ -1390,17 +1407,17 @@ public class Animation { frames[frameIndex + VALUE] = position; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixPose pose, + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixBlend blend, MixDirection direction) { PathConstraint constraint = skeleton.pathConstraints.get(pathConstraintIndex); float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. - switch (pose) { + switch (blend) { case setup: constraint.position = constraint.data.position; return; - case current: + case first: constraint.position += (constraint.data.position - constraint.position) * alpha; } return; @@ -1419,7 +1436,7 @@ public class Animation { position += (frames[frame + VALUE] - position) * percent; } - if (pose == setup) + if (blend == setup) constraint.position = constraint.data.position + (position - constraint.data.position) * alpha; else constraint.position += (position - constraint.position) * alpha; @@ -1436,17 +1453,17 @@ public class Animation { return (TimelineType.pathConstraintSpacing.ordinal() << 24) + pathConstraintIndex; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixPose pose, + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixBlend blend, MixDirection direction) { PathConstraint constraint = skeleton.pathConstraints.get(pathConstraintIndex); float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. - switch (pose) { + switch (blend) { case setup: constraint.spacing = constraint.data.spacing; return; - case current: + case first: constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha; } return; @@ -1466,7 +1483,7 @@ public class Animation { spacing += (frames[frame + VALUE] - spacing) * percent; } - if (pose == setup) + if (blend == setup) constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha; else constraint.spacing += (spacing - constraint.spacing) * alpha; @@ -1515,18 +1532,18 @@ public class Animation { frames[frameIndex + TRANSLATE] = translateMix; } - public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixPose pose, + public void apply (Skeleton skeleton, float lastTime, float time, Array events, float alpha, MixBlend blend, MixDirection direction) { PathConstraint constraint = skeleton.pathConstraints.get(pathConstraintIndex); float[] frames = this.frames; if (time < frames[0]) { // Time is before first frame. - switch (pose) { + switch (blend) { case setup: constraint.rotateMix = constraint.data.rotateMix; constraint.translateMix = constraint.data.translateMix; return; - case current: + case first: constraint.rotateMix += (constraint.data.rotateMix - constraint.rotateMix) * alpha; constraint.translateMix += (constraint.data.translateMix - constraint.translateMix) * alpha; } @@ -1550,7 +1567,7 @@ public class Animation { translate += (frames[frame + TRANSLATE] - translate) * percent; } - if (pose == setup) { + if (blend == 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 376f613ae..4cc13530b 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java @@ -40,8 +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.MixBlend; import com.esotericsoftware.spine.Animation.MixDirection; -import com.esotericsoftware.spine.Animation.MixPose; import com.esotericsoftware.spine.Animation.RotateTimeline; import com.esotericsoftware.spine.Animation.Timeline; @@ -197,12 +197,14 @@ 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; + + // Track 0 animations aren't for layering, so do not show the previously applied animations before the first key. + MixBlend blend = i == 0 ? MixBlend.first : current.mixBlend; // Apply mixing from entries first. float mix = current.alpha; if (current.mixingFrom != null) - mix *= applyMixingFrom(current, skeleton, currentPose); + mix *= applyMixingFrom(current, skeleton, blend); else if (current.trackTime >= current.trackEnd && current.next == null) // mix = 0; // Set to setup pose the last time the entry will be applied. @@ -210,9 +212,9 @@ public class AnimationState { float animationLast = current.animationLast, animationTime = current.getAnimationTime(); int timelineCount = current.animation.timelines.size; Object[] timelines = current.animation.timelines.items; - if (mix == 1) { + if (mix == 1 || blend == MixBlend.add) { for (int ii = 0; ii < timelineCount; ii++) - ((Timeline)timelines[ii]).apply(skeleton, animationLast, animationTime, events, 1, MixPose.setup, MixDirection.in); + ((Timeline)timelines[ii]).apply(skeleton, animationLast, animationTime, events, mix, blend, MixDirection.in); } else { int[] timelineData = current.timelineData.items; @@ -222,11 +224,12 @@ public class AnimationState { for (int ii = 0; ii < timelineCount; ii++) { Timeline timeline = (Timeline)timelines[ii]; - 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); + MixBlend timelineBlend = timelineData[ii] >= FIRST ? MixBlend.setup : blend; + if (timeline instanceof RotateTimeline) { + applyRotateTimeline(timeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, ii << 1, + firstFrame); + } else + timeline.apply(skeleton, animationLast, animationTime, events, mix, timelineBlend, MixDirection.in); } } queueEvents(current, animationTime); @@ -239,9 +242,9 @@ public class AnimationState { return applied; } - private float applyMixingFrom (TrackEntry to, Skeleton skeleton, MixPose currentPose) { + private float applyMixingFrom (TrackEntry to, Skeleton skeleton, MixBlend blend) { TrackEntry from = to.mixingFrom; - if (from.mixingFrom != null) applyMixingFrom(from, skeleton, currentPose); + if (from.mixingFrom != null) applyMixingFrom(from, skeleton, blend); float mix; if (to.mixDuration == 0) // Single frame mix to undo mixingFrom changes. @@ -256,45 +259,58 @@ public class AnimationState { float animationLast = from.animationLast, animationTime = from.getAnimationTime(); int timelineCount = from.animation.timelines.size; Object[] timelines = from.animation.timelines.items; - int[] timelineData = from.timelineData.items; - Object[] timelineDipMix = from.timelineDipMix.items; - - boolean firstFrame = from.timelinesRotation.size == 0; - if (firstFrame) from.timelinesRotation.setSize(timelineCount << 1); - float[] timelinesRotation = from.timelinesRotation.items; - - 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: - if (!attachments && timeline instanceof AttachmentTimeline) continue; - if (!drawOrder && timeline instanceof DrawOrderTimeline) continue; - pose = currentPose; - alpha = alphaMix; - break; - case FIRST: - pose = MixPose.setup; - alpha = alphaMix; - break; - case DIP: - pose = MixPose.setup; - alpha = alphaDip; - break; - default: - pose = MixPose.setup; - alpha = alphaDip; - TrackEntry dipMix = (TrackEntry)timelineDipMix[i]; - alpha *= Math.max(0, 1 - dipMix.mixTime / dipMix.mixDuration); - break; + + if (blend != MixBlend.first) blend = from.mixBlend; + if (blend == MixBlend.add) { + for (int i = 0; i < timelineCount; i++) + ((Timeline)timelines[i]).apply(skeleton, animationLast, animationTime, events, alphaMix, blend, MixDirection.out); + } else { + int[] timelineData = from.timelineData.items; + Object[] timelineDipMix = from.timelineDipMix.items; + + boolean firstFrame = from.timelinesRotation.size == 0; + if (firstFrame) from.timelinesRotation.setSize(timelineCount << 1); + float[] timelinesRotation = from.timelinesRotation.items; + + from.totalAlpha = 0; + for (int i = 0; i < timelineCount; i++) { + Timeline timeline = (Timeline)timelines[i]; + MixBlend timelineBlend; + switch (timelineData[i]) { + case SUBSEQUENT: + if (!attachments && timeline instanceof AttachmentTimeline) continue; + if (!drawOrder && timeline instanceof DrawOrderTimeline) continue; + timelineBlend = blend; + alpha = alphaMix; + break; + case FIRST: + timelineBlend = MixBlend.setup; + alpha = alphaMix; + break; + case DIP: + timelineBlend = MixBlend.setup; +// alpha = mix == 1 ? 0 : alphaDip; + alpha = alphaDip; + break; + default: + timelineBlend = MixBlend.setup; + // BOZO! - Bad fix. +// if (mix == 1) +// alpha = 0; +// else { + TrackEntry dipMix = (TrackEntry)timelineDipMix[i]; + alpha = alphaDip * Math.max(0, 1 - dipMix.mixTime / dipMix.mixDuration); +// } + break; + } + from.totalAlpha += alpha; + if (timeline instanceof RotateTimeline) { + applyRotateTimeline(timeline, skeleton, animationTime, alpha, timelineBlend, timelinesRotation, i << 1, + firstFrame); + } else + timeline.apply(skeleton, animationLast, animationTime, events, alpha, timelineBlend, MixDirection.out); } - from.totalAlpha += alpha; - if (timeline instanceof RotateTimeline) - 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); @@ -305,13 +321,13 @@ public class AnimationState { return mix; } - private void applyRotateTimeline (Timeline timeline, Skeleton skeleton, float time, float alpha, MixPose pose, + private void applyRotateTimeline (Timeline timeline, Skeleton skeleton, float time, float alpha, MixBlend blend, float[] timelinesRotation, int i, boolean firstFrame) { if (firstFrame) timelinesRotation[i] = 0; if (alpha == 1) { - timeline.apply(skeleton, 0, time, null, 1, pose, MixDirection.in); + timeline.apply(skeleton, 0, time, null, 1, blend, MixDirection.in); return; } @@ -319,7 +335,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 (pose == MixPose.setup) bone.rotation = bone.data.rotation; + if (blend == MixBlend.setup) bone.rotation = bone.data.rotation; return; } @@ -341,7 +357,7 @@ public class AnimationState { } // 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 r1 = blend == MixBlend.setup ? bone.data.rotation : bone.rotation; float total, diff = r2 - r1; if (diff == 0) total = timelinesRotation[i]; @@ -646,7 +662,7 @@ public class AnimationState { for (int i = 0, n = tracks.size; i < n; i++) { TrackEntry entry = tracks.get(i); - if (entry != null) entry.setTimelineData(null, mixingTo, propertyIDs); + if (entry != null && entry.mixBlend != MixBlend.add) entry.setTimelineData(null, mixingTo, propertyIDs); } } @@ -731,6 +747,7 @@ public class AnimationState { float animationStart, animationEnd, animationLast, nextAnimationLast; float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale; float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha; + MixBlend mixBlend = MixBlend.replace; final IntArray timelineData = new IntArray(); final Array timelineDipMix = new Array(); final FloatArray timelinesRotation = new FloatArray(); @@ -999,8 +1016,8 @@ public class AnimationState { * {@link AnimationStateData#getMix(Animation, Animation)} based on the animation before this animation (if any). *

* The mixDuration can be set manually rather than use the value from - * {@link AnimationStateData#getMix(Animation, Animation)}. In that case, the mixDuration must be set for a new - * track entry before {@link AnimationState#update(float)} is next called. + * {@link AnimationStateData#getMix(Animation, Animation)}. In that case, the mixDuration can be set for a new + * track entry only before {@link AnimationState#update(float)} is first called. *

* When using {@link AnimationState#addAnimation(int, Animation, boolean, float)} with a delay <= 0, note the * {@link #getDelay()} is set using the mix duration from the {@link AnimationStateData}. */ @@ -1012,6 +1029,20 @@ public class AnimationState { this.mixDuration = mixDuration; } + /** Controls how properties keyed in the animation are mixed with lower tracks. Defaults to {@link MixBlend#replace}, which + * replaces the values from the lower tracks with the animation values. {@link MixBlend#add} adds the animation values to + * the values from the lower tracks. + *

+ * The mixBlend can be set for a new track entry only before {@link AnimationState#apply(Skeleton)} is first + * called. */ + public MixBlend getMixBlend () { + return mixBlend; + } + + public void setMixBlend (MixBlend mixBlend) { + this.mixBlend = mixBlend; + } + /** The track entry for the previous animation when mixing from the previous animation to this animation, or null if no * mixing is currently occuring. When mixing from multiple animations, mixingFrom makes up a linked list. */ public TrackEntry getMixingFrom () { @@ -1021,10 +1052,10 @@ public class AnimationState { /** Resets the rotation directions for mixing this entry's rotate timelines. This can be useful to avoid bones rotating the * long way around when using {@link #alpha} and starting animations on other tracks. *

- * Mixing involves finding a rotation between two others, which has two possible solutions: the short way or the long way - * around. The two rotations likely change over time, so which direction is the short or long way also changes. If the short - * way was always chosen, bones would flip to the other side when that direction became the long way. TrackEntry chooses the - * short way the first time it is applied and remembers that direction. */ + * Mixing with {@link MixBlend#replace} involves finding a rotation between two others, which has two possible solutions: + * the short way or the long way around. The two rotations likely change over time, so which direction is the short or long + * way also changes. If the short way was always chosen, bones would flip to the other side when that direction became the + * long way. TrackEntry chooses the short way the first time it is applied and remembers that direction. */ public void resetRotationDirections () { timelinesRotation.clear(); } 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 77c9451c3..baf878aeb 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Event.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Event.java @@ -36,7 +36,7 @@ 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, com.esotericsoftware.spine.Animation.MixPose, com.esotericsoftware.spine.Animation.MixDirection)}, + * {@link Timeline#apply(Skeleton, float, float, com.badlogic.gdx.utils.Array, float, com.esotericsoftware.spine.Animation.MixBlend, 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 { 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 d4c2a51cb..82a903a49 100644 --- a/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java +++ b/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java @@ -83,6 +83,7 @@ import com.badlogic.gdx.utils.Align; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.StringBuilder; import com.badlogic.gdx.utils.viewport.ScreenViewport; +import com.esotericsoftware.spine.Animation.MixBlend; import com.esotericsoftware.spine.AnimationState.AnimationStateAdapter; import com.esotericsoftware.spine.AnimationState.TrackEntry; import com.esotericsoftware.spine.utils.TwoColorPolygonBatch; @@ -264,6 +265,7 @@ public class SkeletonViewer extends ApplicationAdapter { } else { entry = state.setAnimation(track, ui.animationList.getSelected(), ui.loopCheckbox.isChecked()); } + entry.setMixBlend(ui.addCheckbox.isChecked() ? MixBlend.add : MixBlend.replace); entry.setAlpha(ui.alphaSlider.getValue()); } @@ -442,6 +444,7 @@ public class SkeletonViewer extends ApplicationAdapter { ButtonGroup trackButtons = new ButtonGroup(); CheckBox loopCheckbox = new CheckBox("Loop", skin); + CheckBox addCheckbox = new CheckBox("Add", skin); Slider alphaSlider = new Slider(0, 1, 0.01f, false, skin); Label alphaLabel = new Label("1.0", skin); @@ -562,6 +565,7 @@ public class SkeletonViewer extends ApplicationAdapter { for (TextButton button : trackButtons.getButtons()) table.add(button); table.add(loopCheckbox); + table.add(addCheckbox); root.add(table).row(); } root.add("Entry alpha:"); @@ -764,6 +768,12 @@ public class SkeletonViewer extends ApplicationAdapter { } }); + addCheckbox.addListener(new ChangeListener() { + public void changed (ChangeEvent event, Actor actor) { + setAnimation(); + } + }); + linearCheckbox.addListener(new ChangeListener() { public void changed (ChangeEvent event, Actor actor) { if (atlas == null) return; @@ -799,7 +809,12 @@ public class SkeletonViewer extends ApplicationAdapter { alphaSlider.setDisabled(track == 0); alphaSlider.setValue(current == null ? 1 : current.alpha); - if (current != null) loopCheckbox.setChecked(current.getLoop()); + addCheckbox.setDisabled(track == 0); + + if (current != null) { + loopCheckbox.setChecked(current.getLoop()); + addCheckbox.setChecked(current.getMixBlend() == MixBlend.add); + } } }; for (TextButton button : trackButtons.getButtons()) @@ -856,6 +871,7 @@ public class SkeletonViewer extends ApplicationAdapter { debugClippingCheckbox.addListener(savePrefsListener); premultipliedCheckbox.addListener(savePrefsListener); loopCheckbox.addListener(savePrefsListener); + addCheckbox.addListener(savePrefsListener); speedSlider.addListener(savePrefsListener); speedResetButton.addListener(savePrefsListener); mixSlider.addListener(savePrefsListener); @@ -920,6 +936,7 @@ public class SkeletonViewer extends ApplicationAdapter { prefs.putBoolean("debugClipping", debugClippingCheckbox.isChecked()); prefs.putBoolean("premultiplied", premultipliedCheckbox.isChecked()); prefs.putBoolean("loop", loopCheckbox.isChecked()); + prefs.putBoolean("add", addCheckbox.isChecked()); prefs.putFloat("speed", speedSlider.getValue()); prefs.putFloat("mix", mixSlider.getValue()); prefs.putFloat("scale", scaleSlider.getValue()); @@ -948,7 +965,8 @@ public class SkeletonViewer extends ApplicationAdapter { debugPointsCheckbox.setChecked(prefs.getBoolean("debugPoints", true)); debugClippingCheckbox.setChecked(prefs.getBoolean("debugClipping", true)); premultipliedCheckbox.setChecked(prefs.getBoolean("premultiplied", true)); - loopCheckbox.setChecked(prefs.getBoolean("loop", false)); + loopCheckbox.setChecked(prefs.getBoolean("loop", true)); + addCheckbox.setChecked(prefs.getBoolean("add", false)); speedSlider.setValue(prefs.getFloat("speed", 0.3f)); mixSlider.setValue(prefs.getFloat("mix", 0.3f)); From 0d4ce16fc377ffe047837fea39ad80a00f677014 Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Fri, 27 Oct 2017 21:02:36 +0200 Subject: [PATCH 04/10] MSAA for Skeleton Viewer. WCGW --- .../src/com/esotericsoftware/spine/SkeletonViewer.java | 1 + 1 file changed, 1 insertion(+) 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 82a903a49..1bd2cfbbf 100644 --- a/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java +++ b/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java @@ -1001,6 +1001,7 @@ public class SkeletonViewer extends ApplicationAdapter { config.height = (int)(600 * uiScale); config.title = "Skeleton Viewer"; config.allowSoftwareMode = true; + config.samples = 2; new LwjglApplication(new SkeletonViewer(), config); } } From 980352a7ce6a7511402dec454a39a4a4b2f36508 Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Sat, 28 Oct 2017 00:08:47 +0200 Subject: [PATCH 05/10] Removed bad fix for #1024 --- .../src/com/esotericsoftware/spine/AnimationState.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) 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 4cc13530b..98966aec1 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java @@ -259,7 +259,7 @@ public class AnimationState { float animationLast = from.animationLast, animationTime = from.getAnimationTime(); int timelineCount = from.animation.timelines.size; Object[] timelines = from.animation.timelines.items; - float alphaDip = from.alpha * to.interruptAlpha, alphaMix = alphaDip * (1 - mix), alpha; + float alphaDip = from.alpha * to.interruptAlpha, alphaMix = alphaDip * (1 - mix); if (blend != MixBlend.first) blend = from.mixBlend; if (blend == MixBlend.add) { @@ -277,6 +277,7 @@ public class AnimationState { for (int i = 0; i < timelineCount; i++) { Timeline timeline = (Timeline)timelines[i]; MixBlend timelineBlend; + float alpha; switch (timelineData[i]) { case SUBSEQUENT: if (!attachments && timeline instanceof AttachmentTimeline) continue; @@ -290,18 +291,12 @@ public class AnimationState { break; case DIP: timelineBlend = MixBlend.setup; -// alpha = mix == 1 ? 0 : alphaDip; alpha = alphaDip; break; default: timelineBlend = MixBlend.setup; - // BOZO! - Bad fix. -// if (mix == 1) -// alpha = 0; -// else { TrackEntry dipMix = (TrackEntry)timelineDipMix[i]; alpha = alphaDip * Math.max(0, 1 - dipMix.mixTime / dipMix.mixDuration); -// } break; } from.totalAlpha += alpha; From 0414803ee1d488f84fc9744b2129a57d76c6afcc Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Sat, 28 Oct 2017 00:18:20 +0200 Subject: [PATCH 06/10] Fix not returning to setup pose when multiple mixing from entries end at same time #1024 --- .../src/com/esotericsoftware/spine/AnimationState.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 98966aec1..4ca5a3556 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java @@ -247,11 +247,13 @@ public class AnimationState { if (from.mixingFrom != null) applyMixingFrom(from, skeleton, blend); float mix; - if (to.mixDuration == 0) // Single frame mix to undo mixingFrom changes. + if (to.mixDuration == 0) { // Single frame mix to undo mixingFrom changes. mix = 1; - else { + blend = MixBlend.setup; + } else { mix = to.mixTime / to.mixDuration; if (mix > 1) mix = 1; + if (blend != MixBlend.first) blend = from.mixBlend; } Array events = mix < from.eventThreshold ? this.events : null; @@ -261,7 +263,6 @@ public class AnimationState { Object[] timelines = from.animation.timelines.items; float alphaDip = from.alpha * to.interruptAlpha, alphaMix = alphaDip * (1 - mix); - if (blend != MixBlend.first) blend = from.mixBlend; if (blend == MixBlend.add) { for (int i = 0; i < timelineCount; i++) ((Timeline)timelines[i]).apply(skeleton, animationLast, animationTime, events, alphaMix, blend, MixDirection.out); From eb44a672404e0d751b0425fb86c34ff9d14bcf79 Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Sat, 28 Oct 2017 15:33:19 +0200 Subject: [PATCH 07/10] Additive for translate and scale timelines --- .../com/esotericsoftware/spine/Animation.java | 85 +++++++++++++------ 1 file changed, 58 insertions(+), 27 deletions(-) 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 ecadc4105..9de2cba70 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -336,8 +336,7 @@ public class Animation { return; case first: float r = bone.data.rotation - bone.rotation; - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - bone.rotation += r * alpha; + bone.rotation += (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha; } return; } @@ -351,7 +350,7 @@ public class Animation { case first: case replace: r += bone.data.rotation - bone.rotation; - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; // Wrap within -180 and 180. + r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; // Fall through. case add: bone.rotation += r * alpha; @@ -366,20 +365,17 @@ public class Animation { float percent = getCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); float r = frames[frame + ROTATION] - prevRotation; - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - r = prevRotation + r * percent; + r = prevRotation + (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * percent; switch (blend) { case setup: - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - bone.rotation = bone.data.rotation + r * alpha; + bone.rotation = bone.data.rotation + (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha; break; case first: case replace: r += bone.data.rotation - bone.rotation; // Fall through. case add: - r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; - bone.rotation += r * alpha; + bone.rotation += (r - (16384 - (int)(16384.499999999996 - r / 360)) * 360) * alpha; } } } @@ -459,12 +455,19 @@ public class Animation { x += (frames[frame + X] - x) * percent; y += (frames[frame + Y] - y) * percent; } - if (blend == setup) { + switch (blend) { + case setup: bone.x = bone.data.x + x * alpha; bone.y = bone.data.y + y * alpha; - } else { + break; + case first: + case replace: bone.x += (bone.data.x + x - bone.x) * alpha; bone.y += (bone.data.y + y - bone.y) * alpha; + break; + case add: + bone.x += x * alpha; + bone.y += y * alpha; } } } @@ -514,27 +517,55 @@ public class Animation { y = (y + (frames[frame + Y] - y) * percent) * bone.data.scaleY; } if (alpha == 1) { - bone.scaleX = x; - bone.scaleY = y; + if (blend == add) { + bone.scaleX += x - bone.data.scaleX; + bone.scaleY += y - bone.data.scaleY; + } else { + bone.scaleX = x; + bone.scaleY = y; + } } else { - float bx, by; - if (blend == setup) { - bx = bone.data.scaleX; - by = bone.data.scaleY; - } else { - bx = bone.scaleX; - by = bone.scaleY; - } // Mixing out uses sign of setup or current pose, else use sign of key. + float bx, by; if (direction == out) { - x = Math.abs(x) * Math.signum(bx); - y = Math.abs(y) * Math.signum(by); + switch (blend) { + case setup: + bx = bone.data.scaleX; + by = bone.data.scaleY; + bone.scaleX = bx + (Math.abs(x) * Math.signum(bx) - bx) * alpha; + bone.scaleY = by + (Math.abs(y) * Math.signum(by) - by) * alpha; + break; + case add: + bx = bone.scaleX; + by = bone.scaleY; + bone.scaleX = bx + (Math.abs(x) * Math.signum(bx) - bone.data.scaleX) * alpha; + bone.scaleY = by + (Math.abs(y) * Math.signum(by) - bone.data.scaleY) * alpha; + break; + default: + bx = bone.scaleX; + by = bone.scaleY; + bone.scaleX = bx + (Math.abs(x) * Math.signum(bx) - bx) * alpha; + bone.scaleY = by + (Math.abs(y) * Math.signum(by) - by) * alpha; + } } else { - bx = Math.abs(bx) * Math.signum(x); - by = Math.abs(by) * Math.signum(y); + switch (blend) { + case setup: + bx = Math.abs(bone.data.scaleX) * Math.signum(x); + by = Math.abs(bone.data.scaleY) * Math.signum(y); + bone.scaleX = bx + (x - bx) * alpha; + bone.scaleY = by + (y - by) * alpha; + break; + case add: + bx = Math.signum(x); + by = Math.signum(y); + bone.scaleX = Math.abs(bone.scaleX) * bx + (x - Math.abs(bone.data.scaleX) * bx) * alpha; + bone.scaleY = Math.abs(bone.scaleY) * by + (y - Math.abs(bone.data.scaleY) * by) * alpha; + break; + default: + bone.scaleX += (x - bone.scaleX * Math.signum(x)) * alpha; + bone.scaleY += (y - bone.scaleY * Math.signum(y)) * alpha; + } } - bone.scaleX = bx + (x - bx) * alpha; - bone.scaleY = by + (y - by) * alpha; } } } From ecedff1ffaf76ca2865020b9baa6767f4d3e532b Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Sat, 28 Oct 2017 18:11:41 +0200 Subject: [PATCH 08/10] Can only use #1024 fix on track 0. --- .../src/com/esotericsoftware/spine/AnimationState.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 4ca5a3556..3506341e0 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java @@ -249,11 +249,11 @@ public class AnimationState { float mix; if (to.mixDuration == 0) { // Single frame mix to undo mixingFrom changes. mix = 1; - blend = MixBlend.setup; + if (blend == MixBlend.first) blend = MixBlend.setup; // Tracks >0 are transparent and can't reset to setup pose. } else { mix = to.mixTime / to.mixDuration; if (mix > 1) mix = 1; - if (blend != MixBlend.first) blend = from.mixBlend; + if (blend != MixBlend.first) blend = from.mixBlend; // Track 0 ignores track mix blend. } Array events = mix < from.eventThreshold ? this.events : null; From 5a4da17e7d7d9a7bb93519e9c3f1982ff1e16687 Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Sat, 28 Oct 2017 18:11:56 +0200 Subject: [PATCH 09/10] Shear timeline additive, clean up. --- .../com/esotericsoftware/spine/Animation.java | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) 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 9de2cba70..ebc145a22 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -535,17 +535,18 @@ public class Animation { bone.scaleX = bx + (Math.abs(x) * Math.signum(bx) - bx) * alpha; bone.scaleY = by + (Math.abs(y) * Math.signum(by) - by) * alpha; break; + case first: + case replace: + bx = bone.scaleX; + by = bone.scaleY; + bone.scaleX = bx + (Math.abs(x) * Math.signum(bx) - bx) * alpha; + bone.scaleY = by + (Math.abs(y) * Math.signum(by) - by) * alpha; + break; case add: bx = bone.scaleX; by = bone.scaleY; bone.scaleX = bx + (Math.abs(x) * Math.signum(bx) - bone.data.scaleX) * alpha; bone.scaleY = by + (Math.abs(y) * Math.signum(by) - bone.data.scaleY) * alpha; - break; - default: - bx = bone.scaleX; - by = bone.scaleY; - bone.scaleX = bx + (Math.abs(x) * Math.signum(bx) - bx) * alpha; - bone.scaleY = by + (Math.abs(y) * Math.signum(by) - by) * alpha; } } else { switch (blend) { @@ -555,15 +556,16 @@ public class Animation { bone.scaleX = bx + (x - bx) * alpha; bone.scaleY = by + (y - by) * alpha; break; + case first: + case replace: + bone.scaleX += (x - bone.scaleX * Math.signum(x)) * alpha; + bone.scaleY += (y - bone.scaleY * Math.signum(y)) * alpha; + break; case add: bx = Math.signum(x); by = Math.signum(y); bone.scaleX = Math.abs(bone.scaleX) * bx + (x - Math.abs(bone.data.scaleX) * bx) * alpha; bone.scaleY = Math.abs(bone.scaleY) * by + (y - Math.abs(bone.data.scaleY) * by) * alpha; - break; - default: - bone.scaleX += (x - bone.scaleX * Math.signum(x)) * alpha; - bone.scaleY += (y - bone.scaleY * Math.signum(y)) * alpha; } } } @@ -614,12 +616,19 @@ public class Animation { x = x + (frames[frame + X] - x) * percent; y = y + (frames[frame + Y] - y) * percent; } - if (blend == setup) { + switch (blend) { + case setup: bone.shearX = bone.data.shearX + x * alpha; bone.shearY = bone.data.shearY + y * alpha; - } else { + break; + case first: + case replace: bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha; bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha; + break; + case add: + bone.shearX += x * alpha; + bone.shearY += y * alpha; } } } From 72d18eefd41b77ff20bc2707c7c4240a8a0e17a4 Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Sat, 28 Oct 2017 20:14:08 +0200 Subject: [PATCH 10/10] Deform timeline additive --- .../com/esotericsoftware/spine/Animation.java | 142 +++++++++++++----- .../spine/AnimationState.java | 2 +- 2 files changed, 106 insertions(+), 38 deletions(-) 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 ebc145a22..43fb2f14b 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java @@ -173,10 +173,14 @@ public class Animation { * first is intended for the first animations applied, not for animations layered on top of those. */ first, /** Transitions from the current value to the timeline value. No change is made before the first key (the current value is - * kept until the first key). */ + * kept until the first key). + *

+ * replace is intended for animations layered on top of others, not for the first animations applied. */ replace, /** Transitions from the current value to the current value plus the timeline value. No change is made before the first key - * (the current value is kept until the first key). */ + * (the current value is kept until the first key). + *

+ * add is intended for animations layered on top of others, not for the first animations applied. */ add } @@ -1016,26 +1020,49 @@ public class Animation { 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 (blend == setup) { - VertexAttachment vertexAttachment = (VertexAttachment)slotAttachment; - if (vertexAttachment.getBones() == null) { - // Unweighted vertex positions, with alpha. - float[] setupVertices = vertexAttachment.getVertices(); - for (int i = 0; i < vertexCount; i++) { - float setup = setupVertices[i]; - vertices[i] = setup + (lastVertices[i] - setup) * alpha; + if (blend == add) { + VertexAttachment vertexAttachment = (VertexAttachment)slotAttachment; + if (vertexAttachment.getBones() == null) { + // Unweighted vertex positions, no alpha. + float[] setupVertices = vertexAttachment.getVertices(); + for (int i = 0; i < vertexCount; i++) + vertices[i] += lastVertices[i] - setupVertices[i]; + } else { + // Weighted deform offsets, no alpha. + for (int i = 0; i < vertexCount; i++) + vertices[i] += lastVertices[i]; } } else { - // Weighted deform offsets, with alpha. - for (int i = 0; i < vertexCount; i++) - vertices[i] = lastVertices[i] * alpha; + // Vertex positions or deform offsets, no alpha. + System.arraycopy(lastVertices, 0, vertices, 0, vertexCount); } } else { - // Vertex positions or deform offsets, with alpha. - for (int i = 0; i < vertexCount; i++) - vertices[i] += (lastVertices[i] - vertices[i]) * alpha; + switch (blend) { + case setup: + VertexAttachment vertexAttachment = (VertexAttachment)slotAttachment; + if (vertexAttachment.getBones() == null) { + // Unweighted vertex positions, with alpha. + float[] setupVertices = vertexAttachment.getVertices(); + for (int i = 0; i < vertexCount; i++) { + float setup = setupVertices[i]; + vertices[i] = setup + (lastVertices[i] - setup) * alpha; + } + } else { + // Weighted deform offsets, with alpha. + for (int i = 0; i < vertexCount; i++) + vertices[i] = lastVertices[i] * alpha; + } + break; + case first: + case replace: + // Vertex positions or deform offsets, with alpha. + for (int i = 0; i < vertexCount; i++) + vertices[i] += (lastVertices[i] - vertices[i]) * alpha; + break; + case add: + for (int i = 0; i < vertexCount; i++) + vertices[i] += lastVertices[i] * alpha; + } } return; } @@ -1048,32 +1075,73 @@ public class Animation { float percent = getCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime)); if (alpha == 1) { - // Vertex positions or deform offsets, no alpha. - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - vertices[i] = prev + (nextVertices[i] - prev) * percent; - } - } else if (blend == setup) { - VertexAttachment vertexAttachment = (VertexAttachment)slotAttachment; - if (vertexAttachment.getBones() == null) { - // Unweighted vertex positions, with alpha. - float[] setupVertices = vertexAttachment.getVertices(); - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i], setup = setupVertices[i]; - vertices[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha; + if (blend == add) { + VertexAttachment vertexAttachment = (VertexAttachment)slotAttachment; + if (vertexAttachment.getBones() == null) { + // Unweighted vertex positions, no alpha. + float[] setupVertices = vertexAttachment.getVertices(); + for (int i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + vertices[i] += prev + (nextVertices[i] - prev) * percent - setupVertices[i]; + } + } else { + // Weighted deform offsets, no alpha. + for (int i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + vertices[i] += prev + (nextVertices[i] - prev) * percent; + } } } else { - // Weighted deform offsets, with alpha. + // Vertex positions or deform offsets, no alpha. for (int i = 0; i < vertexCount; i++) { float prev = prevVertices[i]; - vertices[i] = (prev + (nextVertices[i] - prev) * percent) * alpha; + vertices[i] = prev + (nextVertices[i] - prev) * percent; } } } else { - // Vertex positions or deform offsets, with alpha. - for (int i = 0; i < vertexCount; i++) { - float prev = prevVertices[i]; - vertices[i] += (prev + (nextVertices[i] - prev) * percent - vertices[i]) * alpha; + switch (blend) { + case setup: { + VertexAttachment vertexAttachment = (VertexAttachment)slotAttachment; + if (vertexAttachment.getBones() == null) { + // Unweighted vertex positions, with alpha. + float[] setupVertices = vertexAttachment.getVertices(); + for (int i = 0; i < vertexCount; i++) { + float prev = prevVertices[i], setup = setupVertices[i]; + vertices[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha; + } + } else { + // Weighted deform offsets, with alpha. + for (int i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + vertices[i] = (prev + (nextVertices[i] - prev) * percent) * alpha; + } + } + break; + } + case first: + case replace: + // Vertex positions or deform offsets, with alpha. + for (int i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + vertices[i] += (prev + (nextVertices[i] - prev) * percent - vertices[i]) * alpha; + } + break; + case add: + VertexAttachment vertexAttachment = (VertexAttachment)slotAttachment; + if (vertexAttachment.getBones() == null) { + // Unweighted vertex positions, with alpha. + float[] setupVertices = vertexAttachment.getVertices(); + for (int i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + vertices[i] += (prev + (nextVertices[i] - prev) * percent - setupVertices[i]) * alpha; + } + } else { + // Weighted deform offsets, with alpha. + for (int i = 0; i < vertexCount; i++) { + float prev = prevVertices[i]; + vertices[i] += (prev + (nextVertices[i] - prev) * percent) * alpha; + } + } } } } 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 3506341e0..93e866b1a 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java @@ -224,7 +224,7 @@ public class AnimationState { for (int ii = 0; ii < timelineCount; ii++) { Timeline timeline = (Timeline)timelines[ii]; - MixBlend timelineBlend = timelineData[ii] >= FIRST ? MixBlend.setup : blend; + MixBlend timelineBlend = timelineData[ii] == SUBSEQUENT ? blend : MixBlend.setup; if (timeline instanceof RotateTimeline) { applyRotateTimeline(timeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, ii << 1, firstFrame);