AnimationState.h 18.4 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
/******************************************************************************
 * Spine Runtimes License Agreement
 * Last updated May 1, 2019. Replaces all prior versions.
 *
 * Copyright (c) 2013-2019, 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.
 *
 * THIS SOFTWARE IS 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 THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *****************************************************************************/

#ifndef Spine_AnimationState_h
#define Spine_AnimationState_h

#include <spine/Vector.h>
#include <spine/Pool.h>
#include <spine/MixBlend.h>
#include <spine/SpineObject.h>
#include <spine/SpineString.h>
#include <spine/HasRendererObject.h>

#ifdef SPINE_USE_STD_FUNCTION
#include <functional>
#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<void (AnimationState* state, EventType type, TrackEntry* entry, Event* event)> 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<int> _timelineMode;
		Vector<TrackEntry*> _timelineHoldMix;
		Vector<float> _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<EventQueueEntry> _eventQueueEntries;
		AnimationState& _state;
		Pool<TrackEntry>& _trackEntryPool;
		bool _drainDisabled;

		static EventQueue* newEventQueue(AnimationState& state, Pool<TrackEntry>& trackEntryPool);

		static EventQueueEntry newEventQueueEntry(EventType eventType, TrackEntry* entry, Event* event = NULL);

		EventQueue(AnimationState& state, Pool<TrackEntry>& 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 &lt;= 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 &lt;= 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<TrackEntry*> &getTracks();

		float getTimeScale();
		void setTimeScale(float inValue);

		void setListener(AnimationStateListener listener);
		void setListener(AnimationStateListenerObject* listener);

		void disableQueue();
		void enableQueue();

	private:

		AnimationStateData* _data;

		Pool<TrackEntry> _trackEntryPool;
		Vector<TrackEntry*> _tracks;
		Vector<Event*> _events;
		EventQueue* _queue;

		HashMap<int, bool> _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<float>& 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 */