Browse Source

[c] More AnimationState porting. Close, but no cookie yet...

badlogic 9 years ago
parent
commit
aa3a5080b6

+ 2 - 0
spine-c/include/spine/Animation.h

@@ -174,6 +174,8 @@ typedef struct spBaseTimeline {
 
 /**/
 
+static const int ROTATE_PREV_TIME = -2, ROTATE_PREV_ROTATION = -1;
+static const int ROTATE_ROTATION = 1;
 static const int ROTATE_ENTRIES = 2;
 
 typedef struct spBaseTimeline spRotateTimeline;

+ 3 - 1
spine-c/include/spine/AnimationState.h

@@ -96,8 +96,8 @@ struct spAnimationState {
 #ifdef __cplusplus
 	spAnimationState() :
 		data(0),
-		tracks(0),
 		tracksCount(0),
+		tracks(0),
 		listener(0),
 		timeScale(0) {
 	}
@@ -133,6 +133,8 @@ spTrackEntry* spAnimationState_getCurrent (spAnimationState* self, int trackInde
 
 spTrackEntry* spAnimationState_clearListenerNotifications(spAnimationState* self);
 
+float spTrackEntry_getAnimationTime (spTrackEntry* entry);
+
 #ifdef SPINE_SHORT_NAMES
 typedef spEventType EventType;
 #define ANIMATION_START SP_ANIMATION_START

+ 8 - 1
spine-c/include/spine/extension.h

@@ -177,8 +177,10 @@ typedef union _spEventQueueItem {
 	spEvent* event;
 } _spEventQueueItem;
 
+typedef struct _spAnimationState _spAnimationState;
+
 typedef struct _spEventQueue {
-	spAnimationState* state;
+	_spAnimationState* state;
 	_spEventQueueItem* objects;
 	int objectsCount;
 	int objectsCapacity;
@@ -189,6 +191,7 @@ typedef struct _spEventQueue {
 		state(0),
 		objects(0),
 		objectsCount(0),
+		objectsCapacity(0),
 		drainDisabled(0) {
 	}
 #endif
@@ -198,6 +201,7 @@ typedef struct _spAnimationState {
 	spAnimationState super;
 
 	int eventsCount;
+	int eventsCapacity;
 	spEvent** events;
 
 	_spEventQueue* queue;
@@ -210,6 +214,7 @@ typedef struct _spAnimationState {
 	_spAnimationState() :
 		super(),
 		eventsCount(0),
+		eventsCapacity(0),
 		events(0),
 		queue(0),
 		propertyIDs(0),
@@ -275,10 +280,12 @@ void _spCurveTimeline_init (spCurveTimeline* self, spTimelineType type, int fram
 	void (*apply) (const spTimeline* self, spSkeleton* skeleton, float lastTime, float time, spEvent** firedEvents, int* eventsCount, float alpha, int setupPose, int mixingOut),
 	int (*getPropertyId) (const spTimeline* self));
 void _spCurveTimeline_deinit (spCurveTimeline* self);
+int _spCurveTimeline_binarySearch (float *values, int valuesLength, float target, int step);
 
 #ifdef SPINE_SHORT_NAMES
 #define _CurveTimeline_init(...) _spCurveTimeline_init(__VA_ARGS__)
 #define _CurveTimeline_deinit(...) _spCurveTimeline_deinit(__VA_ARGS__)
+#define _CurveTimeline_binarySearch(...) _spCurveTimeline_binarySearch(__VA_ARGS__)
 #endif
 
 #ifdef __cplusplus

+ 4 - 3
spine-c/src/spine/Animation.c

@@ -192,6 +192,10 @@ static int binarySearch (float *values, int valuesLength, float target, int step
 	return 0;
 }
 
+int _spCurveTimeline_binarySearch (float *values, int valuesLength, float target, int step) {
+	return binarySearch(values, valuesLength, target, step);
+}
+
 /* @param target After the first and before the last entry. */
 static int binarySearch1 (float *values, int valuesLength, float target) {
 	int low = 0, current;
@@ -234,9 +238,6 @@ struct spBaseTimeline* _spBaseTimeline_create (int framesCount, spTimelineType t
 
 /**/
 
-static const int ROTATE_PREV_TIME = -2, ROTATE_PREV_ROTATION = -1;
-static const int ROTATE_ROTATION = 1;
-
 void _spRotateTimeline_apply (const spTimeline* timeline, spSkeleton* skeleton, float lastTime, float time, spEvent** firedEvents,
 		int* eventsCount, float alpha, int setupPose, int mixingOut) {
 	spBone *bone;

+ 602 - 20
spine-c/src/spine/AnimationState.c

@@ -32,27 +32,32 @@
 #include <spine/extension.h>
 #include <string.h>
 
-_spEventQueue* _spEventQueue_create (spAnimationState* state) {
-	_spEventQueue *self = MALLOC(_spEventQueue, 1);
+static spAnimation* SP_EMPTY_ANIMATION = 0;
+
+// Forward declaration of some "private" functions so we can keep
+// the same function order in C as we have method order in Java
+void _spAnimationState_disposeTrackEntry (spTrackEntry* entry);
+
+_spEventQueue* _spEventQueue_create (_spAnimationState* state) {
+	_spEventQueue *self = CALLOC(_spEventQueue, 1);
 	self->state = state;
 	self->objectsCount = 0;
 	self->objectsCapacity = 16;
-	self->objects = MALLOC(_spEventQueueItem, self->objectsCapacity * sizeof(_spEventQueueItem));
+	self->objects = CALLOC(_spEventQueueItem, self->objectsCapacity);
 	self->drainDisabled = 0;
 	return self;
 }
 
 void _spEventQueue_free (_spEventQueue* self) {
-	if (!self) return;
-	if (self->objects) FREE(self->objects);
+	FREE(self->objects);
 }
 
 void _spEventQueue_ensureCapacity (_spEventQueue* self, int newElements) {
 	if (self->objectsCount + newElements > self->objectsCapacity) {
 		_spEventQueueItem* newObjects;
 		self->objectsCapacity <<= 1;
-		newObjects = MALLOC(_spEventQueueItem, self->objectsCapacity);
-		memcpy(newObjects, self->objects, self->objectsCount * sizeof(_spEventQueueItem));
+		newObjects = CALLOC(_spEventQueueItem, self->objectsCapacity);
+		memcpy(newObjects, self->objects, self->objectsCount);
 		FREE(self->objects);
 		self->objects = newObjects;
 	}
@@ -74,10 +79,9 @@ void _spEventQueue_addEvent (_spEventQueue* self, spEvent* event) {
 }
 
 void _spEventQueue_start (_spEventQueue* self, spTrackEntry* entry) {
-	_spAnimationState* internalState = (_spAnimationState*)self->state;
 	_spEventQueue_addType(self, SP_ANIMATION_START);
 	_spEventQueue_addEntry(self, entry);
-	internalState->animationsChanged = 1;
+	self->state->animationsChanged = 1;
 }
 
 void _spEventQueue_interrupt (_spEventQueue* self, spTrackEntry* entry) {
@@ -86,10 +90,9 @@ void _spEventQueue_interrupt (_spEventQueue* self, spTrackEntry* entry) {
 }
 
 void _spEventQueue_end (_spEventQueue* self, spTrackEntry* entry) {
-	_spAnimationState* internalState = (_spAnimationState*)self->state;
 	_spEventQueue_addType(self, SP_ANIMATION_END);
 	_spEventQueue_addEntry(self, entry);
-	internalState->animationsChanged = 1;
+	self->state->animationsChanged = 1;
 }
 
 void _spEventQueue_dispose (_spEventQueue* self, spTrackEntry* entry) {
@@ -119,17 +122,596 @@ void _spEventQueue_drain (_spEventQueue* self) {
 	for (i = 0; i < self->objectsCount; i += 2) {
 		spEventType type = self->objects[i].type;
 		spTrackEntry* entry = self->objects[i+1].entry;
-		if (type != SP_ANIMATION_EVENT) {
-			if (entry->listener) entry->listener(self->state, type, entry, 0);
-			if (self->state->listener) self->state->listener(self->state, type, entry, 0);
-		} else {
-			spEvent* event = self->objects[i+2].event;
-			if (entry->listener) entry->listener(self->state, type, entry, event);
-			if (self->state->listener) self->state->listener(self->state, type, entry, event);
-			i++;
+		spEvent* event;
+		switch (type) {
+			case SP_ANIMATION_START:
+			case SP_ANIMATION_INTERRUPT:
+			case SP_ANIMATION_COMPLETE:
+				if (entry->listener) entry->listener(self->state, type, entry, 0);
+				if (self->state->super.listener) self->state->super.listener(self->state, type, entry, 0);
+				break;
+			case SP_ANIMATION_END:
+				if (entry->listener) entry->listener(self->state, type, entry, 0);
+				if (self->state->super.listener) self->state->super.listener(self->state, type, entry, 0);
+				// Fall through.
+			case SP_ANIMATION_DISPOSE:
+				if (entry->listener) entry->listener(self->state, type, entry, 0);
+				if (self->state->super.listener) self->state->super.listener(self->state, type, entry, 0);
+				_spAnimationState_disposeTrackEntry(entry);
+				break;
+			case SP_ANIMATION_EVENT:
+				event = self->objects[i+2].event;
+				if (entry->listener) entry->listener(self->state, type, entry, event);
+				if (self->state->super.listener) self->state->super.listener(self->state, type, entry, event);
+				i++;
+				break;
 		}
 	}
-
 	_spEventQueue_clear(self);
+
 	self->drainDisabled = 0;
 }
+
+void _spAnimationState_disposeTrackEntry (spTrackEntry* entry) {
+	FREE(entry);
+}
+
+void _spAnimationState_disposeTrackEntries (spAnimationState* state, spTrackEntry* entry) {
+	while (entry) {
+		spTrackEntry* next = entry->next;
+		_spAnimationState_disposeTrackEntry(entry);
+		entry = next;
+	}
+}
+
+spAnimationState* spAnimationState_create (spAnimationStateData* data) {
+	if (!SP_EMPTY_ANIMATION) {
+		SP_EMPTY_ANIMATION = 1; /* dirty trick so we can recursively call spAnimation_create */
+		SP_EMPTY_ANIMATION = spAnimation_create("<empty>", 0);
+	}
+
+	_spAnimationState* internal = NEW(_spAnimationState);
+	spAnimationState* self = SUPER(internal);
+
+	CONST_CAST(spAnimationStateData*, self->data) = data;
+	self->timeScale = 1;
+
+	internal->queue = _spEventQueue_create(self);
+	internal->events = CALLOC(spEvent*, 64);
+	internal->eventsCapacity = 64;
+	// FIXME propertyIDs
+	return self;
+}
+
+void spAnimationState_dispose (spAnimationState* self) {
+	int i;
+	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+	for (i = 0; i < self->tracksCount; i++)
+		_spAnimationState_disposeTrackEntries(self, self->tracks[i]);
+	FREE(self->tracks);
+	_spEventQueue_free(internal->queue);
+	FREE(internal->events);
+	// FIXME propertyIDs
+}
+
+void spAnimationState_update (spAnimationState* self, float delta) {
+	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+	delta *= self->timeScale;
+	for (int i = 0, n = self->tracksCount; i < n; i++) {
+		spTrackEntry* current = self->tracks[i];
+		if (!current) continue;
+
+		current->animationLast = current->nextAnimationLast;
+		current->trackLast = current->nextTrackLast;
+
+		float currentDelta = delta * current->timeScale;
+
+		if (current->delay > 0) {
+			current->delay -= currentDelta;
+			if (current->delay > 0) continue;
+			currentDelta = -current->delay;
+			current->delay = 0;
+		}
+
+		spTrackEntry* next = current->next;
+		if (next) {
+			// When the next entry's delay is passed, change to the next entry, preserving leftover time.
+			float nextTime = current->trackLast - next->delay;
+			if (nextTime >= 0) {
+				next->delay = 0;
+				next->trackTime = nextTime + delta * next->timeScale;
+				current->trackTime += currentDelta;
+				_spAnimationState_setCurrent(self, i, next);
+				while (next->mixingFrom) {
+					next->mixTime += currentDelta;
+					next = next->mixingFrom;
+				}
+				continue;
+			}
+			_spAnimationState_updateMixingFrom(self, current, delta, 1);
+		} else {
+			_spAnimationState_updateMixingFrom(self, current, delta, 1);
+			// Clear the track when there is no next entry, the track end time is reached, and there is no mixingFrom.
+			if (current->trackLast >= current->trackEnd && current->mixingFrom == 0) {
+				self->tracks[i] = 0;
+				_spEventQueue_end(internal->queue, current);
+				_spAnimationState_disposeNext(self, current);
+				continue;
+			}
+		}
+
+		current->trackTime += currentDelta;
+	}
+
+	_spEventQueue_drain(internal->queue);
+}
+
+void _spAnimationState_updateMixingFrom (spAnimationState* self, spTrackEntry* entry, float delta, int /*boolean*/ canEnd) {
+	spTrackEntry* from = entry->mixingFrom;
+	spTrackEntry* newFrom;
+	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+	if (!from) return;
+
+	if (canEnd && entry->mixTime >= entry->mixDuration && entry->mixTime > 0) {
+		_spEventQueue_end(internal->queue, from);
+		newFrom = from->mixingFrom;
+		entry->mixingFrom = newFrom;
+		if (!newFrom) return;
+		entry->mixTime = from->mixTime;
+		entry->mixDuration = from->mixDuration;
+		from = newFrom;
+	}
+
+	from->animationLast = from->nextAnimationLast;
+	from->trackLast = from->nextTrackLast;
+	float mixingFromDelta = delta * from->timeScale;
+	from->trackTime += mixingFromDelta;
+	entry->mixTime += mixingFromDelta;
+
+	_spAnimationState_updateMixingFrom(self, from, delta, canEnd && from->alpha == 1);
+}
+
+void spAnimationState_apply (spAnimationState* self, spSkeleton* skeleton) {
+	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+	spTrackEntry* current;
+	int i, ii, n;
+	float animationLast, animationTime;
+	int timelineCount;
+	spTimeline** timelines;
+	int /*boolean*/ firstFrame;
+	float* timelinesRotation;
+	int* timelinesFirst;
+	spTimeline* timeline;
+
+	if (internal->animationsChanged) _spAnimationState_animationsChanged(self);
+
+	for (i = 0, n = self->tracksCount; i < n; i++) {
+		current = self->tracks[i];
+		if (!current || current->delay > 0) continue;
+
+		// Apply mixing from entries first.
+		float mix = current->alpha;
+		if (current->mixingFrom) mix *= _spAnimationState_applyMixingFrom(self, current, skeleton);
+
+		// Apply current entry.
+		animationLast = current->animationLast; animationTime = spTrackEntry_getAnimationTime(current);
+		timelineCount = current->animation->timelinesCount;
+		timelines = current->animation->timelines;
+		if (mix == 1) {
+			for (ii = 0; ii < timelineCount; ii++)
+				spTimeline_apply(timelines[ii], skeleton, animationLast, animationTime, internal->events, internal->eventsCount, 1, 1, 0);
+		} else {
+			firstFrame = current->timelinesRotationCount == 0;
+			if (firstFrame) current->timelinesRotation.setSize(timelineCount << 1);
+			timelinesRotation = current->timelinesRotation;
+
+			timelinesFirst = current->timelinesFirst;
+			for (int ii = 0; ii < timelineCount; ii++) {
+				timeline = timelines[ii];
+				if (timeline->type == SP_TIMELINE_ROTATE)
+					_spAnimationState_applyRotateTimeline(self, timeline, skeleton, animationTime, mix, timelinesFirst[ii], timelinesRotation, ii << 1, firstFrame);
+				else
+					spTimeline_apply(timeline, skeleton, animationLast, animationTime, internal->events, internal->eventsCount, mix, timelinesFirst[ii], 0);
+			}
+		}
+		_spAnimationState_queueEvents(self, current, animationTime);
+		current->nextAnimationLast = animationTime;
+		current->nextTrackLast = current->trackTime;
+	}
+
+	_spEventQueue_drain(internal->queue);
+}
+
+float _spAnimationState_applyMixingFrom (spAnimationState* self, spTrackEntry* entry, spSkeleton* skeleton) {
+	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+	float mix;
+	spEvent** events;
+	int /*boolean*/ attachments;
+	int /*boolean*/ drawOrder;
+	float animationLast;
+	float animationTime;
+	int timelineCount;
+	spTimeline** timelines;
+	int* timelinesFirst;
+	float alpha;
+	int /*boolean*/ firstFrame;
+	float* timelinesRotation;
+	spTimeline* timeline;
+	int /*boolean*/ setupPose;
+
+	spTrackEntry* from = entry->mixingFrom;
+	if (from->mixingFrom) _spAnimationState_applyMixingFrom(self, from, skeleton);
+
+	mix;
+	if (entry->mixDuration == 0) // Single frame mix to undo mixingFrom changes.
+		mix = 1;
+	else {
+		mix = entry->mixTime / entry->mixDuration;
+		if (mix > 1) mix = 1;
+	}
+
+	events = mix < from->eventThreshold ? internal->events : 0;
+	attachments = mix < from->attachmentThreshold;
+	drawOrder = mix < from->drawOrderThreshold;
+	animationLast = from->animationLast;
+	animationTime = spTrackEntry_getAnimationTime(from);
+	timelineCount = from->animation->timelinesCount;
+	timelines = from->animation->timelines;
+	timelinesFirst = from->timelinesFirst;
+	alpha = from->alpha * entry->mixAlpha * (1 - mix);
+
+	firstFrame = from->timelinesRotationCount == 0;
+	if (firstFrame) from->timelinesRotation.setSize(timelineCount << 1);
+	timelinesRotation = from->timelinesRotation;
+
+	for (int i = 0; i < timelineCount; i++) {
+		timeline = timelines[i];
+		setupPose = timelinesFirst[i];
+		if (timeline->type == SP_TIMELINE_ROTATE)
+			_spAnimationState_applyRotateTimeline(self, timeline, skeleton, animationTime, alpha, setupPose, timelinesRotation, i << 1, firstFrame);
+		else {
+			if (!setupPose) {
+				if (!attachments && timeline->type == SP_TIMELINE_ATTACHMENT) continue;
+				if (!drawOrder && timeline->type == SP_TIMELINE_DRAWORDER) continue;
+			}
+			spTimeline_apply(timeline, skeleton, animationLast, animationTime, events, internal->eventsCount, alpha, setupPose, 1);
+		}
+	}
+
+	_spAnimationState_queueEvents(self, from, animationTime);
+	from->nextAnimationLast = animationTime;
+	from->nextTrackLast = from->trackTime;
+
+	return mix;
+}
+
+void _spAnimationState_applyRotateTimeline (spAnimationState* self, spTimeline* timeline, spSkeleton* skeleton, float time, float alpha, int /*boolean*/ setupPose, float* timelinesRotation, int i, int /*boolean*/ firstFrame) {
+	spRotateTimeline *rotateTimeline;
+	float *frames;
+	spBone* bone;
+	float r1, r2;
+	int frame;
+	float prevRotation;
+	float frameTime;
+	float percent;
+	float total, diff;
+	int /*boolean*/ current, dir;
+
+	if (alpha == 1) {
+		spTimeline_apply(timeline, skeleton, 0, time, 0, 0, 1, setupPose, 0);
+		return;
+	}
+
+	rotateTimeline = SUB_CAST(spRotateTimeline, timeline);
+	frames = rotateTimeline->frames;
+	bone = skeleton->bones[rotateTimeline->boneIndex];
+	if (time < frames[0]) {
+		if (setupPose) {
+			bone->rotation = bone->data->rotation;
+		}
+		return; // Time is before first frame.
+	}
+
+	if (time >= frames[rotateTimeline->framesCount - ROTATE_ENTRIES]) // Time is after last frame.
+		r2 = bone->data->rotation + frames[rotateTimeline->framesCount + ROTATE_PREV_ROTATION];
+	else {
+		// Interpolate between the previous frame and the current frame.
+		frame = _spCurveTimeline_binarySearch(frames, rotateTimeline->framesCount, time, ROTATE_ENTRIES);
+		prevRotation = frames[frame + ROTATE_PREV_ROTATION];
+		frameTime = frames[frame];
+		percent = spCurveTimeline_getCurvePercent(SUPER(rotateTimeline), (frame >> 1) - 1,
+													   1 - (time - frameTime) / (frames[frame + ROTATE_PREV_TIME] - frameTime));
+
+		r2 = frames[frame + ROTATE_ROTATION] - prevRotation;
+		r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360;
+		r2 = prevRotation + r2 * percent + bone->data->rotation;
+		r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360;
+	}
+
+	// Mix between rotations using the direction of the shortest route on the first frame while detecting crosses.
+	r1 = setupPose ? bone->data->rotation : bone->rotation;
+	diff = r2 - r1;
+	if (diff == 0) {
+		if (firstFrame) {
+			timelinesRotation[i] = 0;
+			total = 0;
+		} else
+			total = timelinesRotation[i];
+	} else {
+		diff -= (16384 - (int)(16384.499999999996 - diff / 360)) * 360;
+		float lastTotal, lastDiff;
+		if (firstFrame) {
+			lastTotal = 0;
+			lastDiff = diff;
+		} else {
+			lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops.
+			lastDiff = timelinesRotation[i + 1]; // Difference between bones.
+		}
+		current = diff > 0;
+		dir = lastTotal >= 0;
+		// Detect cross at 0 (not 180).
+		if (SIGNUM(lastDiff) != SIGNUM(diff) && ABS(lastDiff) <= 90) {
+			// A cross after a 360 rotation is a loop.
+			if (ABS(lastTotal) > 180) lastTotal += 360 * SIGNUM(lastTotal);
+			dir = current;
+		}
+		total = diff + lastTotal - FMOD(lastTotal, 360); // Store loops as part of lastTotal.
+		if (dir != current) total += 360 * SIGNUM(lastTotal);
+		timelinesRotation[i] = total;
+	}
+	timelinesRotation[i + 1] = diff;
+	r1 += total * alpha;
+	bone->rotation = r1 - (16384 - (int)(16384.499999999996 - r1 / 360)) * 360;
+}
+
+void _spAnimationState_queueEvents (spAnimationState* self, spTrackEntry* entry, float animationTime) {
+	spEvent** events;
+	spEvent* event;
+	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+	int i, n;
+	float animationStart = entry->animationStart, animationEnd = entry->animationEnd;
+	float duration = animationEnd - animationStart;
+	float trackLastWrapped = FMOD(entry->trackLast, duration);
+
+	// Queue events before complete.
+	events = internal->events;
+	for (i = 0, n = internal->eventsCount; i < n; i++) {
+		event = events[i];
+		if (event->time < trackLastWrapped) break;
+		if (event->time > animationEnd) continue; // Discard events outside animation start/end.
+		_spEventQueue_event(internal->queue, entry, event);
+	}
+
+	// Queue complete if completed a loop iteration or the animation.
+	if (entry->loop ? (trackLastWrapped > FMOD(entry->trackTime, duration))
+				   : (animationTime >= animationEnd && entry->animationLast < animationEnd)) {
+		_spEventQueue_complete(internal->queue, entry);
+	}
+
+	// Queue events after complete.
+	for (; i < n; i++) {
+		event = events[i];
+		if (event->time < animationStart) continue; // Discard events outside animation start/end.
+		_spEventQueue_event(internal->queue, entry, event);
+	}
+	internal->eventsCount = 0;
+}
+
+void spAnimationState_clearTracks (spAnimationState* self) {
+	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+	int i, n;
+	internal->queue->drainDisabled = 1;
+	for (i = 0, n = self->tracksCount; i < n; i++)
+		spAnimationState_clearTrack(self, i);
+	self->tracksCount = 0;
+	internal->queue->drainDisabled = 0;
+	_spEventQueue_drain(internal->queue);
+}
+
+void spAnimationState_clearTrack (spAnimationState* self, int trackIndex) {
+	spTrackEntry* current;
+	spTrackEntry* entry;
+	spTrackEntry* from;
+	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+
+	if (trackIndex >= self->tracksCount) return;
+	current = self->tracks[trackIndex];
+	if (!current) return;
+
+	_spEventQueue_end(internal->queue, current);
+
+	_spAnimationState_disposeNext(self, current);
+
+	entry = current;
+	while (1) {
+		from = entry->mixingFrom;
+		if (!from) break;
+		_spEventQueue_end(internal->queue, from);
+		entry->mixingFrom = 0;
+		entry = from;
+	}
+
+	self->tracks[current->trackIndex, 0];
+	_spEventQueue_drain(internal->queue);
+}
+
+void _spAnimationState_setCurrent (spAnimationState* self, int index, spTrackEntry* current) {
+	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+	spTrackEntry* from = _spAnimationState_expandToIndex(self, index);
+	self->tracks[index] = current;
+
+	if (from) {
+		_spEventQueue_interrupt(internal->queue, from);
+		current->mixingFrom = from;
+		current->mixTime = 0;
+
+		// If not completely mixed in, set mixAlpha so mixing out happens from current mix to zero.
+		if (from->mixingFrom) current->mixAlpha *= MIN(from->mixTime / from->mixDuration, 1);
+	}
+
+	_spEventQueue_start(internal->queue, current);
+}
+
+/** Set the current animation. Any queued animations are cleared. */
+spTrackEntry* spAnimationState_setAnimationByName (spAnimationState* self, int trackIndex, const char* animationName,
+												   int/*bool*/loop) {
+	spAnimation* animation = spSkeletonData_findAnimation(self->data->skeletonData, animationName);
+	return spAnimationState_setAnimation(self, trackIndex, animation, loop);
+}
+
+spTrackEntry* spAnimationState_setAnimation (spAnimationState* self, int trackIndex, spAnimation* animation, int/*bool*/loop) {
+	spTrackEntry* entry;
+	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+	spTrackEntry* current = _spAnimationState_expandToIndex(self, trackIndex);
+	if (current) {
+		if (current->nextTrackLast == -1) {
+			// Don't mix from an entry that was never applied.
+			self->tracks[trackIndex] = 0;
+			_spEventQueue_interrupt(internal->queue, current);
+			_spEventQueue_end(internal->queue, current);
+			_spAnimationState_disposeNext(self, current);
+			current = 0;
+		} else
+			_spAnimationState_disposeNext(self, current);
+	}
+	entry = _spAnimationstate_trackEntry(self, trackIndex, animation, loop, current);
+	_spAnimationState_setCurrent(self, trackIndex, entry);
+	_spEventQueue_drain(internal->queue);
+	return entry;
+}
+
+/** Adds an animation to be played delay seconds after the current or last queued animation, taking into account any mix
+ * duration. */
+spTrackEntry* spAnimationState_addAnimationByName (spAnimationState* self, int trackIndex, const char* animationName,
+												   int/*bool*/loop, float delay) {
+	spAnimation* animation = spSkeletonData_findAnimation(self->data->skeletonData, animationName);
+	return spAnimationState_addAnimation(self, trackIndex, animation, loop, delay);
+}
+
+spTrackEntry* spAnimationState_addAnimation (spAnimationState* self, int trackIndex, spAnimation* animation, int/*bool*/loop,
+											 float delay) {
+	spTrackEntry* entry;
+	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+	spTrackEntry* last = _spAnimationState_expandToIndex(self, trackIndex);
+	if (last) {
+		while (last->next)
+			last = last->next;
+	}
+
+	entry = _spAnimationState_trackEntry(self, trackIndex, animation, loop, last);
+
+	if (!last) {
+		_spAnimationState_setCurrent(self, trackIndex, entry);
+		_spEventQueue_drain(internal->queue);
+	} else {
+		last->next = entry;
+		if (delay <= 0) {
+			float duration = last->animationEnd - last->animationStart;
+			if (duration != 0)
+				delay += duration * (1 + (int)(last->trackTime / duration)) - spAnimationStateData_getMix(self->data, last->animation, animation);
+			else
+				delay = 0;
+		}
+	}
+
+	entry->delay = delay;
+	return entry;
+}
+
+spTrackEntry* spAnimationState_setEmptyAnimation(spAnimationState* self, int trackIndex, float mixDuration) {
+	spTrackEntry* entry = spAnimationState_setAnimation(self, trackIndex, SP_EMPTY_ANIMATION, 0);
+	entry->mixDuration = mixDuration;
+	entry->trackEnd = mixDuration;
+	return entry;
+}
+
+spTrackEntry* spAnimationState_addEmptyAnimation(spAnimationState* self, int trackIndex, float mixDuration, float delay) {
+	spTrackEntry* entry;
+	if (delay <= 0) delay -= mixDuration;
+	entry = spAnimationState_addAnimation(self, trackIndex, SP_EMPTY_ANIMATION, 0, delay);
+	entry->mixDuration = mixDuration;
+	entry->trackEnd = mixDuration;
+	return entry;
+}
+
+spTrackEntry* spAnimationState_setEmptyAnimations(spAnimationState* self, float mixDuration) {
+	spTrackEntry* entry;
+	spTrackEntry* current;
+	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+	internal->queue->drainDisabled = 1;
+	for (int i = 0, n = self->tracksCount; i < n; i++) {
+		current = self->tracks[i];
+		if (current) spAnimationState_setEmptyAnimation(self, current->trackIndex, mixDuration);
+	}
+	internal->queue->drainDisabled = 0;
+	_spEventQueue_drain(internal->queue);
+}
+
+spTrackEntry* _spAnimationState_expandToIndex (spAnimationState* self, int index) {
+	spTrackEntry** newTracks;
+	if (index < self->tracksCount) return self->tracks[index];
+	newTracks = CALLOC(spTrackEntry*, index + 1);
+	memcpy(newTracks, self->tracks, self->tracksCount * sizeof(spTrackEntry*));
+	FREE(self->tracks);
+	self->tracks = newTracks;
+	self->tracksCount = index + 1;
+	return 0;
+}
+
+spTrackEntry* _spAnimationState_trackEntry (spAnimationState* self, int trackIndex, spAnimation* animation, int /*boolean*/ loop, spTrackEntry* last) {
+	spTrackEntry* entry = NEW(spTrackEntry);
+	entry->trackIndex = trackIndex;
+	entry->animation = animation;
+	entry->loop = loop;
+
+	entry->eventThreshold = 0;
+	entry->attachmentThreshold = 0;
+	entry->drawOrderThreshold = 0;
+
+	entry->animationStart = 0;
+	entry->animationEnd = animation->duration;
+	entry->animationLast = -1;
+	entry->nextAnimationLast = -1;
+
+	entry->delay = 0;
+	entry->trackTime = 0;
+	entry->trackLast = -1;
+	entry->nextTrackLast = -1;
+	entry->trackEnd = loop ? Integer.MAX_VALUE : entry->animationEnd;
+	entry->timeScale = 1;
+
+	entry->alpha = 1;
+	entry->mixAlpha = 1;
+	entry->mixTime = 0;
+	entry->mixDuration = !last ? 0 : spAnimationStateData_getMix(self->data, last->animation, animation);
+	return entry;
+}
+
+void _spAnimationState_disposeNext (spAnimationState* self, spTrackEntry* entry) {
+	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+	spTrackEntry* next = entry->next;
+	while (next) {
+		_spEventQueue_dispose(internal->queue, next);
+		next = next->next;
+	}
+	entry->next = 0;
+}
+
+spTrackEntry* spAnimationState_getCurrent (spAnimationState* self, int trackIndex) {
+	if (trackIndex >= self->tracksCount) return 0;
+	return self->tracks[trackIndex];
+}
+
+spTrackEntry* spAnimationState_clearListenerNotifications(spAnimationState* self) {
+	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+	_spEventQueue_clear(internal->queue);
+}
+
+float spTrackEntry_getAnimationTime (spTrackEntry* entry) {
+	if (entry->loop) {
+		float duration = entry->animationEnd - entry->animationStart;
+		if (duration == 0) return entry->animationStart;
+		return (FMOD(entry->trackTime, duration) + entry->animationStart;
+	}
+	return MIN(entry->trackTime + entry->animationStart, entry->animationEnd);
+}