Fixed dipping when mixing more than 2 animations that key the same property.

Fixes `a->b,a` where a and b key the same property.
This commit is contained in:
NathanSweet 2017-05-16 14:01:44 +02:00
parent b882fb8c51
commit 02660ddd0a

View File

@ -57,6 +57,7 @@ public class AnimationState {
final Array<AnimationStateListener> listeners = new Array(); final Array<AnimationStateListener> listeners = new Array();
private final EventQueue queue = new EventQueue(); private final EventQueue queue = new EventQueue();
private final IntSet propertyIDs = new IntSet(); private final IntSet propertyIDs = new IntSet();
private final Array<TrackEntry> mixingTo = new Array();
boolean animationsChanged; boolean animationsChanged;
private float timeScale = 1; private float timeScale = 1;
@ -161,7 +162,7 @@ public class AnimationState {
// 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, 0); mix *= applyMixingFrom(current, skeleton);
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.
@ -173,11 +174,12 @@ public class AnimationState {
for (int ii = 0; ii < timelineCount; ii++) for (int ii = 0; ii < timelineCount; ii++)
((Timeline)timelines[ii]).apply(skeleton, animationLast, animationTime, events, 1, true, false); ((Timeline)timelines[ii]).apply(skeleton, animationLast, animationTime, events, 1, true, false);
} else { } else {
int[] timelineData = current.timelineData.items;
boolean firstFrame = current.timelinesRotation.size == 0; boolean firstFrame = current.timelinesRotation.size == 0;
if (firstFrame) current.timelinesRotation.setSize(timelineCount << 1); if (firstFrame) current.timelinesRotation.setSize(timelineCount << 1);
float[] timelinesRotation = current.timelinesRotation.items; float[] timelinesRotation = current.timelinesRotation.items;
int[] timelineData = current.timelineData.items;
for (int ii = 0; ii < timelineCount; ii++) { for (int ii = 0; ii < timelineCount; ii++) {
Timeline timeline = (Timeline)timelines[ii]; Timeline timeline = (Timeline)timelines[ii];
if (timeline instanceof RotateTimeline) { if (timeline instanceof RotateTimeline) {
@ -196,7 +198,10 @@ public class AnimationState {
queue.drain(); queue.drain();
} }
private float applyMixingFrom (TrackEntry to, Skeleton skeleton, float parentMix) { private float applyMixingFrom (TrackEntry to, Skeleton skeleton) {
TrackEntry from = to.mixingFrom;
if (from.mixingFrom != null) applyMixingFrom(from, skeleton);
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.
mix = 1; mix = 1;
@ -205,16 +210,14 @@ public class AnimationState {
if (mix > 1) mix = 1; if (mix > 1) mix = 1;
} }
TrackEntry from = to.mixingFrom;
if (from.mixingFrom != null) applyMixingFrom(from, skeleton, mix);
Array<Event> events = mix < from.eventThreshold ? this.events : null; Array<Event> events = mix < from.eventThreshold ? this.events : null;
boolean attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold; boolean attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold;
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;
int[] timelineData = from.timelineData.items; int[] timelineData = from.timelineData.items;
float alphaMix = from.alpha * to.mixAlpha * (1 - mix), alphaDip = from.alpha * to.mixAlpha * (1 - parentMix); Object[] timelineDipMix = from.timelineDipMix.items;
float alphaDip = from.alpha * to.mixAlpha, alphaMix = alphaDip * (1 - mix);
boolean firstFrame = from.timelinesRotation.size == 0; boolean firstFrame = from.timelinesRotation.size == 0;
if (firstFrame) from.timelinesRotation.setSize(timelineCount << 1); if (firstFrame) from.timelinesRotation.setSize(timelineCount << 1);
@ -223,8 +226,24 @@ public class AnimationState {
for (int i = 0; i < timelineCount; i++) { for (int i = 0; i < timelineCount; i++) {
Timeline timeline = (Timeline)timelines[i]; Timeline timeline = (Timeline)timelines[i];
int data = timelineData[i]; int data = timelineData[i];
boolean first = data > 0; boolean first;
float alpha = data == DIP ? alphaDip : alphaMix; float alpha;
switch (data) {
case SUBSEQUENT:
first = false;
alpha = alphaMix;
break;
case FIRST:
first = true;
alpha = alphaMix;
break;
default:
first = true;
alpha = alphaDip;
TrackEntry dipMix = (TrackEntry)timelineDipMix[i];
if (dipMix != null) alpha *= Math.max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
break;
}
if (timeline instanceof RotateTimeline) if (timeline instanceof RotateTimeline)
applyRotateTimeline(timeline, skeleton, animationTime, alpha, first, timelinesRotation, i << 1, firstFrame); applyRotateTimeline(timeline, skeleton, animationTime, alpha, first, timelinesRotation, i << 1, firstFrame);
else { else {
@ -388,8 +407,7 @@ public class AnimationState {
if (interrupt) queue.interrupt(from); if (interrupt) queue.interrupt(from);
current.mixingFrom = from; current.mixingFrom = from;
current.mixTime = 0; current.mixTime = 0;
current.mixAlpha *= Math.min(from.mixTime / from.mixDuration, 1); // Store interrupted mix percentage. current.mixAlpha *= Math.min(1, from.mixTime / from.mixDuration); // Store interrupted mix percentage.
from.timelinesRotation.clear(); // Reset rotation for mixing out, in case entry was mixed in. from.timelinesRotation.clear(); // Reset rotation for mixing out, in case entry was mixed in.
} }
@ -567,14 +585,15 @@ public class AnimationState {
private void animationsChanged () { private void animationsChanged () {
animationsChanged = false; animationsChanged = false;
// Set timelinesData for all entries, from lowest track to highest.
IntSet propertyIDs = this.propertyIDs; IntSet propertyIDs = this.propertyIDs;
propertyIDs.clear(); propertyIDs.clear();
Array<TrackEntry> mixingTo = this.mixingTo;
TrackEntry lastEntry = null; TrackEntry lastEntry = null;
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) { if (entry != null) {
entry.setTimelineData(lastEntry, propertyIDs); entry.setTimelineData(lastEntry, mixingTo, propertyIDs);
lastEntry = entry; lastEntry = entry;
} }
} }
@ -662,6 +681,7 @@ public class AnimationState {
float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale; float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale;
float alpha, mixTime, mixDuration, mixAlpha; float alpha, mixTime, mixDuration, mixAlpha;
final IntArray timelineData = new IntArray(); final IntArray timelineData = new IntArray();
final Array<TrackEntry> timelineDipMix = new Array();
final FloatArray timelinesRotation = new FloatArray(); final FloatArray timelinesRotation = new FloatArray();
public void reset () { public void reset () {
@ -670,21 +690,41 @@ public class AnimationState {
animation = null; animation = null;
listener = null; listener = null;
timelineData.clear(); timelineData.clear();
timelineDipMix.clear();
timelinesRotation.clear(); timelinesRotation.clear();
} }
TrackEntry setTimelineData (TrackEntry parent, IntSet propertyIDs) { /** @param to May be null. */
TrackEntry lastEntry = mixingFrom != null ? mixingFrom.setTimelineData(this, propertyIDs) : this; TrackEntry setTimelineData (TrackEntry to, Array<TrackEntry> mixingToArray, IntSet propertyIDs) {
int n = animation.timelines.size; if (to != null) mixingToArray.add(to);
TrackEntry lastEntry = mixingFrom != null ? mixingFrom.setTimelineData(this, mixingToArray, propertyIDs) : this;
if (to != null) mixingToArray.pop();
Object[] mixingTo = mixingToArray.items;
int mixingToLast = mixingToArray.size - 1;
Object[] timelines = animation.timelines.items; Object[] timelines = animation.timelines.items;
int[] timelineData = this.timelineData.setSize(n << 1); int timelinesCount = animation.timelines.size;
for (int i = 0; i < n; i++) { int[] timelineData = this.timelineData.setSize(timelinesCount);
Object[] timelineDipMix = this.timelineDipMix.setSize(timelinesCount);
outer:
for (int i = 0; i < timelinesCount; i++) {
int id = ((Timeline)timelines[i]).getPropertyId(); int id = ((Timeline)timelines[i]).getPropertyId();
boolean first = propertyIDs.add(id); if (!propertyIDs.add(id))
if (first && parent != null && parent.hasTimeline(id)) timelineData[i] = SUBSEQUENT;
else if (to == null || !to.hasTimeline(id))
timelineData[i] = FIRST;
else {
timelineData[i] = DIP; timelineData[i] = DIP;
else for (int ii = mixingToLast; ii >= 0; ii--) {
timelineData[i] = first ? FIRST : SUBSEQUENT; TrackEntry entry = (TrackEntry)mixingTo[ii];
if (!entry.hasTimeline(id)) {
timelineDipMix[i] = entry;
continue outer;
}
}
timelineDipMix[i] = null;
}
} }
return lastEntry; return lastEntry;
} }