Moved track changes to update, so AnimationState can be applied to multiple skeletons without side effects.

Also:
- Moved queue drains outside of update loop.
- Better naming.
- Dirty flag after animations have been changed to avoid computing timelinesFirst more times than needed.
- Don't use timelinesFirst when mix == 1.
This commit is contained in:
NathanSweet 2016-08-22 21:33:04 +02:00
parent ef2a440655
commit 4c2d2b0c0a
3 changed files with 126 additions and 98 deletions

View File

@ -167,9 +167,11 @@ public class AnimationStateTest {
expect(1, "start", 0.1f, 1.1f), //
expect(0, "interrupt", 1.1f, 1.1f), //
expect(0, "end", 1.1f, 1.1f), //
expect(1, "event 0", 0.1f, 1.1f), //
expect(0, "end", 1.1f, 1.2f), //
expect(1, "event 14", 0.5f, 1.5f), //
expect(1, "event 30", 1, 2), //
expect(1, "complete", 1, 2), //
@ -177,9 +179,11 @@ public class AnimationStateTest {
expect(0, "start", 0.1f, 2.1f), //
expect(1, "interrupt", 1.1f, 2.1f), //
expect(1, "end", 1.1f, 2.1f), //
expect(0, "event 0", 0.1f, 2.1f), //
expect(1, "end", 1.1f, 2.2f), //
expect(0, "event 14", 0.5f, 2.5f), //
expect(0, "event 30", 1, 3), //
expect(0, "complete", 1, 3), //
@ -198,9 +202,11 @@ public class AnimationStateTest {
expect(1, "start", 0.1f, 0.6f), //
expect(0, "interrupt", 0.6f, 0.6f), //
expect(0, "end", 0.6f, 0.6f), //
expect(1, "event 0", 0.1f, 0.6f), //
expect(0, "end", 0.6f, 0.7f), //
expect(1, "event 14", 0.5f, 1.0f), //
expect(1, "event 30", 1, 1.5f), //
expect(1, "complete", 1, 1.5f), //
@ -223,7 +229,7 @@ public class AnimationStateTest {
expect(1, "event 0", 0.1f, 1), //
expect(1, "event 14", 0.5f, 1.4f), //
expect(0, "end", 1.6f, 1.6f), //
expect(0, "end", 1.6f, 1.7f), //
expect(1, "event 30", 1, 1.9f), //
expect(1, "complete", 1, 1.9f), //
@ -246,7 +252,7 @@ public class AnimationStateTest {
expect(1, "event 14", 0.5f, 0.9f), //
expect(0, "complete", 1, 1), //
expect(0, "end", 1.1f, 1.1f), //
expect(0, "end", 1.1f, 1.2f), //
expect(1, "event 30", 1, 1.4f), //
expect(1, "complete", 1, 1.4f), //
@ -270,7 +276,7 @@ public class AnimationStateTest {
expect(1, "event 14", 0.5f, 0.9f), //
expect(0, "complete", 1, 1), //
expect(0, "end", 1.1f, 1.1f), //
expect(0, "end", 1.1f, 1.2f), //
expect(1, "event 30", 1, 1.4f), //
expect(1, "complete", 1, 1.4f), //
@ -298,7 +304,7 @@ public class AnimationStateTest {
expect(1, "event 14", 0.5f, 1.3f), //
expect(0, "end", 1.5f, 1.5f), //
expect(0, "end", 1.5f, 1.6f), //
expect(1, "event 30", 1, 1.8f), //
expect(1, "complete", 1, 1.8f), //
@ -341,9 +347,11 @@ public class AnimationStateTest {
expect(1, "start", 0.1f, 2.1f), //
expect(0, "interrupt", 2.1f, 2.1f), //
expect(0, "end", 2.1f, 2.1f), //
expect(1, "event 0", 0.1f, 2.1f), //
expect(0, "end", 2.1f, 2.2f), //
expect(1, "event 14", 0.5f, 2.5f), //
expect(1, "event 30", 1, 3), //
expect(1, "complete", 1, 3), //
@ -368,9 +376,11 @@ public class AnimationStateTest {
expect(1, "start", 0.1f, 2.1f), //
expect(0, "interrupt", 2.1f, 2.1f), //
expect(0, "end", 2.1f, 2.1f), //
expect(1, "event 0", 0.1f, 2.1f), //
expect(0, "end", 2.1f, 2.2f), //
expect(1, "event 14", 0.5f, 2.5f), //
expect(1, "event 30", 1, 3), //
expect(1, "complete", 1, 3), //
@ -461,7 +471,7 @@ public class AnimationStateTest {
expect(1, "event 14", 0.5f, 1.2f), //
expect(0, "end", 1.4f, 1.4f), //
expect(0, "end", 1.4f, 1.5f), //
expect(1, "event 30", 1, 1.7f), //
expect(1, "complete", 1, 1.7f), //
@ -488,7 +498,7 @@ public class AnimationStateTest {
expect(1, "event 0", 0.1f, 1), //
expect(1, "event 14", 0.5f, 1.4f), //
expect(0, "end", 1.6f, 1.6f), //
expect(0, "end", 1.6f, 1.7f), //
expect(1, "event 30", 1, 1.9f), //
expect(1, "complete", 1, 1.9f), //
@ -507,17 +517,21 @@ public class AnimationStateTest {
expect(1, "start", 0, 0), //
expect(0, "interrupt", 0, 0), //
expect(0, "end", 0, 0), //
expect(1, "event 0", 0, 0), //
expect(0, "end", 0.1f, 0.2f), //
expect(1, "event 14", 0.5f, 0.5f), //
expect(0, "start", 0.1f, 0.8f), //
expect(1, "interrupt", 0.8f, 0.8f), //
expect(1, "end", 0.8f, 0.8f), //
expect(0, "event 0", 0.1f, 0.8f), //
expect(1, "end", 0.8f, 0.9f), //
expect(0, "event 14", 0.5f, 1.2f), //
expect(0, "event 30", 1, 1.7f), //
expect(0, "complete", 1, 1.7f), //

View File

@ -144,7 +144,7 @@ public class Animation {
public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha, boolean setupPose,
boolean mixingOut);
public int getId ();
public int getPropertyId ();
}
static private enum TimelineType {
@ -255,7 +255,7 @@ public class Animation {
frames = new float[frameCount << 1];
}
public int getId () {
public int getPropertyId () {
return (TimelineType.rotate.ordinal() << 24) + boneIndex;
}
@ -330,7 +330,7 @@ public class Animation {
frames = new float[frameCount * ENTRIES];
}
public int getId () {
public int getPropertyId () {
return (TimelineType.translate.ordinal() << 24) + boneIndex;
}
@ -397,7 +397,7 @@ public class Animation {
super(frameCount);
}
public int getId () {
public int getPropertyId () {
return (TimelineType.scale.ordinal() << 24) + boneIndex;
}
@ -455,7 +455,7 @@ public class Animation {
super(frameCount);
}
public int getId () {
public int getPropertyId () {
return (TimelineType.shear.ordinal() << 24) + boneIndex;
}
@ -508,7 +508,7 @@ public class Animation {
frames = new float[frameCount * ENTRIES];
}
public int getId () {
public int getPropertyId () {
return (TimelineType.color.ordinal() << 24) + slotIndex;
}
@ -584,7 +584,7 @@ public class Animation {
attachmentNames = new String[frameCount];
}
public int getId () {
public int getPropertyId () {
return (TimelineType.attachment.ordinal() << 24) + slotIndex;
}
@ -651,7 +651,7 @@ public class Animation {
frameVertices = new float[frameCount][];
}
public int getId () {
public int getPropertyId () {
return (TimelineType.deform.ordinal() << 24) + slotIndex;
}
@ -742,7 +742,7 @@ public class Animation {
events = new Event[frameCount];
}
public int getId () {
public int getPropertyId () {
return TimelineType.event.ordinal() << 24;
}
@ -803,7 +803,7 @@ public class Animation {
drawOrders = new int[frameCount][];
}
public int getId () {
public int getPropertyId () {
return TimelineType.drawOrder.ordinal() << 24;
}
@ -862,7 +862,7 @@ public class Animation {
frames = new float[frameCount * ENTRIES];
}
public int getId () {
public int getPropertyId () {
return (TimelineType.ikConstraint.ordinal() << 24) + ikConstraintIndex;
}
@ -924,7 +924,7 @@ public class Animation {
frames = new float[frameCount * ENTRIES];
}
public int getId () {
public int getPropertyId () {
return (TimelineType.transformConstraint.ordinal() << 24) + transformConstraintIndex;
}
@ -998,7 +998,7 @@ public class Animation {
frames = new float[frameCount * ENTRIES];
}
public int getId () {
public int getPropertyId () {
return (TimelineType.pathConstraintPosition.ordinal() << 24) + pathConstraintIndex;
}
@ -1050,7 +1050,7 @@ public class Animation {
super(frameCount);
}
public int getId () {
public int getPropertyId () {
return (TimelineType.pathConstraintSpacing.ordinal() << 24) + pathConstraintIndex;
}
@ -1091,7 +1091,7 @@ public class Animation {
frames = new float[frameCount * ENTRIES];
}
public int getId () {
public int getPropertyId () {
return (TimelineType.pathConstraintMix.ordinal() << 24) + pathConstraintIndex;
}

View File

@ -53,7 +53,8 @@ public class AnimationState {
}
};
private final EventQueue queue = new EventQueue(listeners, trackEntryPool);
private final IntSet usage = new IntSet();
private final IntSet propertyIDs = new IntSet();
private boolean animationsChanged;
private float timeScale = 1;
/** Creates an uninitialized AnimationState. The animation state data must be set before use. */
@ -68,7 +69,7 @@ public class AnimationState {
/** Increments the track entry times, setting queued animations as current if needed. */
public void update (float delta) {
delta *= timeScale;
for (int i = 0; i < tracks.size; i++) {
for (int i = 0, n = tracks.size; i < n; i++) {
TrackEntry current = tracks.get(i);
if (current == null) continue;
@ -81,7 +82,7 @@ public class AnimationState {
current.delay = 0;
}
TrackEntry next = current.next;
TrackEntry next = current.next, mixingFrom = current.mixingFrom;
if (next != null) {
// When the next entry's delay is passed, change to the next entry.
float nextTime = current.trackLast - next.delay;
@ -95,23 +96,40 @@ public class AnimationState {
}
} else if (current.trackLast >= current.trackEnd) {
// Clear the track when the end time is reached and there is no next entry.
clearTrack(i);
// BOZO - This leaves the skeleton in the last pose, with no easy way of resetting.
// Should we get rid of the track end time?
// Or default it to MAX_VALUE even for non-looping animations?
// Or reset the skeleton before clearing? Note only apply() has a skeleton.
freeAll(current.next);
queue.end(current);
if (mixingFrom != null) queue.end(mixingFrom);
tracks.set(i, null);
continue;
}
current.trackTime += currentDelta;
if (current.mixingFrom != null) {
float mixingFromDelta = delta * current.mixingFrom.timeScale;
current.mixingFrom.trackTime += mixingFromDelta;
current.mixTime += mixingFromDelta;
if (mixingFrom != null) {
if (current.mixTime >= current.mixDuration && current.mixTime > 0) {
queue.end(mixingFrom);
current.mixingFrom = null;
animationsChanged = true;
} else {
float mixingFromDelta = delta * mixingFrom.timeScale;
mixingFrom.trackTime += mixingFromDelta;
current.mixTime += mixingFromDelta;
}
}
}
queue.drain();
}
/** Poses the skeleton using the track entry animations. */
public void apply (Skeleton skeleton) {
if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
if (animationsChanged) animationsChanged();
Array<Event> events = this.events;
for (int i = 0; i < tracks.size; i++) {
@ -128,18 +146,18 @@ public class AnimationState {
if (mix > 1) mix = 1;
}
applyMixingFrom(current.mixingFrom, skeleton, mix);
if (mix == 1) {
queue.end(current.mixingFrom);
current.mixingFrom = null;
updateSetupPose();
}
}
float animationLast = current.animationLast, animationTime = current.getAnimationTime();
Array<Timeline> timelines = current.animation.timelines;
BooleanArray setupPose = current.setupPose;
for (int ii = 0, n = timelines.size; ii < n; ii++)
timelines.get(ii).apply(skeleton, animationLast, animationTime, events, mix, setupPose.get(ii), false);
if (mix == 1) {
for (int ii = 0, n = timelines.size; ii < n; ii++)
timelines.get(ii).apply(skeleton, animationLast, animationTime, events, 1, false, false);
} else {
boolean[] timelinesFirst = current.timelinesFirst.items;
for (int ii = 0, n = timelines.size; ii < n; ii++)
timelines.get(ii).apply(skeleton, animationLast, animationTime, events, mix, timelinesFirst[ii], false);
}
queueEvents(current, animationTime);
current.animationLast = animationTime;
current.trackLast = current.trackTime;
@ -154,12 +172,12 @@ public class AnimationState {
float animationLast = entry.animationLast, animationTime = entry.getAnimationTime();
Array<Timeline> timelines = entry.animation.timelines;
BooleanArray setupPose = entry.setupPose;
boolean[] timelinesFirst = entry.timelinesFirst.items;
float alphaFull = entry.alpha, alphaMix = entry.alpha * (1 - mix);
if (attachments && drawOrder) {
for (int i = 0, n = timelines.size; i < n; i++) {
Timeline timeline = timelines.get(i);
if (setupPose.get(i))
if (timelinesFirst[i])
timeline.apply(skeleton, animationLast, animationTime, events, alphaMix, true, true);
else
timeline.apply(skeleton, animationLast, animationTime, events, alphaFull, false, false);
@ -167,12 +185,13 @@ public class AnimationState {
} else {
for (int i = 0, n = timelines.size; i < n; i++) {
Timeline timeline = timelines.get(i);
if (!attachments && timeline instanceof AttachmentTimeline) continue;
if (!drawOrder && timeline instanceof DrawOrderTimeline) continue;
if (setupPose.get(i))
if (timelinesFirst[i])
timeline.apply(skeleton, animationLast, animationTime, events, alphaMix, true, true);
else
else {
if (!attachments && timeline instanceof AttachmentTimeline) continue;
if (!drawOrder && timeline instanceof DrawOrderTimeline) continue;
timeline.apply(skeleton, animationLast, animationTime, events, alphaFull, false, false);
}
}
}
@ -217,6 +236,7 @@ public class AnimationState {
tracks.clear();
}
// BOZO - This leaves the skeleton in the last pose, with no easy way of resetting.
public void clearTrack (int trackIndex) {
if (trackIndex >= tracks.size) return;
TrackEntry current = tracks.get(trackIndex);
@ -268,60 +288,50 @@ public class AnimationState {
if (mixingFrom != null) queue.end(mixingFrom);
}
queue.drain();
updateSetupPose();
animationsChanged = true;
}
private void updateSetupPose () {
usage.clear();
private void animationsChanged () {
animationsChanged = false;
propertyIDs.clear();
int i = 0, n = tracks.size;
for (; i < n; i++) {
TrackEntry entry = tracks.get(i);
if (entry == null) continue;
if (entry.mixingFrom != null) {
updateFirstSetupPose(entry.mixingFrom);
updateSetupPose(entry);
setTimelinesFirst(entry.mixingFrom);
checkTimelinesFirst(entry);
} else
updateFirstSetupPose(entry);
setTimelinesFirst(entry);
i++;
break;
}
for (; i < n; i++) {
TrackEntry entry = tracks.get(i);
if (entry == null) continue;
if (entry.mixingFrom != null) updateSetupPose(entry.mixingFrom);
updateSetupPose(entry);
if (entry.mixingFrom != null) checkTimelinesFirst(entry.mixingFrom);
checkTimelinesFirst(entry);
}
}
private void updateFirstSetupPose (TrackEntry entry) {
IntSet usage = this.usage;
BooleanArray setupPose = entry.setupPose;
setupPose.clear();
private void setTimelinesFirst (TrackEntry entry) {
IntSet propertyIDs = this.propertyIDs;
Array<Timeline> timelines = entry.animation.timelines;
for (int ii = 0, nn = timelines.size; ii < nn; ii++) {
Timeline timeline = timelines.get(ii);
usage.add(timeline.getId());
setupPose.add(true);
int n = timelines.size;
boolean[] timelinesFirst = entry.timelinesFirst.setSize(n);
for (int i = 0; i < n; i++) {
propertyIDs.add(timelines.get(i).getPropertyId());
timelinesFirst[i] = true;
}
}
private void updateSetupPose (TrackEntry entry) {
IntSet usage = this.usage;
BooleanArray setupPose = entry.setupPose;
setupPose.clear();
private void checkTimelinesFirst (TrackEntry entry) {
IntSet propertyIDs = this.propertyIDs;
Array<Timeline> timelines = entry.animation.timelines;
for (int ii = 0, nn = timelines.size; ii < nn; ii++) {
Timeline timeline = timelines.get(ii);
int id = timeline.getId();
if (usage.contains(id))
setupPose.add(false);
else {
usage.add(id);
setupPose.add(true);
}
}
int n = timelines.size;
boolean[] timelinesFirst = entry.timelinesFirst.setSize(n);
for (int i = 0; i < n; i++)
timelinesFirst[i] = propertyIDs.add(timelines.get(i).getPropertyId());
}
/** @see #setAnimation(int, Animation, boolean) */
@ -340,13 +350,15 @@ public class AnimationState {
if (animation == null) throw new IllegalArgumentException("animation cannot be null.");
TrackEntry current = expandToIndex(trackIndex);
TrackEntry entry = trackEntry(trackIndex, animation, loop, current);
if (current == null)
if (current == null) {
setCurrent(trackIndex, entry);
else {
queue.drain();
} else {
freeAll(current.next);
if (current.trackLast == -1) // If current was never applied, replace it.
if (current.trackLast == -1) { // If current was never applied, replace it.
setCurrent(trackIndex, entry);
else {
queue.drain();
} else {
current.next = entry;
entry.delay = current.trackLast;
}
@ -377,9 +389,10 @@ public class AnimationState {
TrackEntry entry = trackEntry(trackIndex, animation, loop, last);
if (last == null)
if (last == null) {
setCurrent(trackIndex, entry);
else {
queue.drain();
} else {
last.next = entry;
if (delay <= 0) {
float duration = last.animationEnd - last.animationStart;
@ -402,7 +415,7 @@ public class AnimationState {
entry.loop = loop;
entry.eventThreshold = 0;
entry.attachmentThreshold = 1;
entry.attachmentThreshold = 0;
entry.drawOrderThreshold = 0;
entry.delay = 0;
@ -496,14 +509,14 @@ public class AnimationState {
float delay, trackTime, trackLast, trackEnd, animationStart, animationEnd, animationLast, timeScale;
float alpha;
float mixTime, mixDuration;
final BooleanArray setupPose = new BooleanArray();
final BooleanArray timelinesFirst = new BooleanArray();
public void reset () {
next = null;
mixingFrom = null;
animation = null;
listener = null;
setupPose.clear();
timelinesFirst.clear();
}
public int getTrackIndex () {
@ -538,7 +551,8 @@ public class AnimationState {
}
/** Current time in seconds this track entry has been the current track entry. The track time determines
* {@link #getAnimationTime()} and can be set to start the animation at a time other than 0. */
* {@link #getAnimationTime()}. The track time can be set to start the animation at a time other than 0, without affecting
* looping. */
public float getTrackTime () {
return trackTime;
}
@ -548,8 +562,8 @@ public class AnimationState {
}
/** The track time in seconds when this animation will be removed from the track. If the track end time is reached and no
* other animations are queued for playback, the track is cleared. Defaults to the animation duration for non-looping
* animations and to {@link Integer#MAX_VALUE} for looping animations. */
* other animations are queued for playback, the track is cleared, leaving the skeleton in the last applied pose. Defaults
* to the animation duration for non-looping animations and to {@link Integer#MAX_VALUE} for looping animations. */
public float getTrackEnd () {
return trackEnd;
}
@ -560,8 +574,8 @@ public class AnimationState {
/** Seconds when this animation starts, both initially and after looping. Defaults to 0.
* <p>
* When changing the animation start time, it often makes sense to also change {@link #getAnimationLast()} to control when
* timelines will trigger. */
* When changing the animation start time, it often makes sense to also change {@link #getAnimationLast()} to control which
* timeline keys will trigger. */
public float getAnimationStart () {
return animationStart;
}
@ -581,8 +595,8 @@ public class AnimationState {
}
/** The time in seconds this animation was last applied. Some timelines use this for one-time triggers. Eg, when this
* animation is applied, event timelines will fire all events between lastTime (exclusive) and time (inclusive). Defaults to
* -1 to ensure triggers on frame 0 happen the first time this animation is applied. */
* animation is applied, event timelines will fire all events between the animation last time (exclusive) and animation time
* (inclusive). Defaults to -1 to ensure triggers on frame 0 happen the first time this animation is applied. */
public float getAnimationLast () {
return animationLast;
}