Added additive mixing, still a WIP.

This commit is contained in:
NathanSweet 2017-10-27 00:23:41 +02:00
parent 1ae9fb955b
commit bda035e836
9 changed files with 236 additions and 170 deletions

View File

@ -32,7 +32,7 @@ package com.esotericsoftware.spine;
import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.files.FileHandle;
import com.esotericsoftware.spine.Animation.MixDirection; 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.AttachmentLoader;
import com.esotericsoftware.spine.attachments.BoundingBoxAttachment; import com.esotericsoftware.spine.attachments.BoundingBoxAttachment;
import com.esotericsoftware.spine.attachments.ClippingAttachment; import com.esotericsoftware.spine.attachments.ClippingAttachment;
@ -76,7 +76,7 @@ public class BonePlotting {
for (Animation animation : skeletonData.getAnimations()) { for (Animation animation : skeletonData.getAnimations()) {
float time = 0; float time = 0;
while (time < animation.getDuration()) { 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(); skeleton.updateWorldTransform();
System.out System.out
.println(animation.getName() + "," + bone.getWorldX() + "," + bone.getWorldY() + "," + bone.getWorldRotationX()); .println(animation.getName() + "," + bone.getWorldX() + "," + bone.getWorldY() + "," + bone.getWorldRotationX());

View File

@ -31,7 +31,7 @@
package com.esotericsoftware.spine; package com.esotericsoftware.spine;
import com.esotericsoftware.spine.Animation.MixDirection; 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.AtlasAttachmentLoader;
import com.esotericsoftware.spine.attachments.RegionAttachment; import com.esotericsoftware.spine.attachments.RegionAttachment;
@ -146,7 +146,7 @@ public class Box2DExample extends ApplicationAdapter {
batch.setTransformMatrix(camera.view); batch.setTransformMatrix(camera.view);
batch.begin(); 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.x += 8 * delta;
skeleton.updateWorldTransform(); skeleton.updateWorldTransform();
skeletonRenderer.draw(batch, skeleton); skeletonRenderer.draw(batch, skeleton);

View File

@ -32,7 +32,7 @@ package com.esotericsoftware.spine;
import com.esotericsoftware.spine.Animation.EventTimeline; import com.esotericsoftware.spine.Animation.EventTimeline;
import com.esotericsoftware.spine.Animation.MixDirection; 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.Array;
import com.badlogic.gdx.utils.StringBuilder; import com.badlogic.gdx.utils.StringBuilder;
@ -177,7 +177,7 @@ public class EventTimelineTests {
int beforeCount = firedEvents.size; int beforeCount = firedEvents.size;
Array<Event> original = new Array(firedEvents); Array<Event> 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) { while (beforeCount < firedEvents.size) {
char fired = firedEvents.get(beforeCount).getData().getName().charAt(0); char fired = firedEvents.get(beforeCount).getData().getName().charAt(0);
@ -186,7 +186,7 @@ public class EventTimelineTests {
} else { } else {
if (firedEvents.size > eventsCount) { if (firedEvents.size > eventsCount) {
if (print) System.out.println(lastTimeLooped + "->" + timeLooped + ": " + fired + " == ?"); 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."); fail("Too many events fired.");
} }
} }
@ -194,7 +194,7 @@ public class EventTimelineTests {
System.out.println(lastTimeLooped + "->" + timeLooped + ": " + fired + " == " + events[eventIndex]); System.out.println(lastTimeLooped + "->" + timeLooped + ": " + fired + " == " + events[eventIndex]);
} }
if (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."); fail("Wrong event fired.");
} }
eventIndex++; eventIndex++;
@ -206,7 +206,7 @@ public class EventTimelineTests {
i++; i++;
} }
if (firedEvents.size < eventsCount) { 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); if (print) System.out.println(firedEvents);
fail("Event not fired: " + events[eventIndex] + ", " + frames[eventIndex]); fail("Event not fired: " + events[eventIndex] + ", " + frames[eventIndex]);
} }

View File

@ -38,7 +38,7 @@ import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Array;
import com.esotericsoftware.spine.Animation.MixDirection; import com.esotericsoftware.spine.Animation.MixDirection;
import com.esotericsoftware.spine.Animation.MixPose; import com.esotericsoftware.spine.Animation.MixBlend;
public class MixTest extends ApplicationAdapter { public class MixTest extends ApplicationAdapter {
SpriteBatch batch; SpriteBatch batch;
@ -105,23 +105,23 @@ public class MixTest extends ApplicationAdapter {
skeleton.setX(-50); skeleton.setX(-50);
} else if (time > beforeJump + jump) { } else if (time > beforeJump + jump) {
// just walk after 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) { } else if (time > blendOutStart) {
// blend out jump // 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, 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) { } else if (time > beforeJump + blendIn) {
// just jump // 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) { } else if (time > beforeJump) {
// blend in jump // 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, jumpAnimation.apply(skeleton, time - beforeJump, time - beforeJump, false, events, (time - beforeJump) / blendIn,
MixPose.current, MixDirection.in); MixBlend.first, MixDirection.in);
} else { } else {
// just walk before jump // 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(); skeleton.updateWorldTransform();

View File

@ -57,7 +57,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Window;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener; import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
import com.badlogic.gdx.utils.Align; import com.badlogic.gdx.utils.Align;
import com.esotericsoftware.spine.Animation.MixDirection; import com.esotericsoftware.spine.Animation.MixDirection;
import com.esotericsoftware.spine.Animation.MixPose; import com.esotericsoftware.spine.Animation.MixBlend;
public class NormalMapTest extends ApplicationAdapter { public class NormalMapTest extends ApplicationAdapter {
String skeletonPath, animationName; String skeletonPath, animationName;
@ -132,7 +132,7 @@ public class NormalMapTest extends ApplicationAdapter {
public void render () { public void render () {
float lastTime = time; float lastTime = time;
time += Gdx.graphics.getDeltaTime(); 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.updateWorldTransform();
skeleton.update(Gdx.graphics.getDeltaTime()); skeleton.update(Gdx.graphics.getDeltaTime());

View File

@ -30,8 +30,8 @@
package com.esotericsoftware.spine; package com.esotericsoftware.spine;
import static com.esotericsoftware.spine.Animation.MixBlend.*;
import static com.esotericsoftware.spine.Animation.MixDirection.*; import static com.esotericsoftware.spine.Animation.MixDirection.*;
import static com.esotericsoftware.spine.Animation.MixPose.*;
import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.MathUtils;
@ -69,9 +69,9 @@ public class Animation {
/** Applies all the animation's timelines to the specified skeleton. /** Applies all the animation's timelines to the specified skeleton.
* <p> * <p>
* See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixPose, MixDirection)}. */ * See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixBlend, MixDirection)}. */
public void apply (Skeleton skeleton, float lastTime, float time, boolean loop, Array<Event> events, float alpha, MixPose pose, public void apply (Skeleton skeleton, float lastTime, float time, boolean loop, Array<Event> events, float alpha,
MixDirection direction) { MixBlend blend, MixDirection direction) {
if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null."); if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
if (loop && duration != 0) { if (loop && duration != 0) {
@ -81,7 +81,7 @@ public class Animation {
Array<Timeline> timelines = this.timelines; Array<Timeline> timelines = this.timelines;
for (int i = 0, n = timelines.size; i < n; i++) 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. */ /** The animation's name, which is unique within the skeleton. */
@ -145,39 +145,45 @@ public class Animation {
* interpolate between the keys. * 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 * @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. * timeline does not fire events.
* @param alpha 0 applies the current or setup pose value (depending on <code>setupPose</code>). 1 applies the timeline * @param alpha 0 applies the current or setup value (depending on <code>blend</code>). 1 applies the timeline value.
* value. Between 0 and 1 applies a value between the current or setup pose and the timeline value. By adjusting * Between 0 and 1 applies a value between the current or setup value and the timeline value. By adjusting
* <code>alpha</code> over time, an animation can be mixed in or out. <code>alpha</code> can also be useful to * <code>alpha</code> over time, an animation can be mixed in or out. <code>alpha</code> can also be useful to
* apply animations on top of each other (layered). * apply animations on top of each other (layered).
* @param pose Controls how mixing is applied when <code>alpha</code> < 1. * @param blend Controls how mixing is applied when <code>alpha</code> < 1.
* @param direction Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions, * @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}. */ * such as {@link DrawOrderTimeline} or {@link AttachmentTimeline}. */
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixPose pose, public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixBlend blend,
MixDirection direction); MixDirection direction);
/** Uniquely encodes both the type of this timeline and the skeleton property that it affects. */ /** Uniquely encodes both the type of this timeline and the skeleton property that it affects. */
public int getPropertyId (); 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.
* <p> * <p>
* 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 MixPose { static public enum MixBlend {
/** The timeline value is mixed with the setup pose (the current pose is not used). */ /** 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, setup,
/** The timeline value is mixed with the current pose. The setup pose is used as the timeline value before the first key, /** Transitions from the current value to the timeline value. Before the first key, transitions from the current value to
* except for timelines which perform instant transitions, such as {@link DrawOrderTimeline} or * the setup value, except for timelines which perform instant transitions, such as {@link DrawOrderTimeline} or
* {@link AttachmentTimeline}. */ * {@link AttachmentTimeline}.
current, * <p>
/** The timeline value is mixed with the current pose. No change is made before the first key (the current pose is kept * <code>first</code> is intended for the first animations applied, not for animations layered on top of those. */
* until the first key). */ first,
currentLayered /** 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 <code>alpha</code> is mixing out over time toward 0 (the setup or current pose) or mixing in /** Indicates whether a timeline's <code>alpha</code> is mixing out over time toward 0 (the setup or current pose value) or
* toward 1 (the timeline's pose). * mixing in toward 1 (the timeline's value).
* <p> * <p>
* 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 { static public enum MixDirection {
in, out in, out
} }
@ -318,17 +324,17 @@ public class Animation {
frames[frameIndex + ROTATION] = degrees; frames[frameIndex + ROTATION] = degrees;
} }
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixPose pose, public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixBlend blend,
MixDirection direction) { MixDirection direction) {
Bone bone = skeleton.bones.get(boneIndex); Bone bone = skeleton.bones.get(boneIndex);
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame. if (time < frames[0]) { // Time is before first frame.
switch (pose) { switch (blend) {
case setup: case setup:
bone.rotation = bone.data.rotation; bone.rotation = bone.data.rotation;
return; return;
case current: case first:
float r = bone.data.rotation - bone.rotation; float r = bone.data.rotation - bone.rotation;
r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360;
bone.rotation += r * alpha; bone.rotation += r * alpha;
@ -337,11 +343,17 @@ public class Animation {
} }
if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame.
if (pose == setup) float r = frames[frames.length + PREV_ROTATION];
bone.rotation = bone.data.rotation + frames[frames.length + PREV_ROTATION] * alpha; switch (blend) {
else { case setup:
float r = bone.data.rotation + frames[frames.length + PREV_ROTATION] - bone.rotation; 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. r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; // Wrap within -180 and 180.
// Fall through.
case add:
bone.rotation += r * alpha; bone.rotation += r * alpha;
} }
return; return;
@ -356,11 +368,16 @@ public class Animation {
float r = frames[frame + ROTATION] - prevRotation; float r = frames[frame + ROTATION] - prevRotation;
r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360;
r = prevRotation + r * percent; r = prevRotation + r * percent;
if (pose == setup) { switch (blend) {
case setup:
r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360;
bone.rotation = bone.data.rotation + r * alpha; bone.rotation = bone.data.rotation + r * alpha;
} else { break;
r = bone.data.rotation + r - bone.rotation; case first:
case replace:
r += bone.data.rotation - bone.rotation;
// Fall through.
case add:
r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360; r -= (16384 - (int)(16384.499999999996 - r / 360)) * 360;
bone.rotation += r * alpha; bone.rotation += r * alpha;
} }
@ -408,18 +425,18 @@ public class Animation {
frames[frameIndex + Y] = y; frames[frameIndex + Y] = y;
} }
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixPose pose, public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixBlend blend,
MixDirection direction) { MixDirection direction) {
Bone bone = skeleton.bones.get(boneIndex); Bone bone = skeleton.bones.get(boneIndex);
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame. if (time < frames[0]) { // Time is before first frame.
switch (pose) { switch (blend) {
case setup: case setup:
bone.x = bone.data.x; bone.x = bone.data.x;
bone.y = bone.data.y; bone.y = bone.data.y;
return; return;
case current: case first:
bone.x += (bone.data.x - bone.x) * alpha; bone.x += (bone.data.x - bone.x) * alpha;
bone.y += (bone.data.y - bone.y) * alpha; bone.y += (bone.data.y - bone.y) * alpha;
} }
@ -442,7 +459,7 @@ public class Animation {
x += (frames[frame + X] - x) * percent; x += (frames[frame + X] - x) * percent;
y += (frames[frame + Y] - y) * percent; y += (frames[frame + Y] - y) * percent;
} }
if (pose == setup) { if (blend == setup) {
bone.x = bone.data.x + x * alpha; bone.x = bone.data.x + x * alpha;
bone.y = bone.data.y + y * alpha; bone.y = bone.data.y + y * alpha;
} else { } else {
@ -462,18 +479,18 @@ public class Animation {
return (TimelineType.scale.ordinal() << 24) + boneIndex; return (TimelineType.scale.ordinal() << 24) + boneIndex;
} }
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixPose pose, public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixBlend blend,
MixDirection direction) { MixDirection direction) {
Bone bone = skeleton.bones.get(boneIndex); Bone bone = skeleton.bones.get(boneIndex);
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame. if (time < frames[0]) { // Time is before first frame.
switch (pose) { switch (blend) {
case setup: case setup:
bone.scaleX = bone.data.scaleX; bone.scaleX = bone.data.scaleX;
bone.scaleY = bone.data.scaleY; bone.scaleY = bone.data.scaleY;
return; return;
case current: case first:
bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha; bone.scaleX += (bone.data.scaleX - bone.scaleX) * alpha;
bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha; bone.scaleY += (bone.data.scaleY - bone.scaleY) * alpha;
} }
@ -501,7 +518,7 @@ public class Animation {
bone.scaleY = y; bone.scaleY = y;
} else { } else {
float bx, by; float bx, by;
if (pose == setup) { if (blend == setup) {
bx = bone.data.scaleX; bx = bone.data.scaleX;
by = bone.data.scaleY; by = bone.data.scaleY;
} else { } else {
@ -532,18 +549,18 @@ public class Animation {
return (TimelineType.shear.ordinal() << 24) + boneIndex; return (TimelineType.shear.ordinal() << 24) + boneIndex;
} }
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixPose pose, public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixBlend blend,
MixDirection direction) { MixDirection direction) {
Bone bone = skeleton.bones.get(boneIndex); Bone bone = skeleton.bones.get(boneIndex);
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame. if (time < frames[0]) { // Time is before first frame.
switch (pose) { switch (blend) {
case setup: case setup:
bone.shearX = bone.data.shearX; bone.shearX = bone.data.shearX;
bone.shearY = bone.data.shearY; bone.shearY = bone.data.shearY;
return; return;
case current: case first:
bone.shearX += (bone.data.shearX - bone.shearX) * alpha; bone.shearX += (bone.data.shearX - bone.shearX) * alpha;
bone.shearY += (bone.data.shearY - bone.shearY) * alpha; bone.shearY += (bone.data.shearY - bone.shearY) * alpha;
} }
@ -566,7 +583,7 @@ public class Animation {
x = x + (frames[frame + X] - x) * percent; x = x + (frames[frame + X] - x) * percent;
y = y + (frames[frame + Y] - y) * percent; y = y + (frames[frame + Y] - y) * percent;
} }
if (pose == setup) { if (blend == setup) {
bone.shearX = bone.data.shearX + x * alpha; bone.shearX = bone.data.shearX + x * alpha;
bone.shearY = bone.data.shearY + y * alpha; bone.shearY = bone.data.shearY + y * alpha;
} else { } else {
@ -619,17 +636,17 @@ public class Animation {
frames[frameIndex + A] = a; frames[frameIndex + A] = a;
} }
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixPose pose, public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixBlend blend,
MixDirection direction) { MixDirection direction) {
Slot slot = skeleton.slots.get(slotIndex); Slot slot = skeleton.slots.get(slotIndex);
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame. if (time < frames[0]) { // Time is before first frame.
switch (pose) { switch (blend) {
case setup: case setup:
slot.color.set(slot.data.color); slot.color.set(slot.data.color);
return; return;
case current: case first:
Color color = slot.color, setup = slot.data.color; 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, color.add((setup.r - color.r) * alpha, (setup.g - color.g) * alpha, (setup.b - color.b) * alpha,
(setup.a - color.a) * alpha); (setup.a - color.a) * alpha);
@ -664,7 +681,7 @@ public class Animation {
slot.color.set(r, g, b, a); slot.color.set(r, g, b, a);
else { else {
Color color = slot.color; 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); 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; frames[frameIndex + B2] = b2;
} }
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixPose pose, public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixBlend blend,
MixDirection direction) { MixDirection direction) {
Slot slot = skeleton.slots.get(slotIndex); Slot slot = skeleton.slots.get(slotIndex);
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame. if (time < frames[0]) { // Time is before first frame.
switch (pose) { switch (blend) {
case setup: case setup:
slot.color.set(slot.data.color); slot.color.set(slot.data.color);
slot.darkColor.set(slot.data.darkColor); slot.darkColor.set(slot.data.darkColor);
return; return;
case current: case first:
Color light = slot.color, dark = slot.darkColor, setupLight = slot.data.color, setupDark = slot.data.darkColor; 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, light.add((setupLight.r - light.r) * alpha, (setupLight.g - light.g) * alpha, (setupLight.b - light.b) * alpha,
(setupLight.a - light.a) * alpha); (setupLight.a - light.a) * alpha);
@ -774,7 +791,7 @@ public class Animation {
slot.darkColor.set(r2, g2, b2, 1); slot.darkColor.set(r2, g2, b2, 1);
} else { } else {
Color light = slot.color, dark = slot.darkColor; Color light = slot.color, dark = slot.darkColor;
if (pose == setup) { if (blend == setup) {
light.set(slot.data.color); light.set(slot.data.color);
dark.set(slot.data.darkColor); dark.set(slot.data.darkColor);
} }
@ -830,11 +847,11 @@ public class Animation {
attachmentNames[frameIndex] = attachmentName; attachmentNames[frameIndex] = attachmentName;
} }
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixPose pose, public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixBlend blend,
MixDirection direction) { MixDirection direction) {
Slot slot = skeleton.slots.get(slotIndex); Slot slot = skeleton.slots.get(slotIndex);
if (direction == out && pose == setup) { if (direction == out && blend == setup) {
String attachmentName = slot.data.attachmentName; String attachmentName = slot.data.attachmentName;
slot.setAttachment(attachmentName == null ? null : skeleton.getAttachment(slotIndex, attachmentName)); slot.setAttachment(attachmentName == null ? null : skeleton.getAttachment(slotIndex, attachmentName));
return; return;
@ -842,7 +859,7 @@ public class Animation {
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame. if (time < frames[0]) { // Time is before first frame.
if (pose == setup) { if (blend == setup) {
String attachmentName = slot.data.attachmentName; String attachmentName = slot.data.attachmentName;
slot.setAttachment(attachmentName == null ? null : skeleton.getAttachment(slotIndex, attachmentName)); slot.setAttachment(attachmentName == null ? null : skeleton.getAttachment(slotIndex, attachmentName));
} }
@ -913,7 +930,7 @@ public class Animation {
frameVertices[frameIndex] = vertices; frameVertices[frameIndex] = vertices;
} }
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixPose pose, public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixBlend blend,
MixDirection direction) { MixDirection direction) {
Slot slot = skeleton.slots.get(slotIndex); Slot slot = skeleton.slots.get(slotIndex);
@ -921,7 +938,7 @@ public class Animation {
if (!(slotAttachment instanceof VertexAttachment) || !((VertexAttachment)slotAttachment).applyDeform(attachment)) return; if (!(slotAttachment instanceof VertexAttachment) || !((VertexAttachment)slotAttachment).applyDeform(attachment)) return;
FloatArray verticesArray = slot.getAttachmentVertices(); FloatArray verticesArray = slot.getAttachmentVertices();
if (verticesArray.size == 0) alpha = 1; if (verticesArray.size == 0) blend = setup;
float[][] frameVertices = this.frameVertices; float[][] frameVertices = this.frameVertices;
int vertexCount = frameVertices[0].length; int vertexCount = frameVertices[0].length;
@ -929,11 +946,11 @@ public class Animation {
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame. if (time < frames[0]) { // Time is before first frame.
VertexAttachment vertexAttachment = (VertexAttachment)slotAttachment; VertexAttachment vertexAttachment = (VertexAttachment)slotAttachment;
switch (pose) { switch (blend) {
case setup: case setup:
verticesArray.clear(); verticesArray.clear();
return; return;
case current: case first:
if (alpha == 1) { if (alpha == 1) {
verticesArray.clear(); verticesArray.clear();
return; return;
@ -961,7 +978,7 @@ public class Animation {
if (alpha == 1) { if (alpha == 1) {
// Vertex positions or deform offsets, no alpha. // Vertex positions or deform offsets, no alpha.
System.arraycopy(lastVertices, 0, vertices, 0, vertexCount); System.arraycopy(lastVertices, 0, vertices, 0, vertexCount);
} else if (pose == setup) { } else if (blend == setup) {
VertexAttachment vertexAttachment = (VertexAttachment)slotAttachment; VertexAttachment vertexAttachment = (VertexAttachment)slotAttachment;
if (vertexAttachment.getBones() == null) { if (vertexAttachment.getBones() == null) {
// Unweighted vertex positions, with alpha. // Unweighted vertex positions, with alpha.
@ -996,7 +1013,7 @@ public class Animation {
float prev = prevVertices[i]; float prev = prevVertices[i];
vertices[i] = prev + (nextVertices[i] - prev) * percent; vertices[i] = prev + (nextVertices[i] - prev) * percent;
} }
} else if (pose == setup) { } else if (blend == setup) {
VertexAttachment vertexAttachment = (VertexAttachment)slotAttachment; VertexAttachment vertexAttachment = (VertexAttachment)slotAttachment;
if (vertexAttachment.getBones() == null) { if (vertexAttachment.getBones() == null) {
// Unweighted vertex positions, with alpha. // Unweighted vertex positions, with alpha.
@ -1058,7 +1075,7 @@ public class Animation {
} }
/** Fires events for frames > <code>lastTime</code> and <= <code>time</code>. */ /** Fires events for frames > <code>lastTime</code> and <= <code>time</code>. */
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> firedEvents, float alpha, MixPose pose, public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> firedEvents, float alpha, MixBlend blend,
MixDirection direction) { MixDirection direction) {
if (firedEvents == null) return; if (firedEvents == null) return;
@ -1066,7 +1083,7 @@ public class Animation {
int frameCount = frames.length; int frameCount = frames.length;
if (lastTime > time) { // Fire events after last time for looped animations. 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; lastTime = -1f;
} else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame. } else if (lastTime >= frames[frameCount - 1]) // Last time is after last frame.
return; return;
@ -1125,19 +1142,19 @@ public class Animation {
drawOrders[frameIndex] = drawOrder; drawOrders[frameIndex] = drawOrder;
} }
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixPose pose, public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixBlend blend,
MixDirection direction) { MixDirection direction) {
Array<Slot> drawOrder = skeleton.drawOrder; Array<Slot> drawOrder = skeleton.drawOrder;
Array<Slot> slots = skeleton.slots; Array<Slot> slots = skeleton.slots;
if (direction == out && pose == setup) { if (direction == out && blend == setup) {
System.arraycopy(slots.items, 0, drawOrder.items, 0, slots.size); System.arraycopy(slots.items, 0, drawOrder.items, 0, slots.size);
return; return;
} }
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame. 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; return;
} }
@ -1198,18 +1215,18 @@ public class Animation {
frames[frameIndex + BEND_DIRECTION] = bendDirection; frames[frameIndex + BEND_DIRECTION] = bendDirection;
} }
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixPose pose, public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixBlend blend,
MixDirection direction) { MixDirection direction) {
IkConstraint constraint = skeleton.ikConstraints.get(ikConstraintIndex); IkConstraint constraint = skeleton.ikConstraints.get(ikConstraintIndex);
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame. if (time < frames[0]) { // Time is before first frame.
switch (pose) { switch (blend) {
case setup: case setup:
constraint.mix = constraint.data.mix; constraint.mix = constraint.data.mix;
constraint.bendDirection = constraint.data.bendDirection; constraint.bendDirection = constraint.data.bendDirection;
return; return;
case current: case first:
constraint.mix += (constraint.data.mix - constraint.mix) * alpha; constraint.mix += (constraint.data.mix - constraint.mix) * alpha;
constraint.bendDirection = constraint.data.bendDirection; constraint.bendDirection = constraint.data.bendDirection;
} }
@ -1217,7 +1234,7 @@ public class Animation {
} }
if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. 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.mix = constraint.data.mix + (frames[frames.length + PREV_MIX] - constraint.data.mix) * alpha;
constraint.bendDirection = direction == out ? constraint.data.bendDirection constraint.bendDirection = direction == out ? constraint.data.bendDirection
: (int)frames[frames.length + PREV_BEND_DIRECTION]; : (int)frames[frames.length + PREV_BEND_DIRECTION];
@ -1234,7 +1251,7 @@ public class Animation {
float frameTime = frames[frame]; float frameTime = frames[frame];
float percent = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); 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.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha;
constraint.bendDirection = direction == out ? constraint.data.bendDirection constraint.bendDirection = direction == out ? constraint.data.bendDirection
: (int)frames[frame + PREV_BEND_DIRECTION]; : (int)frames[frame + PREV_BEND_DIRECTION];
@ -1288,21 +1305,21 @@ public class Animation {
frames[frameIndex + SHEAR] = shearMix; frames[frameIndex + SHEAR] = shearMix;
} }
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixPose pose, public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixBlend blend,
MixDirection direction) { MixDirection direction) {
TransformConstraint constraint = skeleton.transformConstraints.get(transformConstraintIndex); TransformConstraint constraint = skeleton.transformConstraints.get(transformConstraintIndex);
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame. if (time < frames[0]) { // Time is before first frame.
TransformConstraintData data = constraint.data; TransformConstraintData data = constraint.data;
switch (pose) { switch (blend) {
case setup: case setup:
constraint.rotateMix = data.rotateMix; constraint.rotateMix = data.rotateMix;
constraint.translateMix = data.translateMix; constraint.translateMix = data.translateMix;
constraint.scaleMix = data.scaleMix; constraint.scaleMix = data.scaleMix;
constraint.shearMix = data.shearMix; constraint.shearMix = data.shearMix;
return; return;
case current: case first:
constraint.rotateMix += (data.rotateMix - constraint.rotateMix) * alpha; constraint.rotateMix += (data.rotateMix - constraint.rotateMix) * alpha;
constraint.translateMix += (data.translateMix - constraint.translateMix) * alpha; constraint.translateMix += (data.translateMix - constraint.translateMix) * alpha;
constraint.scaleMix += (data.scaleMix - constraint.scaleMix) * alpha; constraint.scaleMix += (data.scaleMix - constraint.scaleMix) * alpha;
@ -1334,7 +1351,7 @@ public class Animation {
scale += (frames[frame + SCALE] - scale) * percent; scale += (frames[frame + SCALE] - scale) * percent;
shear += (frames[frame + SHEAR] - shear) * percent; shear += (frames[frame + SHEAR] - shear) * percent;
} }
if (pose == setup) { if (blend == setup) {
TransformConstraintData data = constraint.data; TransformConstraintData data = constraint.data;
constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha; constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha;
constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha; constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha;
@ -1390,17 +1407,17 @@ public class Animation {
frames[frameIndex + VALUE] = position; frames[frameIndex + VALUE] = position;
} }
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixPose pose, public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixBlend blend,
MixDirection direction) { MixDirection direction) {
PathConstraint constraint = skeleton.pathConstraints.get(pathConstraintIndex); PathConstraint constraint = skeleton.pathConstraints.get(pathConstraintIndex);
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame. if (time < frames[0]) { // Time is before first frame.
switch (pose) { switch (blend) {
case setup: case setup:
constraint.position = constraint.data.position; constraint.position = constraint.data.position;
return; return;
case current: case first:
constraint.position += (constraint.data.position - constraint.position) * alpha; constraint.position += (constraint.data.position - constraint.position) * alpha;
} }
return; return;
@ -1419,7 +1436,7 @@ public class Animation {
position += (frames[frame + VALUE] - position) * percent; position += (frames[frame + VALUE] - position) * percent;
} }
if (pose == setup) if (blend == setup)
constraint.position = constraint.data.position + (position - constraint.data.position) * alpha; constraint.position = constraint.data.position + (position - constraint.data.position) * alpha;
else else
constraint.position += (position - constraint.position) * alpha; constraint.position += (position - constraint.position) * alpha;
@ -1436,17 +1453,17 @@ public class Animation {
return (TimelineType.pathConstraintSpacing.ordinal() << 24) + pathConstraintIndex; return (TimelineType.pathConstraintSpacing.ordinal() << 24) + pathConstraintIndex;
} }
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixPose pose, public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixBlend blend,
MixDirection direction) { MixDirection direction) {
PathConstraint constraint = skeleton.pathConstraints.get(pathConstraintIndex); PathConstraint constraint = skeleton.pathConstraints.get(pathConstraintIndex);
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame. if (time < frames[0]) { // Time is before first frame.
switch (pose) { switch (blend) {
case setup: case setup:
constraint.spacing = constraint.data.spacing; constraint.spacing = constraint.data.spacing;
return; return;
case current: case first:
constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha; constraint.spacing += (constraint.data.spacing - constraint.spacing) * alpha;
} }
return; return;
@ -1466,7 +1483,7 @@ public class Animation {
spacing += (frames[frame + VALUE] - spacing) * percent; spacing += (frames[frame + VALUE] - spacing) * percent;
} }
if (pose == setup) if (blend == setup)
constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha; constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha;
else else
constraint.spacing += (spacing - constraint.spacing) * alpha; constraint.spacing += (spacing - constraint.spacing) * alpha;
@ -1515,18 +1532,18 @@ public class Animation {
frames[frameIndex + TRANSLATE] = translateMix; frames[frameIndex + TRANSLATE] = translateMix;
} }
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixPose pose, public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, MixBlend blend,
MixDirection direction) { MixDirection direction) {
PathConstraint constraint = skeleton.pathConstraints.get(pathConstraintIndex); PathConstraint constraint = skeleton.pathConstraints.get(pathConstraintIndex);
float[] frames = this.frames; float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame. if (time < frames[0]) { // Time is before first frame.
switch (pose) { switch (blend) {
case setup: case setup:
constraint.rotateMix = constraint.data.rotateMix; constraint.rotateMix = constraint.data.rotateMix;
constraint.translateMix = constraint.data.translateMix; constraint.translateMix = constraint.data.translateMix;
return; return;
case current: case first:
constraint.rotateMix += (constraint.data.rotateMix - constraint.rotateMix) * alpha; constraint.rotateMix += (constraint.data.rotateMix - constraint.rotateMix) * alpha;
constraint.translateMix += (constraint.data.translateMix - constraint.translateMix) * alpha; constraint.translateMix += (constraint.data.translateMix - constraint.translateMix) * alpha;
} }
@ -1550,7 +1567,7 @@ public class Animation {
translate += (frames[frame + TRANSLATE] - translate) * percent; translate += (frames[frame + TRANSLATE] - translate) * percent;
} }
if (pose == setup) { if (blend == setup) {
constraint.rotateMix = constraint.data.rotateMix + (rotate - constraint.data.rotateMix) * alpha; constraint.rotateMix = constraint.data.rotateMix + (rotate - constraint.data.rotateMix) * alpha;
constraint.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha; constraint.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha;
} else { } else {

View File

@ -40,8 +40,8 @@ import com.badlogic.gdx.utils.Pool;
import com.badlogic.gdx.utils.Pool.Poolable; import com.badlogic.gdx.utils.Pool.Poolable;
import com.esotericsoftware.spine.Animation.AttachmentTimeline; import com.esotericsoftware.spine.Animation.AttachmentTimeline;
import com.esotericsoftware.spine.Animation.DrawOrderTimeline; import com.esotericsoftware.spine.Animation.DrawOrderTimeline;
import com.esotericsoftware.spine.Animation.MixBlend;
import com.esotericsoftware.spine.Animation.MixDirection; import com.esotericsoftware.spine.Animation.MixDirection;
import com.esotericsoftware.spine.Animation.MixPose;
import com.esotericsoftware.spine.Animation.RotateTimeline; import com.esotericsoftware.spine.Animation.RotateTimeline;
import com.esotericsoftware.spine.Animation.Timeline; import com.esotericsoftware.spine.Animation.Timeline;
@ -197,12 +197,14 @@ public class AnimationState {
TrackEntry current = tracks.get(i); TrackEntry current = tracks.get(i);
if (current == null || current.delay > 0) continue; if (current == null || current.delay > 0) continue;
applied = true; 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. // Apply mixing from entries first.
float mix = current.alpha; float mix = current.alpha;
if (current.mixingFrom != null) if (current.mixingFrom != null)
mix *= applyMixingFrom(current, skeleton, currentPose); mix *= applyMixingFrom(current, skeleton, blend);
else if (current.trackTime >= current.trackEnd && current.next == null) // else if (current.trackTime >= current.trackEnd && current.next == null) //
mix = 0; // Set to setup pose the last time the entry will be applied. 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(); float animationLast = current.animationLast, animationTime = current.getAnimationTime();
int timelineCount = current.animation.timelines.size; int timelineCount = current.animation.timelines.size;
Object[] timelines = current.animation.timelines.items; Object[] timelines = current.animation.timelines.items;
if (mix == 1) { if (mix == 1 || blend == MixBlend.add) {
for (int ii = 0; ii < timelineCount; ii++) 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 { } else {
int[] timelineData = current.timelineData.items; int[] timelineData = current.timelineData.items;
@ -222,11 +224,12 @@ public class AnimationState {
for (int ii = 0; ii < timelineCount; ii++) { for (int ii = 0; ii < timelineCount; ii++) {
Timeline timeline = (Timeline)timelines[ii]; Timeline timeline = (Timeline)timelines[ii];
MixPose pose = timelineData[ii] >= FIRST ? MixPose.setup : currentPose; MixBlend timelineBlend = timelineData[ii] >= FIRST ? MixBlend.setup : blend;
if (timeline instanceof RotateTimeline) if (timeline instanceof RotateTimeline) {
applyRotateTimeline(timeline, skeleton, animationTime, mix, pose, timelinesRotation, ii << 1, firstFrame); applyRotateTimeline(timeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, ii << 1,
else firstFrame);
timeline.apply(skeleton, animationLast, animationTime, events, mix, pose, MixDirection.in); } else
timeline.apply(skeleton, animationLast, animationTime, events, mix, timelineBlend, MixDirection.in);
} }
} }
queueEvents(current, animationTime); queueEvents(current, animationTime);
@ -239,9 +242,9 @@ public class AnimationState {
return applied; return applied;
} }
private float applyMixingFrom (TrackEntry to, Skeleton skeleton, MixPose currentPose) { private float applyMixingFrom (TrackEntry to, Skeleton skeleton, MixBlend blend) {
TrackEntry from = to.mixingFrom; TrackEntry from = to.mixingFrom;
if (from.mixingFrom != null) applyMixingFrom(from, skeleton, currentPose); if (from.mixingFrom != null) applyMixingFrom(from, skeleton, blend);
float mix; float mix;
if (to.mixDuration == 0) // Single frame mix to undo mixingFrom changes. if (to.mixDuration == 0) // Single frame mix to undo mixingFrom changes.
@ -256,6 +259,13 @@ public class AnimationState {
float animationLast = from.animationLast, animationTime = from.getAnimationTime(); float animationLast = from.animationLast, animationTime = from.getAnimationTime();
int timelineCount = from.animation.timelines.size; int timelineCount = from.animation.timelines.size;
Object[] timelines = from.animation.timelines.items; Object[] timelines = from.animation.timelines.items;
float alphaDip = from.alpha * to.interruptAlpha, alphaMix = alphaDip * (1 - mix), alpha;
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; int[] timelineData = from.timelineData.items;
Object[] timelineDipMix = from.timelineDipMix.items; Object[] timelineDipMix = from.timelineDipMix.items;
@ -263,38 +273,44 @@ public class AnimationState {
if (firstFrame) from.timelinesRotation.setSize(timelineCount << 1); if (firstFrame) from.timelinesRotation.setSize(timelineCount << 1);
float[] timelinesRotation = from.timelinesRotation.items; float[] timelinesRotation = from.timelinesRotation.items;
MixPose pose;
float alphaDip = from.alpha * to.interruptAlpha, alphaMix = alphaDip * (1 - mix), alpha;
from.totalAlpha = 0; from.totalAlpha = 0;
for (int i = 0; i < timelineCount; i++) { for (int i = 0; i < timelineCount; i++) {
Timeline timeline = (Timeline)timelines[i]; Timeline timeline = (Timeline)timelines[i];
MixBlend timelineBlend;
switch (timelineData[i]) { switch (timelineData[i]) {
case SUBSEQUENT: case SUBSEQUENT:
if (!attachments && timeline instanceof AttachmentTimeline) continue; if (!attachments && timeline instanceof AttachmentTimeline) continue;
if (!drawOrder && timeline instanceof DrawOrderTimeline) continue; if (!drawOrder && timeline instanceof DrawOrderTimeline) continue;
pose = currentPose; timelineBlend = blend;
alpha = alphaMix; alpha = alphaMix;
break; break;
case FIRST: case FIRST:
pose = MixPose.setup; timelineBlend = MixBlend.setup;
alpha = alphaMix; alpha = alphaMix;
break; break;
case DIP: case DIP:
pose = MixPose.setup; timelineBlend = MixBlend.setup;
// alpha = mix == 1 ? 0 : alphaDip;
alpha = alphaDip; alpha = alphaDip;
break; break;
default: default:
pose = MixPose.setup; timelineBlend = MixBlend.setup;
alpha = alphaDip; // BOZO! - Bad fix.
// if (mix == 1)
// alpha = 0;
// else {
TrackEntry dipMix = (TrackEntry)timelineDipMix[i]; TrackEntry dipMix = (TrackEntry)timelineDipMix[i];
alpha *= Math.max(0, 1 - dipMix.mixTime / dipMix.mixDuration); alpha = alphaDip * Math.max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
// }
break; break;
} }
from.totalAlpha += alpha; from.totalAlpha += alpha;
if (timeline instanceof RotateTimeline) if (timeline instanceof RotateTimeline) {
applyRotateTimeline(timeline, skeleton, animationTime, alpha, pose, timelinesRotation, i << 1, firstFrame); applyRotateTimeline(timeline, skeleton, animationTime, alpha, timelineBlend, timelinesRotation, i << 1,
else firstFrame);
timeline.apply(skeleton, animationLast, animationTime, events, alpha, pose, MixDirection.out); } else
timeline.apply(skeleton, animationLast, animationTime, events, alpha, timelineBlend, MixDirection.out);
}
} }
if (to.mixDuration > 0) queueEvents(from, animationTime); if (to.mixDuration > 0) queueEvents(from, animationTime);
@ -305,13 +321,13 @@ public class AnimationState {
return mix; 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) { float[] timelinesRotation, int i, boolean firstFrame) {
if (firstFrame) timelinesRotation[i] = 0; if (firstFrame) timelinesRotation[i] = 0;
if (alpha == 1) { if (alpha == 1) {
timeline.apply(skeleton, 0, time, null, 1, pose, MixDirection.in); timeline.apply(skeleton, 0, time, null, 1, blend, MixDirection.in);
return; return;
} }
@ -319,7 +335,7 @@ public class AnimationState {
Bone bone = skeleton.bones.get(rotateTimeline.boneIndex); Bone bone = skeleton.bones.get(rotateTimeline.boneIndex);
float[] frames = rotateTimeline.frames; float[] frames = rotateTimeline.frames;
if (time < frames[0]) { // Time is before first frame. 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; return;
} }
@ -341,7 +357,7 @@ public class AnimationState {
} }
// Mix between rotations using the direction of the shortest route on the first frame. // 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; float total, diff = r2 - r1;
if (diff == 0) if (diff == 0)
total = timelinesRotation[i]; total = timelinesRotation[i];
@ -646,7 +662,7 @@ public class AnimationState {
for (int i = 0, n = tracks.size; i < n; i++) { for (int i = 0, n = tracks.size; i < n; i++) {
TrackEntry entry = tracks.get(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 animationStart, animationEnd, animationLast, nextAnimationLast;
float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale; float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale;
float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha; float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha;
MixBlend mixBlend = MixBlend.replace;
final IntArray timelineData = new IntArray(); final IntArray timelineData = new IntArray();
final Array<TrackEntry> timelineDipMix = new Array(); final Array<TrackEntry> timelineDipMix = new Array();
final FloatArray timelinesRotation = new FloatArray(); 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). * {@link AnimationStateData#getMix(Animation, Animation)} based on the animation before this animation (if any).
* <p> * <p>
* The <code>mixDuration</code> can be set manually rather than use the value from * The <code>mixDuration</code> can be set manually rather than use the value from
* {@link AnimationStateData#getMix(Animation, Animation)}. In that case, the <code>mixDuration</code> must be set for a new * {@link AnimationStateData#getMix(Animation, Animation)}. In that case, the <code>mixDuration</code> can be set for a new
* track entry before {@link AnimationState#update(float)} is next called. * track entry only before {@link AnimationState#update(float)} is first called.
* <p> * <p>
* When using {@link AnimationState#addAnimation(int, Animation, boolean, float)} with a <code>delay</code> <= 0, note the * When using {@link AnimationState#addAnimation(int, Animation, boolean, float)} with a <code>delay</code> <= 0, note the
* {@link #getDelay()} is set using the mix duration from the {@link AnimationStateData}. */ * {@link #getDelay()} is set using the mix duration from the {@link AnimationStateData}. */
@ -1012,6 +1029,20 @@ public class AnimationState {
this.mixDuration = mixDuration; 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.
* <p>
* The <code>mixBlend</code> 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 /** 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, <code>mixingFrom</code> makes up a linked list. */ * mixing is currently occuring. When mixing from multiple animations, <code>mixingFrom</code> makes up a linked list. */
public TrackEntry getMixingFrom () { 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 /** 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. * long way around when using {@link #alpha} and starting animations on other tracks.
* <p> * <p>
* Mixing involves finding a rotation between two others, which has two possible solutions: the short way or the long way * Mixing with {@link MixBlend#replace} involves finding a rotation between two others, which has two possible solutions:
* around. The two rotations likely change over time, so which direction is the short or long way also changes. If the short * the short way or the long way around. The two rotations likely change over time, so which direction is the short or long
* way was always chosen, bones would flip to the other side when that direction became the long way. TrackEntry chooses the * way also changes. If the short way was always chosen, bones would flip to the other side when that direction became the
* short way the first time it is applied and remembers that direction. */ * long way. TrackEntry chooses the short way the first time it is applied and remembers that direction. */
public void resetRotationDirections () { public void resetRotationDirections () {
timelinesRotation.clear(); timelinesRotation.clear();
} }

View File

@ -36,7 +36,7 @@ import com.esotericsoftware.spine.AnimationState.AnimationStateListener;
/** Stores the current pose values for an {@link Event}. /** Stores the current pose values for an {@link Event}.
* <p> * <p>
* See Timeline * 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 * AnimationStateListener {@link AnimationStateListener#event(com.esotericsoftware.spine.AnimationState.TrackEntry, Event)}, and
* <a href="http://esotericsoftware.com/spine-events">Events</a> in the Spine User Guide. */ * <a href="http://esotericsoftware.com/spine-events">Events</a> in the Spine User Guide. */
public class Event { public class Event {

View File

@ -83,6 +83,7 @@ import com.badlogic.gdx.utils.Align;
import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.StringBuilder; import com.badlogic.gdx.utils.StringBuilder;
import com.badlogic.gdx.utils.viewport.ScreenViewport; import com.badlogic.gdx.utils.viewport.ScreenViewport;
import com.esotericsoftware.spine.Animation.MixBlend;
import com.esotericsoftware.spine.AnimationState.AnimationStateAdapter; import com.esotericsoftware.spine.AnimationState.AnimationStateAdapter;
import com.esotericsoftware.spine.AnimationState.TrackEntry; import com.esotericsoftware.spine.AnimationState.TrackEntry;
import com.esotericsoftware.spine.utils.TwoColorPolygonBatch; import com.esotericsoftware.spine.utils.TwoColorPolygonBatch;
@ -264,6 +265,7 @@ public class SkeletonViewer extends ApplicationAdapter {
} else { } else {
entry = state.setAnimation(track, ui.animationList.getSelected(), ui.loopCheckbox.isChecked()); entry = state.setAnimation(track, ui.animationList.getSelected(), ui.loopCheckbox.isChecked());
} }
entry.setMixBlend(ui.addCheckbox.isChecked() ? MixBlend.add : MixBlend.replace);
entry.setAlpha(ui.alphaSlider.getValue()); entry.setAlpha(ui.alphaSlider.getValue());
} }
@ -442,6 +444,7 @@ public class SkeletonViewer extends ApplicationAdapter {
ButtonGroup<TextButton> trackButtons = new ButtonGroup(); ButtonGroup<TextButton> trackButtons = new ButtonGroup();
CheckBox loopCheckbox = new CheckBox("Loop", skin); CheckBox loopCheckbox = new CheckBox("Loop", skin);
CheckBox addCheckbox = new CheckBox("Add", skin);
Slider alphaSlider = new Slider(0, 1, 0.01f, false, skin); Slider alphaSlider = new Slider(0, 1, 0.01f, false, skin);
Label alphaLabel = new Label("1.0", skin); Label alphaLabel = new Label("1.0", skin);
@ -562,6 +565,7 @@ public class SkeletonViewer extends ApplicationAdapter {
for (TextButton button : trackButtons.getButtons()) for (TextButton button : trackButtons.getButtons())
table.add(button); table.add(button);
table.add(loopCheckbox); table.add(loopCheckbox);
table.add(addCheckbox);
root.add(table).row(); root.add(table).row();
} }
root.add("Entry alpha:"); 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() { linearCheckbox.addListener(new ChangeListener() {
public void changed (ChangeEvent event, Actor actor) { public void changed (ChangeEvent event, Actor actor) {
if (atlas == null) return; if (atlas == null) return;
@ -799,7 +809,12 @@ public class SkeletonViewer extends ApplicationAdapter {
alphaSlider.setDisabled(track == 0); alphaSlider.setDisabled(track == 0);
alphaSlider.setValue(current == null ? 1 : current.alpha); 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()) for (TextButton button : trackButtons.getButtons())
@ -856,6 +871,7 @@ public class SkeletonViewer extends ApplicationAdapter {
debugClippingCheckbox.addListener(savePrefsListener); debugClippingCheckbox.addListener(savePrefsListener);
premultipliedCheckbox.addListener(savePrefsListener); premultipliedCheckbox.addListener(savePrefsListener);
loopCheckbox.addListener(savePrefsListener); loopCheckbox.addListener(savePrefsListener);
addCheckbox.addListener(savePrefsListener);
speedSlider.addListener(savePrefsListener); speedSlider.addListener(savePrefsListener);
speedResetButton.addListener(savePrefsListener); speedResetButton.addListener(savePrefsListener);
mixSlider.addListener(savePrefsListener); mixSlider.addListener(savePrefsListener);
@ -920,6 +936,7 @@ public class SkeletonViewer extends ApplicationAdapter {
prefs.putBoolean("debugClipping", debugClippingCheckbox.isChecked()); prefs.putBoolean("debugClipping", debugClippingCheckbox.isChecked());
prefs.putBoolean("premultiplied", premultipliedCheckbox.isChecked()); prefs.putBoolean("premultiplied", premultipliedCheckbox.isChecked());
prefs.putBoolean("loop", loopCheckbox.isChecked()); prefs.putBoolean("loop", loopCheckbox.isChecked());
prefs.putBoolean("add", addCheckbox.isChecked());
prefs.putFloat("speed", speedSlider.getValue()); prefs.putFloat("speed", speedSlider.getValue());
prefs.putFloat("mix", mixSlider.getValue()); prefs.putFloat("mix", mixSlider.getValue());
prefs.putFloat("scale", scaleSlider.getValue()); prefs.putFloat("scale", scaleSlider.getValue());
@ -948,7 +965,8 @@ public class SkeletonViewer extends ApplicationAdapter {
debugPointsCheckbox.setChecked(prefs.getBoolean("debugPoints", true)); debugPointsCheckbox.setChecked(prefs.getBoolean("debugPoints", true));
debugClippingCheckbox.setChecked(prefs.getBoolean("debugClipping", true)); debugClippingCheckbox.setChecked(prefs.getBoolean("debugClipping", true));
premultipliedCheckbox.setChecked(prefs.getBoolean("premultiplied", 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)); speedSlider.setValue(prefs.getFloat("speed", 0.3f));
mixSlider.setValue(prefs.getFloat("mix", 0.3f)); mixSlider.setValue(prefs.getFloat("mix", 0.3f));