events = mix < from.eventThreshold ? this.events : null;
boolean attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold;
float animationLast = from.animationLast, animationTime = from.getAnimationTime();
int timelineCount = from.animation.timelines.size;
Object[] timelines = from.animation.timelines.items;
- boolean[] timelinesFirst = from.timelinesFirst.items, timelinesLast = multipleMixing ? null : from.timelinesLast.items;
- float alphaBase = from.alpha * entry.mixAlpha, alphaMix = alphaBase * (1 - mix);
+ int[] timelineData = from.timelineData.items;
+ float alphaMix = from.alpha * to.mixAlpha * (1 - mix), alphaDip = from.alpha * to.mixAlpha * (1 - parentMix);
boolean firstFrame = from.timelinesRotation.size == 0;
if (firstFrame) from.timelinesRotation.setSize(timelineCount << 1);
@@ -217,20 +222,21 @@ public class AnimationState {
for (int i = 0; i < timelineCount; i++) {
Timeline timeline = (Timeline)timelines[i];
- boolean setupPose = timelinesFirst[i];
- float alpha = timelinesLast != null && setupPose && !timelinesLast[i] ? alphaBase : alphaMix;
+ int data = timelineData[i];
+ boolean first = data > 0;
+ float alpha = data == DIP ? alphaDip : alphaMix;
if (timeline instanceof RotateTimeline)
- applyRotateTimeline(timeline, skeleton, animationTime, alpha, setupPose, timelinesRotation, i << 1, firstFrame);
+ applyRotateTimeline(timeline, skeleton, animationTime, alpha, first, timelinesRotation, i << 1, firstFrame);
else {
- if (!setupPose) {
+ if (!first) {
if (!attachments && timeline instanceof AttachmentTimeline) continue;
if (!drawOrder && timeline instanceof DrawOrderTimeline) continue;
}
- timeline.apply(skeleton, animationLast, animationTime, events, alpha, setupPose, true);
+ timeline.apply(skeleton, animationLast, animationTime, events, alpha, first, true);
}
}
- if (entry.mixDuration > 0) queueEvents(from, animationTime);
+ if (to.mixDuration > 0) queueEvents(from, animationTime);
this.events.clear();
from.nextAnimationLast = animationTime;
from.nextTrackLast = from.trackTime;
@@ -382,29 +388,7 @@ public class AnimationState {
if (interrupt) queue.interrupt(from);
current.mixingFrom = from;
current.mixTime = 0;
-
- TrackEntry mixingFrom = from.mixingFrom;
- if (mixingFrom != null && from.mixDuration > 0) {
- if (multipleMixing) {
- // The interrupted mix will mix out from its current percentage to zero.
- current.mixAlpha *= Math.min(from.mixTime / from.mixDuration, 1);
- } else {
- // A mix was interrupted, mix from the closest animation.
- if (from.mixTime / from.mixDuration < 0.5f && mixingFrom.animation != emptyAnimation) {
- current.mixingFrom = mixingFrom;
- mixingFrom.mixingFrom = from;
- mixingFrom.mixTime = from.mixDuration - from.mixTime;
- mixingFrom.mixDuration = from.mixDuration;
- from.mixingFrom = null;
- from = mixingFrom;
- }
-
- // End the other animation after it is applied one last time.
- from.mixAlpha = 0;
- from.mixTime = 0;
- from.mixDuration = 0;
- }
- }
+ current.mixAlpha *= Math.min(from.mixTime / from.mixDuration, 1); // Store interrupted mix percentage.
from.timelinesRotation.clear(); // Reset rotation for mixing out, in case entry was mixed in.
}
@@ -583,83 +567,19 @@ public class AnimationState {
private void animationsChanged () {
animationsChanged = false;
+ // Set timelinesData for all entries, from lowest track to highest.
IntSet propertyIDs = this.propertyIDs;
-
- // Set timelinesFirst for all entries, from lowest track to highest.
- int i = 0, n = tracks.size;
propertyIDs.clear();
- for (; i < n; i++) { // Find first non-null entry.
+ TrackEntry lastEntry = null;
+ for (int i = 0, n = tracks.size; i < n; i++) {
TrackEntry entry = tracks.get(i);
- if (entry == null) continue;
- setTimelinesFirst(entry);
- i++;
- break;
- }
- for (; i < n; i++) { // Rest of entries.
- TrackEntry entry = tracks.get(i);
- if (entry != null) checkTimelinesFirst(entry);
- }
-
- if (multipleMixing) return;
-
- // Set timelinesLast for mixingFrom entries, from highest track to lowest that has mixingFrom.
- propertyIDs.clear();
- int lowestMixingFrom = n;
- for (i = 0; i < n; i++) { // Find lowest track with a mixingFrom entry.
- TrackEntry entry = tracks.get(i);
- if (entry == null || entry.mixingFrom == null) continue;
- lowestMixingFrom = i;
- break;
- }
- for (i = n - 1; i >= lowestMixingFrom; i--) { // Find first non-null entry.
- TrackEntry entry = tracks.get(i);
- if (entry == null) continue;
-
- // Store properties for non-mixingFrom entry but don't set timelinesLast, which is only used for mixingFrom entries.
- Object[] timelines = entry.animation.timelines.items;
- for (int ii = 0, nn = entry.animation.timelines.size; ii < nn; ii++)
- propertyIDs.add(((Timeline)timelines[ii]).getPropertyId());
-
- entry = entry.mixingFrom;
- while (entry != null) {
- checkTimelinesUsage(entry, entry.timelinesLast);
- entry = entry.mixingFrom;
+ if (entry != null) {
+ entry.setTimelineData(lastEntry, propertyIDs);
+ lastEntry = entry;
}
}
}
- /** From last to first mixingFrom entries, sets timelinesFirst to true on last, calls checkTimelineUsage on rest. */
- private void setTimelinesFirst (TrackEntry entry) {
- if (entry.mixingFrom != null) {
- setTimelinesFirst(entry.mixingFrom);
- checkTimelinesUsage(entry, entry.timelinesFirst);
- return;
- }
- IntSet propertyIDs = this.propertyIDs;
- int n = entry.animation.timelines.size;
- Object[] timelines = entry.animation.timelines.items;
- boolean[] usage = entry.timelinesFirst.setSize(n);
- for (int i = 0; i < n; i++) {
- propertyIDs.add(((Timeline)timelines[i]).getPropertyId());
- usage[i] = true;
- }
- }
-
- /** From last to first mixingFrom entries, calls checkTimelineUsage. */
- private void checkTimelinesFirst (TrackEntry entry) {
- if (entry.mixingFrom != null) checkTimelinesFirst(entry.mixingFrom);
- checkTimelinesUsage(entry, entry.timelinesFirst);
- }
-
- private void checkTimelinesUsage (TrackEntry entry, BooleanArray usageArray) {
- IntSet propertyIDs = this.propertyIDs;
- int n = entry.animation.timelines.size;
- Object[] timelines = entry.animation.timelines.items;
- boolean[] usage = usageArray.setSize(n);
- for (int i = 0; i < n; i++)
- usage[i] = propertyIDs.add(((Timeline)timelines[i]).getPropertyId());
- }
-
/** Returns the track entry for the animation currently playing on the track, or null if no animation is currently playing. */
public TrackEntry getCurrent (int trackIndex) {
if (trackIndex >= tracks.size) return null;
@@ -701,25 +621,6 @@ public class AnimationState {
this.timeScale = timeScale;
}
- /** When false, only two animations can be mixed at once. Interrupting a mix by setting a new animation will discard one of the
- * two old animations, keeping the one closest to being fully mixed in. Discarding an animation in this way may cause keyed
- * values to jump.
- *
- * When true, any number of animations can be mixed at once without causing keyed values to jump. Mixing is done by mixing out
- * one or more old animations while mixing in the newest one. When animations key the same value, this may cause "dipping",
- * where the value moves toward the setup pose as the old animations mix out, then back to the keyed value as the new animation
- * mixes in.
- *
- * Defaults to false. */
- public boolean getMultipleMixing () {
- return multipleMixing;
- }
-
- public void setMultipleMixing (boolean multipleMixing) {
- this.multipleMixing = multipleMixing;
- animationsChanged = true;
- }
-
/** The AnimationStateData to look up mix durations. */
public AnimationStateData getData () {
return data;
@@ -760,7 +661,7 @@ public class AnimationState {
float animationStart, animationEnd, animationLast, nextAnimationLast;
float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale;
float alpha, mixTime, mixDuration, mixAlpha;
- final BooleanArray timelinesFirst = new BooleanArray(), timelinesLast = new BooleanArray();
+ final IntArray timelineData = new IntArray();
final FloatArray timelinesRotation = new FloatArray();
public void reset () {
@@ -768,11 +669,33 @@ public class AnimationState {
mixingFrom = null;
animation = null;
listener = null;
- timelinesFirst.clear();
- timelinesLast.clear();
+ timelineData.clear();
timelinesRotation.clear();
}
+ TrackEntry setTimelineData (TrackEntry parent, IntSet propertyIDs) {
+ TrackEntry lastEntry = mixingFrom != null ? mixingFrom.setTimelineData(this, propertyIDs) : this;
+ int n = animation.timelines.size;
+ Object[] timelines = animation.timelines.items;
+ int[] timelineData = this.timelineData.setSize(n << 1);
+ for (int i = 0; i < n; i++) {
+ int id = ((Timeline)timelines[i]).getPropertyId();
+ boolean first = propertyIDs.add(id);
+ if (first && parent != null && parent.hasTimeline(id))
+ timelineData[i] = DIP;
+ else
+ timelineData[i] = first ? FIRST : SUBSEQUENT;
+ }
+ return lastEntry;
+ }
+
+ private boolean hasTimeline (int id) {
+ Object[] timelines = animation.timelines.items;
+ for (int i = 0, n = animation.timelines.size; i < n; i++)
+ if (((Timeline)timelines[i]).getPropertyId() == id) return true;
+ return false;
+ }
+
/** The index of the track where this track entry is either current or queued.
*
* See {@link AnimationState#getCurrent(int)}. */
@@ -994,7 +917,7 @@ public class AnimationState {
}
/** The track entry for the previous animation when mixing from the previous animation to this animation, or null if no
- * mixing is currently occuring. If mixing from multiple animations, mixingFrom makes up a linked list. */
+ * mixing is currently occuring. When mixing from multiple animations, mixingFrom makes up a linked list. */
public TrackEntry getMixingFrom () {
return mixingFrom;
}
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 30621c5aa..49e24bca6 100644
--- a/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java
+++ b/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java
@@ -187,7 +187,6 @@ public class SkeletonViewer extends ApplicationAdapter {
skeleton.updateWorldTransform();
state = new AnimationState(new AnimationStateData(skeletonData));
- state.setMultipleMixing(ui.multipleMixingCheckbox.isChecked());
state.addListener(new AnimationStateAdapter() {
public void event (TrackEntry entry, Event event) {
ui.toast(event.getData().getName());
@@ -384,7 +383,6 @@ public class SkeletonViewer extends ApplicationAdapter {
List skinList = new List(skin);
ScrollPane skinScroll = new ScrollPane(skinList, skin, "bg");
CheckBox loopCheckbox = new CheckBox("Loop", skin);
- CheckBox multipleMixingCheckbox = new CheckBox("Multiple mixing", skin);
CheckBox premultipliedCheckbox = new CheckBox("Premultiplied", skin);
Slider mixSlider = new Slider(0, 4, 0.01f, false, skin);
Label mixLabel = new Label("0.3", skin);
@@ -539,8 +537,6 @@ public class SkeletonViewer extends ApplicationAdapter {
table.add(mixSlider).fillX().expandX();
root.add(table).fill().row();
}
- root.add();
- root.add(multipleMixingCheckbox).row();
window.add(root).expand().fill();
window.pack();
@@ -706,12 +702,6 @@ public class SkeletonViewer extends ApplicationAdapter {
}
});
- multipleMixingCheckbox.addListener(new ChangeListener() {
- public void changed (ChangeEvent event, Actor actor) {
- if (state != null) state.setMultipleMixing(multipleMixingCheckbox.isChecked());
- }
- });
-
skinList.addListener(new ChangeListener() {
public void changed (ChangeEvent event, Actor actor) {
if (skeleton != null) {
@@ -792,7 +782,6 @@ public class SkeletonViewer extends ApplicationAdapter {
debugClippingCheckbox.addListener(savePrefsListener);
premultipliedCheckbox.addListener(savePrefsListener);
loopCheckbox.addListener(savePrefsListener);
- multipleMixingCheckbox.addListener(savePrefsListener);
speedSlider.addListener(savePrefsListener);
speedResetButton.addListener(savePrefsListener);
mixSlider.addListener(savePrefsListener);
@@ -857,7 +846,6 @@ public class SkeletonViewer extends ApplicationAdapter {
prefs.putBoolean("debugClipping", debugClippingCheckbox.isChecked());
prefs.putBoolean("premultiplied", premultipliedCheckbox.isChecked());
prefs.putBoolean("loop", loopCheckbox.isChecked());
- prefs.putBoolean("multipleMixing", multipleMixingCheckbox.isChecked());
prefs.putFloat("speed", speedSlider.getValue());
prefs.putFloat("mix", mixSlider.getValue());
prefs.putFloat("scale", scaleSlider.getValue());
@@ -886,7 +874,6 @@ public class SkeletonViewer extends ApplicationAdapter {
debugClippingCheckbox.setChecked(prefs.getBoolean("debugClipping", true));
premultipliedCheckbox.setChecked(prefs.getBoolean("premultiplied", true));
loopCheckbox.setChecked(prefs.getBoolean("loop", false));
- multipleMixingCheckbox.setChecked(prefs.getBoolean("multipleMixing", false));
speedSlider.setValue(prefs.getFloat("speed", 0.3f));
mixSlider.setValue(prefs.getFloat("mix", 0.3f));