/****************************************************************************** * Spine Runtimes License Agreement * Last updated January 1, 2020. Replaces all prior versions. * * Copyright (c) 2013-2020, Esoteric Software LLC * * Integration of the Spine Runtimes into software or otherwise creating * derivative works of the Spine Runtimes is permitted under the terms and * conditions of Section 2 of the Spine Editor License Agreement: * http://esotericsoftware.com/spine-editor-license * * Otherwise, it is permitted to integrate the Spine Runtimes into software * or otherwise create derivative works of the Spine Runtimes (collectively, * "Products"), provided that each user of the Products must obtain their own * Spine Editor license and redistribution of the Products in any form must * include this license and copyright notice. * * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ #ifndef Spine_AnimationState_h #define Spine_AnimationState_h #include #include #include #include #include #include #ifdef SPINE_USE_STD_FUNCTION #include #endif namespace spine { enum EventType { EventType_Start, EventType_Interrupt, EventType_End, EventType_Complete, EventType_Dispose, EventType_Event }; class AnimationState; class TrackEntry; class Animation; class Event; class AnimationStateData; class Skeleton; class RotateTimeline; #ifdef SPINE_USE_STD_FUNCTION typedef std::function AnimationStateListener; #else typedef void (*AnimationStateListener) (AnimationState* state, EventType type, TrackEntry* entry, Event* event); #endif /// Abstract class to inherit from to create a callback object class SP_API AnimationStateListenerObject { public: AnimationStateListenerObject() { }; virtual ~AnimationStateListenerObject() { }; public: /// The callback function to be called virtual void callback(AnimationState* state, EventType type, TrackEntry* entry, Event* event) = 0; }; /// State for the playback of an animation class SP_API TrackEntry : public SpineObject, public HasRendererObject { friend class EventQueue; friend class AnimationState; public: TrackEntry(); virtual ~TrackEntry(); /// The index of the track where this entry is either current or queued. int getTrackIndex(); /// The animation to apply for this track entry. Animation* getAnimation(); /// If true, the animation will repeat. If false, it will not, instead its last frame is applied if played beyond its duration. bool getLoop(); void setLoop(bool inValue); /// If true, when mixing from the previous animation to this animation, the previous animation is applied as normal instead /// of being mixed out. /// /// When mixing between animations that key the same property, if a lower track also keys that property then the value will /// briefly dip toward the lower track value during the mix. This happens because the first animation mixes from 100% to 0% /// while the second animation mixes from 0% to 100%. Setting holdPrevious to true applies the first animation /// at 100% during the mix so the lower track value is overwritten. Such dipping does not occur on the lowest track which /// keys the property, only when a higher track also keys the property. /// /// Snapping will occur if holdPrevious is true and this animation does not key all the same properties as the /// previous animation. bool getHoldPrevious(); void setHoldPrevious(bool inValue); /// Seconds to postpone playing the animation. When a track entry is the current track entry, delay postpones incrementing /// the track time. When a track entry is queued, delay is the time from the start of the previous animation to when the /// track entry will become the current track entry. float getDelay(); void setDelay(float inValue); /// Current time in seconds this track entry has been the current track entry. The track time determines /// TrackEntry.AnimationTime. The track time can be set to start the animation at a time other than 0, without affecting looping. float getTrackTime(); void setTrackTime(float inValue); /// The track time in seconds when this animation will be removed from the track. Defaults to the animation duration for /// non-looping animations and to int.MaxValue for looping animations. If the track end time is reached and no /// other animations are queued for playback, and mixing from any previous animations is complete, properties keyed by the animation, /// are set to the setup pose and the track is cleared. /// /// It may be desired to use AnimationState.addEmptyAnimation(int, float, float) to mix the properties back to the /// setup pose over time, rather than have it happen instantly. float getTrackEnd(); void setTrackEnd(float inValue); /// Seconds when this animation starts, both initially and after looping. Defaults to 0. /// /// When changing the animation start time, it often makes sense to set TrackEntry.AnimationLast to the same value to /// prevent timeline keys before the start time from triggering. float getAnimationStart(); void setAnimationStart(float inValue); /// Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animations will /// loop back to TrackEntry.AnimationStart at this time. Defaults to the animation duration. float getAnimationEnd(); void setAnimationEnd(float inValue); /// 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 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. float getAnimationLast(); void setAnimationLast(float inValue); /// Uses TrackEntry.TrackTime to compute the animation time between TrackEntry.AnimationStart. and /// TrackEntry.AnimationEnd. When the track time is 0, the animation time is equal to the animation start time. float getAnimationTime(); /// Multiplier for the delta time when the animation state is updated, causing time for this animation to play slower or /// faster. Defaults to 1. float getTimeScale(); void setTimeScale(float inValue); /// Values less than 1 mix this animation with the last skeleton pose. Defaults to 1, which overwrites the last skeleton pose with /// this animation. /// /// Typically track 0 is used to completely pose the skeleton, then alpha can be used on higher tracks. It doesn't make sense /// to use alpha on track 0 if the skeleton pose is from the last frame render. float getAlpha(); void setAlpha(float inValue); /// /// When the mix percentage (mix time / mix duration) is less than the event threshold, event timelines for the animation /// being mixed out will be applied. Defaults to 0, so event timelines are not applied for an animation being mixed out. float getEventThreshold(); void setEventThreshold(float inValue); /// When the mix percentage (mix time / mix duration) is less than the attachment threshold, attachment timelines for the /// animation being mixed out will be applied. Defaults to 0, so attachment timelines are not applied for an animation being /// mixed out. float getAttachmentThreshold(); void setAttachmentThreshold(float inValue); /// When the mix percentage (mix time / mix duration) is less than the draw order threshold, draw order timelines for the /// animation being mixed out will be applied. Defaults to 0, so draw order timelines are not applied for an animation being /// mixed out. float getDrawOrderThreshold(); void setDrawOrderThreshold(float inValue); /// The animation queued to start after this animation, or NULL. TrackEntry* getNext(); /// Returns true if at least one loop has been completed. bool isComplete(); /// Seconds from 0 to the mix duration when mixing from the previous animation to this animation. May be slightly more than /// TrackEntry.MixDuration when the mix is complete. float getMixTime(); void setMixTime(float inValue); /// Seconds for mixing from the previous animation to this animation. Defaults to the value provided by /// AnimationStateData based on the animation before this animation (if any). /// /// The mix duration can be set manually rather than use the value from AnimationStateData.GetMix. /// In that case, the mixDuration must be set before AnimationState.update(float) is next called. /// /// When using AnimationState::addAnimation(int, Animation, bool, float) with a delay /// less than or equal to 0, note the Delay is set using the mix duration from the AnimationStateData float getMixDuration(); void setMixDuration(float inValue); MixBlend getMixBlend(); void setMixBlend(MixBlend blend); /// The track entry for the previous animation when mixing from the previous animation to this animation, or NULL if no /// mixing is currently occuring. When mixing from multiple animations, MixingFrom makes up a double linked list with MixingTo. TrackEntry* getMixingFrom(); /// The track entry for the next animation when mixing from this animation, or NULL if no mixing is currently occuring. /// When mixing from multiple animations, MixingTo makes up a double linked list with MixingFrom. TrackEntry* getMixingTo(); /// Resets the rotation directions for mixing this entry's rotate timelines. This can be useful to avoid bones rotating the /// long way around when using alpha and starting animations on other tracks. /// /// Mixing involves finding a rotation between two others, which has two possible solutions: the short way or the long way around. /// The two rotations likely change over time, so which direction is the short or long way also changes. /// If the short way was always chosen, bones would flip to the other side when that direction became the long way. /// TrackEntry chooses the short way the first time it is applied and remembers that direction. void resetRotationDirections(); void setListener(AnimationStateListener listener); void setListener(AnimationStateListenerObject* listener); private: Animation* _animation; TrackEntry* _next; TrackEntry* _mixingFrom; TrackEntry* _mixingTo; int _trackIndex; bool _loop, _holdPrevious; float _eventThreshold, _attachmentThreshold, _drawOrderThreshold; float _animationStart, _animationEnd, _animationLast, _nextAnimationLast; float _delay, _trackTime, _trackLast, _nextTrackLast, _trackEnd, _timeScale; float _alpha, _mixTime, _mixDuration, _interruptAlpha, _totalAlpha; MixBlend _mixBlend; Vector _timelineMode; Vector _timelineHoldMix; Vector _timelinesRotation; AnimationStateListener _listener; AnimationStateListenerObject* _listenerObject; void reset(); }; class SP_API EventQueueEntry : public SpineObject { friend class EventQueue; public: EventType _type; TrackEntry* _entry; Event* _event; EventQueueEntry(EventType eventType, TrackEntry* trackEntry, Event* event = NULL); }; class SP_API EventQueue : public SpineObject { friend class AnimationState; private: Vector _eventQueueEntries; AnimationState& _state; Pool& _trackEntryPool; bool _drainDisabled; static EventQueue* newEventQueue(AnimationState& state, Pool& trackEntryPool); static EventQueueEntry newEventQueueEntry(EventType eventType, TrackEntry* entry, Event* event = NULL); EventQueue(AnimationState& state, Pool& trackEntryPool); ~EventQueue(); void start(TrackEntry* entry); void interrupt(TrackEntry* entry); void end(TrackEntry* entry); void dispose(TrackEntry* entry); void complete(TrackEntry* entry); void event(TrackEntry* entry, Event* event); /// Raises all events in the queue and drains the queue. void drain(); }; class SP_API AnimationState : public SpineObject, public HasRendererObject { friend class TrackEntry; friend class EventQueue; public: explicit AnimationState(AnimationStateData* data); ~AnimationState(); /// Increments the track entry times, setting queued animations as current if needed /// @param delta delta time void update(float delta); /// Poses the skeleton using the track entry animations. There are no side effects other than invoking listeners, so the /// animation state can be applied to multiple skeletons to pose them identically. bool apply(Skeleton& skeleton); /// Removes all animations from all tracks, leaving skeletons in their previous pose. /// It may be desired to use AnimationState.setEmptyAnimations(float) to mix the skeletons back to the setup pose, /// rather than leaving them in their previous pose. void clearTracks(); /// Removes all animations from the tracks, leaving skeletons in their previous pose. /// It may be desired to use AnimationState.setEmptyAnimations(float) to mix the skeletons back to the setup pose, /// rather than leaving them in their previous pose. void clearTrack(size_t trackIndex); /// Sets an animation by name. setAnimation(int, Animation, bool) TrackEntry* setAnimation(size_t trackIndex, const String& animationName, bool loop); /// Sets the current animation for a track, discarding any queued animations. /// @param loop If true, the animation will repeat. /// If false, it will not, instead its last frame is applied if played beyond its duration. /// In either case TrackEntry.TrackEnd determines when the track is cleared. /// @return /// A track entry to allow further customization of animation playback. References to the track entry must not be kept /// after AnimationState.Dispose. TrackEntry* setAnimation(size_t trackIndex, Animation* animation, bool loop); /// Queues an animation by name. /// addAnimation(int, Animation, bool, float) TrackEntry* addAnimation(size_t trackIndex, const String& animationName, bool loop, float delay); /// Adds an animation to be played delay seconds after the current or last queued animation /// for a track. If the track is empty, it is equivalent to calling setAnimation. /// @param delay /// Seconds to begin this animation after the start of the previous animation. May be <= 0 to use the animation /// duration of the previous track minus any mix duration plus the negative delay. /// /// @return A track entry to allow further customization of animation playback. References to the track entry must not be kept /// after AnimationState.Dispose TrackEntry* addAnimation(size_t trackIndex, Animation* animation, bool loop, float delay); /// Sets an empty animation for a track, discarding any queued animations, and mixes to it over the specified mix duration. TrackEntry* setEmptyAnimation(size_t trackIndex, float mixDuration); /// Adds an empty animation to be played after the current or last queued animation for a track, and mixes to it over the /// specified mix duration. /// @return /// A track entry to allow further customization of animation playback. References to the track entry must not be kept after AnimationState.Dispose. /// /// @param trackIndex Track number. /// @param mixDuration Mix duration. /// @param delay Seconds to begin this animation after the start of the previous animation. May be <= 0 to use the animation /// duration of the previous track minus any mix duration plus the negative delay. TrackEntry* addEmptyAnimation(size_t trackIndex, float mixDuration, float delay); /// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix duration. void setEmptyAnimations(float mixDuration); /// @return The track entry for the animation currently playing on the track, or NULL if no animation is currently playing. TrackEntry* getCurrent(size_t trackIndex); AnimationStateData* getData(); /// A list of tracks that have animations, which may contain NULLs. Vector &getTracks(); float getTimeScale(); void setTimeScale(float inValue); void setListener(AnimationStateListener listener); void setListener(AnimationStateListenerObject* listener); void disableQueue(); void enableQueue(); private: AnimationStateData* _data; Pool _trackEntryPool; Vector _tracks; Vector _events; EventQueue* _queue; HashMap _propertyIDs; bool _animationsChanged; AnimationStateListener _listener; AnimationStateListenerObject* _listenerObject; float _timeScale; static Animation* getEmptyAnimation(); static void applyRotateTimeline(RotateTimeline* rotateTimeline, Skeleton& skeleton, float time, float alpha, MixBlend pose, Vector& timelinesRotation, size_t i, bool firstFrame); /// Returns true when all mixing from entries are complete. bool updateMixingFrom(TrackEntry* to, float delta); float applyMixingFrom(TrackEntry* to, Skeleton& skeleton, MixBlend currentPose); void queueEvents(TrackEntry* entry, float animationTime); /// Sets the active TrackEntry for a given track number. void setCurrent(size_t index, TrackEntry *current, bool interrupt); TrackEntry* expandToIndex(size_t index); /// Object-pooling version of new TrackEntry. Obtain an unused TrackEntry from the pool and clear/initialize its values. /// @param last May be NULL. TrackEntry* newTrackEntry(size_t trackIndex, Animation *animation, bool loop, TrackEntry *last); /// Dispose all track entries queued after the given TrackEntry. void disposeNext(TrackEntry* entry); void animationsChanged(); void computeHold(TrackEntry *entry); void computeNotLast(TrackEntry *entry); }; } #endif /* Spine_AnimationState_h */