diff --git a/spine-c/include/spine/Animation.h b/spine-c/include/spine/Animation.h index 203f5f5ed..1a432529a 100644 --- a/spine-c/include/spine/Animation.h +++ b/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; diff --git a/spine-c/include/spine/AnimationState.h b/spine-c/include/spine/AnimationState.h index 0817e2a80..3524afa38 100644 --- a/spine-c/include/spine/AnimationState.h +++ b/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 diff --git a/spine-c/include/spine/extension.h b/spine-c/include/spine/extension.h index 602a1d392..3d78e993d 100644 --- a/spine-c/include/spine/extension.h +++ b/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 diff --git a/spine-c/src/spine/Animation.c b/spine-c/src/spine/Animation.c index 34fc7599f..24ee787dc 100644 --- a/spine-c/src/spine/Animation.c +++ b/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; diff --git a/spine-c/src/spine/AnimationState.c b/spine-c/src/spine/AnimationState.c index 76399127d..9dbb2dfd6 100644 --- a/spine-c/src/spine/AnimationState.c +++ b/spine-c/src/spine/AnimationState.c @@ -32,27 +32,32 @@ #include #include -_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); + 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("", 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 { - 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++; + _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); } } - _spEventQueue_clear(self); - self->drainDisabled = 0; + _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); +} \ No newline at end of file