From bda035e8362022d642057cb6cdcb1e8a5215ba9d Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Fri, 27 Oct 2017 00:23:41 +0200 Subject: [PATCH] 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));