Stephen Gowen 7 years ago
parent
commit
0ca6803030

+ 378 - 357
spine-cpp/spine-cpp/include/spine/AnimationState.h

@@ -33,6 +33,7 @@
 
 #include <spine/Vector.h>
 #include <spine/Pool.h>
+#include <spine/MixPose.h>
 
 namespace Spine
 {
@@ -51,6 +52,9 @@ namespace Spine
 
     class Animation;
     class Event;
+    class AnimationStateData;
+    class Skeleton;
+    class RotateTimeline;
     
     typedef void (*OnAnimationEventFunc) (AnimationState& state, EventType type, TrackEntry* entry, Event* event);
     
@@ -58,6 +62,7 @@ namespace Spine
     class TrackEntry
     {
         friend class EventQueue;
+        friend class AnimationState;
         
     public:
         TrackEntry();
@@ -245,12 +250,7 @@ namespace Spine
         TrackEntry* _entry;
         Event* _event;
         
-        EventQueueEntry(EventType eventType, TrackEntry* trackEntry, Event* event = NULL)
-        {
-            _type = eventType;
-            _entry = trackEntry;
-            _event = event;
-        }
+        EventQueueEntry(EventType eventType, TrackEntry* trackEntry, Event* event = NULL);
     };
     
     class EventQueue
@@ -259,13 +259,18 @@ namespace Spine
         
     private:
         Vector<EventQueueEntry*> _eventQueueEntries;
-        bool _drainDisabled;
-
         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);
@@ -276,12 +281,10 @@ namespace Spine
 
         void complete(TrackEntry* entry);
 
-        void event(TrackEntry* entry, Event* e);
+        void event(TrackEntry* entry, Event* event);
 
         /// Raises all events in the queue and drains the queue.
         void drain();
-
-        void clear();
     };
     
     class AnimationState
@@ -290,49 +293,17 @@ namespace Spine
         friend class EventQueue;
         
     public:
-        AnimationState();
+        AnimationState(AnimationStateData& data);
         
-        void setOnAnimationEventFunc(OnAnimationEventFunc inValue);
+        ~AnimationState();
         
-    private:
-        static const int Subsequent, First, Dip, DipMix;
-//        static readonly Animation EmptyAnimation = new Animation("<empty>", new Vector<Timeline>(), 0);
+        void setOnAnimationEventFunc(OnAnimationEventFunc inValue);
         
-//
-//        private AnimationStateData data;
-//
-//        Pool<TrackEntry> trackEntryPool = new Pool<TrackEntry>();
-//        private readonly Vector<TrackEntry> tracks = new Vector<TrackEntry>();
-//        private readonly Vector<Event> events = new Vector<Event>();
-//        private readonly EventQueue queue; // Initialized by constructor.
-//
-//        private readonly HashSet<int> propertyIDs = new HashSet<int>();
-//        private readonly Vector<TrackEntry> mixingTo = new Vector<TrackEntry>();
-        bool _animationsChanged;
-//
-//        private float timeScale = 1;
-//
-//        public AnimationStateData Data { get { return data; } }
-//        /// A list of tracks that have animations, which may contain NULLs.
-//        public Vector<TrackEntry> Tracks { get { return tracks; } }
-//        public float TimeScale { get { return timeScale; } set { timeScale = value; } }
-//
-        OnAnimationEventFunc _onAnimationEventFunc;
-//
-//        public AnimationState(AnimationStateData data) {
-//            if (data == NULL) throw new ArgumentNULLException("data", "data cannot be NULL.");
-//            _data = data;
-//            _queue = new EventQueue(
-//                                        this,
-//                                        delegate { _animationsChanged = true; },
-//                                        trackEntryPool
-//                                        );
-//        }
-//
-//        ///
-//        /// Increments the track entry times, setting queued animations as current if needed
-//        /// @param delta delta time
-//        public void update(float delta) {
+        ///
+        /// Increments the track entry times, setting queued animations as current if needed
+        /// @param delta delta time
+        void update(float delta)
+        {
 //            delta *= timeScale;
 //            var tracksItems = tracks.Items;
 //            for (int i = 0, n = tracks.Count; i < n; i++) {
@@ -388,43 +359,18 @@ namespace Spine
 //            }
 //
 //            queue.drain();
-//        }
-//
-//        /// Returns true when all mixing from entries are complete.
-//        private bool updateMixingFrom(TrackEntry to, float delta) {
-//            TrackEntry from = to.mixingFrom;
-//            if (from == NULL) return true;
-//
-//            bool finished = updateMixingFrom(from, delta);
-//
-//            // Require mixTime > 0 to ensure the mixing from entry was applied at least once.
-//            if (to.mixTime > 0 && (to.mixTime >= to.mixDuration || to.timeScale == 0)) {
-//                // Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame).
-//                if (from.totalAlpha == 0 || to.mixDuration == 0) {
-//                    to.mixingFrom = from.mixingFrom;
-//                    to.interruptAlpha = from.interruptAlpha;
-//                    queue.end(from);
-//                }
-//                return finished;
-//            }
-//
-//            from.animationLast = from.nextAnimationLast;
-//            from.trackLast = from.nextTrackLast;
-//            from.trackTime += delta * from.timeScale;
-//            to.mixTime += delta * to.timeScale;
-//            return false;
-//        }
-//
-//        ///
-//        /// 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.
-//        public bool apply(Skeleton skeleton) {
-//            if (skeleton == NULL) throw new ArgumentNULLException("skeleton", "skeleton cannot be NULL.");
+        }
+        
+        ///
+        /// 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)
+        {
 //            if (animationsChanged) animationsChanged();
 //
 //            var events = _events;
 //
-//            bool applied = false;
+            bool applied = false;
 //            var tracksItems = tracks.Items;
 //            for (int i = 0, m = tracks.Count; i < m; i++) {
 //                TrackEntry current = tracksItems[i];
@@ -471,95 +417,236 @@ namespace Spine
 //            }
 //
 //            queue.drain();
-//            return applied;
-//        }
+            return applied;
+        }
+        
+        ///
+        /// 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()
+        {
+//            bool olddrainDisabled = queue.drainDisabled;
+//            queue.drainDisabled = true;
+//            for (int i = 0, n = tracks.Count; i < n; i++) {
+//                clearTrack(i);
+//            }
+//            tracks.clear();
+//            queue.drainDisabled = olddrainDisabled;
+//            queue.drain();
+        }
+        
+        ///
+        /// 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(int trackIndex)
+        {
+//            if (trackIndex >= tracks.Count) return;
+//            TrackEntry current = tracks.Items[trackIndex];
+//            if (current == NULL) return;
 //
-//        private float applyMixingFrom(TrackEntry to, Skeleton skeleton, MixPose currentPose) {
-//            TrackEntry from = to.mixingFrom;
-//            if (from.mixingFrom != NULL) applyMixingFrom(from, skeleton, currentPose);
+//            queue.end(current);
 //
-//            float mix;
-//            if (to.mixDuration == 0) { // Single frame mix to undo mixingFrom changes.
-//                mix = 1;
-//                currentPose = MixPose.Setup;
-//            } else {
-//                mix = to.mixTime / to.mixDuration;
-//                if (mix > 1) mix = 1;
-//            }
+//            disposeNext(current);
 //
-//            var eventBuffer = mix < from.eventThreshold ? _events : NULL;
-//            bool attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold;
-//            float animationLast = from.animationLast, animationTime = from.AnimationTime;
-//            var timelines = from.animation.timelines;
-//            int timelineCount = timelines.Count;
-//            var timelinesItems = timelines.Items;
-//            var timelineData = from.timelineData.Items;
-//            var timelineDipMix = from.timelineDipMix.Items;
+//            TrackEntry entry = current;
+//            while (true) {
+//                TrackEntry from = entry.mixingFrom;
+//                if (from == NULL) break;
+//                queue.end(from);
+//                entry.mixingFrom = NULL;
+//                entry = from;
+//            }
 //
-//            bool firstFrame = from.timelinesRotation.Count == 0;
-//            if (firstFrame) from.timelinesRotation.Resize(timelines.Count << 1); // from.timelinesRotation.setSize
-//            var timelinesRotation = from.timelinesRotation.Items;
+//            tracks.Items[current.trackIndex] = NULL;
 //
-//            MixPose pose;
-//            float alphaDip = from.alpha * to.interruptAlpha, alphaMix = alphaDip * (1 - mix), alpha;
-//            from.totalAlpha = 0;
-//            for (int i = 0; i < timelineCount; i++) {
-//                Timeline timeline = timelinesItems[i];
-//                switch (timelineData[i]) {
-//                    case Subsequent:
-//                        if (!attachments && timeline is AttachmentTimeline) continue;
-//                        if (!drawOrder && timeline is DrawOrderTimeline) continue;
-//                        pose = currentPose;
-//                        alpha = alphaMix;
-//                        break;
-//                    case First:
-//                        pose = MixPose.Setup;
-//                        alpha = alphaMix;
-//                        break;
-//                    case Dip:
-//                        pose = MixPose.Setup;
-//                        alpha = alphaDip;
-//                        break;
-//                    default:
-//                        pose = MixPose.Setup;
-//                        TrackEntry dipMix = timelineDipMix[i];
-//                        alpha = alphaDip * Math.Max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
-//                        break;
-//                }
-//                from.totalAlpha += alpha;
-//                var rotateTimeline = timeline as RotateTimeline;
-//                if (rotateTimeline != NULL) {
-//                    applyRotateTimeline(rotateTimeline, skeleton, animationTime, alpha, pose, timelinesRotation, i << 1, firstFrame);
+//            queue.drain();
+        }
+        
+        /// Sets an animation by name. setAnimation(int, Animation, bool)
+        TrackEntry* setAnimation(int trackIndex, std::string animationName, bool loop)
+        {
+//            Animation animation = data.skeletonData.FindAnimation(animationName);
+//            if (animation == NULL) throw new ArgumentException("Animation not found: " + animationName, "animationName");
+//            return setAnimation(trackIndex, animation, loop);
+            return NULL;
+        }
+        
+        /// 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(int trackIndex, Animation& animation, bool loop)
+        {
+//            bool interrupt = true;
+//            TrackEntry current = expandToIndex(trackIndex);
+//            if (current != NULL) {
+//                if (current.nextTrackLast == -1) {
+//                    // Don't mix from an entry that was never applied.
+//                    tracks.Items[trackIndex] = current.mixingFrom;
+//                    queue.interrupt(current);
+//                    queue.end(current);
+//                    disposeNext(current);
+//                    current = current.mixingFrom;
+//                    interrupt = false;
 //                } else {
-//                    timeline.apply(skeleton, animationLast, animationTime, eventBuffer, alpha, pose, MixDirection.Out);
+//                    disposeNext(current);
 //                }
 //            }
+//            TrackEntry entry = newTrackEntry(trackIndex, animation, loop, current);
+//            setCurrent(trackIndex, entry, interrupt);
+//            queue.drain();
+//            return entry;
+            return NULL;
+        }
+        
+        /// Queues an animation by name.
+        /// addAnimation(int, Animation, bool, float)
+        TrackEntry* addAnimation(int trackIndex, std::string animationName, bool loop, float delay)
+        {
+//            Animation animation = data.skeletonData.FindAnimation(animationName);
+//            if (animation == NULL) throw new ArgumentException("Animation not found: " + animationName, "animationName");
+//            return addAnimation(trackIndex, animation, loop, delay);
+            return NULL;
+        }
+        
+        /// 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(int trackIndex, Animation& animation, bool loop, float delay)
+        {
+//            TrackEntry last = expandToIndex(trackIndex);
+//            if (last != NULL) {
+//                while (last.next != NULL)
+//                    last = last.next;
+//            }
 //
-//            if (to.mixDuration > 0) queueEvents(from, animationTime);
-//            _events.clear(false);
-//            from.nextAnimationLast = animationTime;
-//            from.nextTrackLast = from.trackTime;
-//
-//            return mix;
-//        }
+//            TrackEntry entry = newTrackEntry(trackIndex, animation, loop, last);
 //
-//        static private void applyRotateTimeline(RotateTimeline rotateTimeline, Skeleton skeleton, float time, float alpha, MixPose pose,
-//                                                 float[] timelinesRotation, int i, bool firstFrame) {
+//            if (last == NULL) {
+//                setCurrent(trackIndex, entry, true);
+//                queue.drain();
+//            } else {
+//                last.next = entry;
+//                if (delay <= 0) {
+//                    float duration = last.animationEnd - last.animationStart;
+//                    if (duration != 0)
+//                        delay += duration * (1 + (int)(last.trackTime / duration)) - data.GetMix(last.animation, animation);
+//                    else
+//                        delay = 0;
+//                }
+//            }
 //
+//            entry.delay = delay;
+//            return entry;
+            return NULL;
+        }
+        
+        ///
+        /// Sets an empty animation for a track, discarding any queued animations, and mixes to it over the specified mix duration.
+        TrackEntry* setEmptyAnimation(int trackIndex, float mixDuration)
+        {
+            TrackEntry* entry = setAnimation(trackIndex, AnimationState::getEmptyAnimation(), false);
+            entry->_mixDuration = mixDuration;
+            entry->_trackEnd = mixDuration;
+            return entry;
+        }
+        
+        ///
+        /// 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(int trackIndex, float mixDuration, float delay)
+        {
+            if (delay <= 0)
+            {
+                delay -= mixDuration;
+            }
+            
+            TrackEntry* entry = addAnimation(trackIndex, AnimationState::getEmptyAnimation(), false, delay);
+            entry->_mixDuration = mixDuration;
+            entry->_trackEnd = mixDuration;
+            return entry;
+        }
+        
+        ///
+        /// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix duration.
+        void setEmptyAnimations(float mixDuration)
+        {
+//            bool olddrainDisabled = queue.drainDisabled;
+//            queue.drainDisabled = true;
+//            for (int i = 0, n = tracks.Count; i < n; i++)
+//            {
+//                TrackEntry current = tracks.Items[i];
+//                if (current != NULL) setEmptyAnimation(i, mixDuration);
+//            }
+//            queue.drainDisabled = olddrainDisabled;
+//            queue.drain();
+        }
+        
+        /// @return The track entry for the animation currently playing on the track, or NULL if no animation is currently playing.
+        TrackEntry* getCurrent(int trackIndex)
+        {
+            return trackIndex >= _tracks.size() ? NULL : _tracks[trackIndex];
+        }
+        
+//        AnimationStateData Data { get { return data; } }
+//        /// A list of tracks that have animations, which may contain NULLs.
+//        Vector<TrackEntry> Tracks { get { return tracks; } }
+//        float TimeScale { get { return timeScale; } set { timeScale = value; } }
+        
+    private:
+        static const int Subsequent, First, Dip, DipMix;
+        
+        AnimationStateData& _data;
+
+        Pool<TrackEntry> _trackEntryPool;
+        Vector<TrackEntry*> _tracks;
+        Vector<Event> _events;
+        EventQueue* _queue;
+
+        Vector<int> _propertyIDs;
+        Vector<TrackEntry> _mixingTo;
+        bool _animationsChanged;
+
+        OnAnimationEventFunc _onAnimationEventFunc;
+        
+        float _timeScale;
+
+        static Animation& getEmptyAnimation();
+        
+        static void applyRotateTimeline(RotateTimeline* rotateTimeline, Skeleton& skeleton, float time, float alpha, MixPose pose,
+                                        Vector<float>& timelinesRotation, int i, bool firstFrame)
+        {
 //            if (firstFrame) timelinesRotation[i] = 0;
-//
+//            
 //            if (alpha == 1) {
 //                rotateTimeline.apply(skeleton, 0, time, NULL, 1, pose, MixDirection.In);
 //                return;
 //            }
-//
+//            
 //            Bone bone = skeleton.bones.Items[rotateTimeline.boneIndex];
 //            float[] frames = rotateTimeline.frames;
 //            if (time < frames[0]) {
 //                if (pose == MixPose.Setup) bone.rotation = bone.data.rotation;
 //                return;
 //            }
-//
+//            
 //            float r2;
 //            if (time >= frames[frames.Length - RotateTimeline.ENTRIES]) // Time is after last frame.
 //                r2 = bone.data.rotation + frames[frames.Length + RotateTimeline.PREV_ROTATION];
@@ -570,13 +657,13 @@ namespace Spine
 //                float frameTime = frames[frame];
 //                float percent = rotateTimeline.GetCurvePercent((frame >> 1) - 1,
 //                                                               1 - (time - frameTime) / (frames[frame + RotateTimeline.PREV_TIME] - frameTime));
-//
+//                
 //                r2 = frames[frame + RotateTimeline.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.
 //            float r1 = pose == MixPose.Setup ? bone.data.rotation : bone.rotation;
 //            float total, diff = r2 - r1;
@@ -606,9 +693,107 @@ namespace Spine
 //            timelinesRotation[i + 1] = diff;
 //            r1 += total * alpha;
 //            bone.rotation = r1 - (16384 - (int)(16384.499999999996 - r1 / 360)) * 360;
-//        }
+        }
+        
+        /// Returns true when all mixing from entries are complete.
+        bool updateMixingFrom(TrackEntry to, float delta)
+        {
+//            TrackEntry from = to.mixingFrom;
+//            if (from == NULL) return true;
+//
+//            bool finished = updateMixingFrom(from, delta);
+//
+//            // Require mixTime > 0 to ensure the mixing from entry was applied at least once.
+//            if (to.mixTime > 0 && (to.mixTime >= to.mixDuration || to.timeScale == 0)) {
+//                // Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame).
+//                if (from.totalAlpha == 0 || to.mixDuration == 0) {
+//                    to.mixingFrom = from.mixingFrom;
+//                    to.interruptAlpha = from.interruptAlpha;
+//                    queue.end(from);
+//                }
+//                return finished;
+//            }
+//
+//            from.animationLast = from.nextAnimationLast;
+//            from.trackLast = from.nextTrackLast;
+//            from.trackTime += delta * from.timeScale;
+//            to.mixTime += delta * to.timeScale;
+            return false;
+        }
+        
+        float applyMixingFrom(TrackEntry* to, Skeleton& skeleton, MixPose currentPose)
+        {
+//            TrackEntry from = to.mixingFrom;
+//            if (from.mixingFrom != NULL) applyMixingFrom(from, skeleton, currentPose);
+//
+//            float mix;
+//            if (to.mixDuration == 0) { // Single frame mix to undo mixingFrom changes.
+//                mix = 1;
+//                currentPose = MixPose.Setup;
+//            } else {
+//                mix = to.mixTime / to.mixDuration;
+//                if (mix > 1) mix = 1;
+//            }
+//
+//            var eventBuffer = mix < from.eventThreshold ? _events : NULL;
+//            bool attachments = mix < from.attachmentThreshold, drawOrder = mix < from.drawOrderThreshold;
+//            float animationLast = from.animationLast, animationTime = from.AnimationTime;
+//            var timelines = from.animation.timelines;
+//            int timelineCount = timelines.Count;
+//            var timelinesItems = timelines.Items;
+//            var timelineData = from.timelineData.Items;
+//            var timelineDipMix = from.timelineDipMix.Items;
+//
+//            bool firstFrame = from.timelinesRotation.Count == 0;
+//            if (firstFrame) from.timelinesRotation.Resize(timelines.Count << 1); // from.timelinesRotation.setSize
+//            var timelinesRotation = from.timelinesRotation.Items;
 //
-//        private void queueEvents(TrackEntry entry, float animationTime) {
+//            MixPose pose;
+//            float alphaDip = from.alpha * to.interruptAlpha, alphaMix = alphaDip * (1 - mix), alpha;
+//            from.totalAlpha = 0;
+//            for (int i = 0; i < timelineCount; i++) {
+//                Timeline timeline = timelinesItems[i];
+//                switch (timelineData[i]) {
+//                    case Subsequent:
+//                        if (!attachments && timeline is AttachmentTimeline) continue;
+//                        if (!drawOrder && timeline is DrawOrderTimeline) continue;
+//                        pose = currentPose;
+//                        alpha = alphaMix;
+//                        break;
+//                    case First:
+//                        pose = MixPose.Setup;
+//                        alpha = alphaMix;
+//                        break;
+//                    case Dip:
+//                        pose = MixPose.Setup;
+//                        alpha = alphaDip;
+//                        break;
+//                    default:
+//                        pose = MixPose.Setup;
+//                        TrackEntry dipMix = timelineDipMix[i];
+//                        alpha = alphaDip * Math.Max(0, 1 - dipMix.mixTime / dipMix.mixDuration);
+//                        break;
+//                }
+//                from.totalAlpha += alpha;
+//                var rotateTimeline = timeline as RotateTimeline;
+//                if (rotateTimeline != NULL) {
+//                    applyRotateTimeline(rotateTimeline, skeleton, animationTime, alpha, pose, timelinesRotation, i << 1, firstFrame);
+//                } else {
+//                    timeline.apply(skeleton, animationLast, animationTime, eventBuffer, alpha, pose, MixDirection.Out);
+//                }
+//            }
+//
+//            if (to.mixDuration > 0) queueEvents(from, animationTime);
+//            _events.clear(false);
+//            from.nextAnimationLast = animationTime;
+//            from.nextTrackLast = from.trackTime;
+//
+//            return mix;
+            return 0;
+        }
+        
+        void queueEvents(TrackEntry* entry, float animationTime)
+        {
 //            float animationStart = entry.animationStart, animationEnd = entry.animationEnd;
 //            float duration = animationEnd - animationStart;
 //            float trackLastWrapped = entry.trackLast % duration;
@@ -636,52 +821,11 @@ namespace Spine
 //                if (e.time < animationStart) continue; // Discard events outside animation start/end.
 //                queue.event(entry, eventsItems[i]);
 //            }
-//        }
-//
-//        ///
-//        /// 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.
-//        public void clearTracks() {
-//            bool olddrainDisabled = queue.drainDisabled;
-//            queue.drainDisabled = true;
-//            for (int i = 0, n = tracks.Count; i < n; i++) {
-//                clearTrack(i);
-//            }
-//            tracks.clear();
-//            queue.drainDisabled = olddrainDisabled;
-//            queue.drain();
-//        }
-//
-//        ///
-//        /// 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.
-//        public void clearTrack(int trackIndex) {
-//            if (trackIndex >= tracks.Count) return;
-//            TrackEntry current = tracks.Items[trackIndex];
-//            if (current == NULL) return;
-//
-//            queue.end(current);
-//
-//            disposeNext(current);
-//
-//            TrackEntry entry = current;
-//            while (true) {
-//                TrackEntry from = entry.mixingFrom;
-//                if (from == NULL) break;
-//                queue.end(from);
-//                entry.mixingFrom = NULL;
-//                entry = from;
-//            }
-//
-//            tracks.Items[current.trackIndex] = NULL;
-//
-//            queue.drain();
-//        }
-//
-//        /// Sets the active TrackEntry for a given track number.
-//        private void setCurrent(int index, TrackEntry current, bool interrupt) {
+        }
+        
+        /// Sets the active TrackEntry for a given track number.
+        void setCurrent(int index, TrackEntry* current, bool interrupt)
+        {
 //            TrackEntry from = expandToIndex(index);
 //            tracks.Items[index] = current;
 //
@@ -698,141 +842,22 @@ namespace Spine
 //            }
 //
 //            queue.start(current); // triggers animationsChanged
-//        }
-//
-//
-//        /// Sets an animation by name. setAnimation(int, Animation, bool)
-//        public TrackEntry setAnimation(int trackIndex, string animationName, bool loop) {
-//            Animation animation = data.skeletonData.FindAnimation(animationName);
-//            if (animation == NULL) throw new ArgumentException("Animation not found: " + animationName, "animationName");
-//            return setAnimation(trackIndex, animation, 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.
-//        public TrackEntry setAnimation(int trackIndex, Animation animation, bool loop) {
-//            if (animation == NULL) throw new ArgumentNULLException("animation", "animation cannot be NULL.");
-//            bool interrupt = true;
-//            TrackEntry current = expandToIndex(trackIndex);
-//            if (current != NULL) {
-//                if (current.nextTrackLast == -1) {
-//                    // Don't mix from an entry that was never applied.
-//                    tracks.Items[trackIndex] = current.mixingFrom;
-//                    queue.interrupt(current);
-//                    queue.end(current);
-//                    disposeNext(current);
-//                    current = current.mixingFrom;
-//                    interrupt = false;
-//                } else {
-//                    disposeNext(current);
-//                }
-//            }
-//            TrackEntry entry = newTrackEntry(trackIndex, animation, loop, current);
-//            setCurrent(trackIndex, entry, interrupt);
-//            queue.drain();
-//            return entry;
-//        }
-//
-//        /// Queues an animation by name.
-//        /// addAnimation(int, Animation, bool, float)
-//        public TrackEntry addAnimation(int trackIndex, string animationName, bool loop, float delay) {
-//            Animation animation = data.skeletonData.FindAnimation(animationName);
-//            if (animation == NULL) throw new ArgumentException("Animation not found: " + animationName, "animationName");
-//            return addAnimation(trackIndex, animation, loop, 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
-//        public TrackEntry addAnimation(int trackIndex, Animation animation, bool loop, float delay) {
-//            if (animation == NULL) throw new ArgumentNULLException("animation", "animation cannot be NULL.");
-//
-//            TrackEntry last = expandToIndex(trackIndex);
-//            if (last != NULL) {
-//                while (last.next != NULL)
-//                    last = last.next;
-//            }
-//
-//            TrackEntry entry = newTrackEntry(trackIndex, animation, loop, last);
-//
-//            if (last == NULL) {
-//                setCurrent(trackIndex, entry, true);
-//                queue.drain();
-//            } else {
-//                last.next = entry;
-//                if (delay <= 0) {
-//                    float duration = last.animationEnd - last.animationStart;
-//                    if (duration != 0)
-//                        delay += duration * (1 + (int)(last.trackTime / duration)) - data.GetMix(last.animation, animation);
-//                    else
-//                        delay = 0;
-//                }
-//            }
-//
-//            entry.delay = delay;
-//            return entry;
-//        }
-//
-//        ///
-//        /// Sets an empty animation for a track, discarding any queued animations, and mixes to it over the specified mix duration.
-//        public TrackEntry setEmptyAnimation(int trackIndex, float mixDuration) {
-//            TrackEntry entry = setAnimation(trackIndex, AnimationState.EmptyAnimation, false);
-//            entry.mixDuration = mixDuration;
-//            entry.trackEnd = mixDuration;
-//            return entry;
-//        }
-//
-//        ///
-//        /// 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.
-//        public TrackEntry addEmptyAnimation(int trackIndex, float mixDuration, float delay) {
-//            if (delay <= 0) delay -= mixDuration;
-//            TrackEntry entry = addAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay);
-//            entry.mixDuration = mixDuration;
-//            entry.trackEnd = mixDuration;
-//            return entry;
-//        }
-//
-//        ///
-//        /// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix duration.
-//        public void setEmptyAnimations(float mixDuration) {
-//            bool olddrainDisabled = queue.drainDisabled;
-//            queue.drainDisabled = true;
-//            for (int i = 0, n = tracks.Count; i < n; i++) {
-//                TrackEntry current = tracks.Items[i];
-//                if (current != NULL) setEmptyAnimation(i, mixDuration);
-//            }
-//            queue.drainDisabled = olddrainDisabled;
-//            queue.drain();
-//        }
-//
-//        private TrackEntry expandToIndex(int index) {
+        }
+
+        TrackEntry* expandToIndex(int index)
+        {
 //            if (index < tracks.Count) return tracks.Items[index];
 //            while (index >= tracks.Count)
+//            {
 //                tracks.Add(NULL);
-//            return NULL;
-//        }
-//
-//        /// Object-pooling version of new TrackEntry. Obtain an unused TrackEntry from the pool and clear/initialize its values.
-//        /// @param last May be NULL.
-//        private TrackEntry newTrackEntry(int trackIndex, Animation animation, bool loop, TrackEntry last) {
+//            }
+            return NULL;
+        }
+
+        /// 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(int trackIndex, Animation* animation, bool loop, TrackEntry* last)
+        {
 //            TrackEntry entry = trackEntryPool.Obtain(); // Pooling
 //            entry.trackIndex = trackIndex;
 //            entry.animation = animation;
@@ -859,19 +884,23 @@ namespace Spine
 //            entry.mixTime = 0;
 //            entry.mixDuration = (last == NULL) ? 0 : data.GetMix(last.animation, animation);
 //            return entry;
-//        }
-//
-//        /// Dispose all track entries queued after the given TrackEntry.
-//        private void disposeNext(TrackEntry entry) {
+            return NULL;
+        }
+
+        /// Dispose all track entries queued after the given TrackEntry.
+        void disposeNext(TrackEntry* entry)
+        {
 //            TrackEntry next = entry.next;
-//            while (next != NULL) {
+//            while (next != NULL)
+//            {
 //                queue.dispose(next);
 //                next = next.next;
 //            }
 //            entry.next = NULL;
-//        }
-//
-//        private void animationsChanged() {
+        }
+
+        void animationsChanged()
+        {
 //            animationsChanged = false;
 //
 //            var propertyIDs = _propertyIDs;
@@ -879,23 +908,15 @@ namespace Spine
 //            var mixingTo = _mixingTo;
 //
 //            var tracksItems = tracks.Items;
-//            for (int i = 0, n = tracks.Count; i < n; i++) {
+//            for (int i = 0, n = tracks.Count; i < n; i++)
+//            {
 //                var entry = tracksItems[i];
-//                if (entry != NULL) entry.setTimelineData(NULL, mixingTo, propertyIDs);
+//                if (entry != NULL)
+//                {
+//                    entry.setTimelineData(NULL, mixingTo, propertyIDs);
+//                }
 //            }
-//        }
-//
-//        /// @return The track entry for the animation currently playing on the track, or NULL if no animation is currently playing.
-//        public TrackEntry getCurrent(int trackIndex) {
-//            return (trackIndex >= tracks.Count) ? NULL : tracks.Items[trackIndex];
-//        }
-//
-//        internal void onStart(TrackEntry entry) { if (Start != NULL) Start(entry); }
-//        internal void onInterrupt(TrackEntry entry) { if (Interrupt != NULL) Interrupt(entry); }
-//        internal void onEnd(TrackEntry entry) { if (End != NULL) End(entry); }
-//        internal void onDispose(TrackEntry entry) { if (Dispose != NULL) Dispose(entry); }
-//        internal void onComplete(TrackEntry entry) { if (Complete != NULL) Complete(entry); }
-//        internal void onEvent(TrackEntry entry, Event e) { if (Event != NULL) Event(entry, e); }
+        }
     };
 }
 

+ 1 - 0
spine-cpp/spine-cpp/include/spine/Extension.h

@@ -33,6 +33,7 @@
 
 /* All allocation uses these. */
 #define MALLOC(TYPE,COUNT) ((TYPE*)spineAlloc(sizeof(TYPE) * (COUNT), __FILE__, __LINE__))
+#define NEW(TYPE) ((TYPE*)spineAlloc(sizeof(TYPE), __FILE__, __LINE__))
 #define REALLOC(PTR,TYPE,COUNT) ((TYPE*)spineRealloc(PTR, sizeof(TYPE) * (COUNT), __FILE__, __LINE__))
 
 /* Frees memory. Can be used on const types. */

+ 1 - 1
spine-cpp/spine-cpp/include/spine/HashMap.h

@@ -149,7 +149,7 @@ namespace Spine
             
             size_t index = hash(key);
             
-            Entry* entry = MALLOC(Entry, 1);
+            Entry* entry = NEW(Entry);
             new (entry) Entry();
             entry->_key = key;
             entry->_value = value;

+ 1 - 1
spine-cpp/spine-cpp/include/spine/Pool.h

@@ -62,7 +62,7 @@ namespace Spine
             }
             else
             {
-                T* ret = MALLOC(T, 1);
+                T* ret = NEW(T);
                 new (ret) T();
                 
                 return ret;

+ 77 - 28
spine-cpp/spine-cpp/src/spine/AnimationState.cpp

@@ -32,10 +32,14 @@
 
 #include <spine/Animation.h>
 #include <spine/Event.h>
+#include <spine/AnimationStateData.h>
+#include <spine/Skeleton.h>
+#include <spine/RotateTimeline.h>
 
 #include <spine/Timeline.h>
 
 #include <spine/MathUtil.h>
+#include <spine/ContainerUtil.h>
 
 namespace Spine
 {
@@ -163,27 +167,32 @@ namespace Spine
             {
                 _timelineData[i] = AnimationState::Subsequent;
             }
-            else if (to == NULL || !to->hasTimeline(id))
-            {
-                _timelineData[i] = AnimationState::First;
-            }
             else
             {
-                for (int ii = mixingToLast; ii >= 0; --ii)
+                propertyIDs.push_back(id);
+                
+                if (to == NULL || !to->hasTimeline(id))
+                {
+                    _timelineData[i] = AnimationState::First;
+                }
+                else
                 {
-                    TrackEntry* entry = mixingToArray[ii];
-                    if (!entry->hasTimeline(id))
+                    for (int ii = mixingToLast; ii >= 0; --ii)
                     {
-                        if (entry->_mixDuration > 0)
+                        TrackEntry* entry = mixingToArray[ii];
+                        if (!entry->hasTimeline(id))
                         {
-                            _timelineData[i] = AnimationState::DipMix;
-                            _timelineDipMix[i] = entry;
-                            goto continue_outer; // continue outer;
+                            if (entry->_mixDuration > 0)
+                            {
+                                _timelineData[i] = AnimationState::DipMix;
+                                _timelineDipMix[i] = entry;
+                                goto continue_outer; // continue outer;
+                            }
+                            break;
                         }
-                        break;
                     }
+                    _timelineData[i] = AnimationState::Dip;
                 }
-                _timelineData[i] = AnimationState::Dip;
             }
         continue_outer: {}
         }
@@ -217,41 +226,70 @@ namespace Spine
         _onAnimationEventFunc = NULL;
     }
     
-    EventQueue::EventQueue(AnimationState& state, Pool<TrackEntry>& trackEntryPool) : _state(state), _trackEntryPool(trackEntryPool)
+    EventQueueEntry::EventQueueEntry(EventType eventType, TrackEntry* trackEntry, Event* event) :
+    _type(eventType),
+    _entry(trackEntry),
+    _event(event)
     {
         // Empty
     }
     
+    EventQueue* EventQueue::newEventQueue(AnimationState& state, Pool<TrackEntry>& trackEntryPool)
+    {
+        EventQueue* ret = NEW(EventQueue);
+        new (ret) EventQueue(state, trackEntryPool);
+        
+        return ret;
+    }
+    
+    EventQueueEntry* EventQueue::newEventQueueEntry(EventType eventType, TrackEntry* entry, Event* event)
+    {
+        EventQueueEntry* ret = NEW(EventQueueEntry);
+        new (ret) EventQueueEntry(eventType, entry, event);
+        
+        return ret;
+    }
+    
+    EventQueue::EventQueue(AnimationState& state, Pool<TrackEntry>& trackEntryPool) : _state(state), _trackEntryPool(trackEntryPool), _drainDisabled(false)
+    {
+        // Empty
+    }
+    
+    EventQueue::~EventQueue()
+    {
+        ContainerUtil::cleanUpVectorOfPointers(_eventQueueEntries);
+    }
+    
     void EventQueue::start(TrackEntry* entry)
     {
-        _eventQueueEntries.push_back(new EventQueueEntry(EventType_Start, entry));
+        _eventQueueEntries.push_back(newEventQueueEntry(EventType_Start, entry));
         _state._animationsChanged = true;
     }
     
     void EventQueue::interrupt(TrackEntry* entry)
     {
-        _eventQueueEntries.push_back(new EventQueueEntry(EventType_Interrupt, entry));
+        _eventQueueEntries.push_back(newEventQueueEntry(EventType_Interrupt, entry));
     }
     
     void EventQueue::end(TrackEntry* entry)
     {
-        _eventQueueEntries.push_back(new EventQueueEntry(EventType_End, entry));
+        _eventQueueEntries.push_back(newEventQueueEntry(EventType_End, entry));
         _state._animationsChanged = true;
     }
     
     void EventQueue::dispose(TrackEntry* entry)
     {
-        _eventQueueEntries.push_back(new EventQueueEntry(EventType_Dispose, entry));
+        _eventQueueEntries.push_back(newEventQueueEntry(EventType_Dispose, entry));
     }
     
     void EventQueue::complete(TrackEntry* entry)
     {
-        _eventQueueEntries.push_back(new EventQueueEntry(EventType_Complete, entry));
+        _eventQueueEntries.push_back(newEventQueueEntry(EventType_Complete, entry));
     }
     
-    void EventQueue::event(TrackEntry* entry, Event* e)
+    void EventQueue::event(TrackEntry* entry, Event* event)
     {
-        _eventQueueEntries.push_back(new EventQueueEntry(EventType_Event, entry, e));
+        _eventQueueEntries.push_back(newEventQueueEntry(EventType_Event, entry, event));
     }
     
     /// Raises all events in the queue and drains the queue.
@@ -266,7 +304,7 @@ namespace Spine
         
         AnimationState& state = _state;
         
-        // Don't cache entries.size() so callbacks can queue their own events (eg, call setAnimation in AnimationState_Complete).
+        // Don't cache _eventQueueEntries.size() so callbacks can queue their own events (eg, call setAnimation in AnimationState_Complete).
         for (int i = 0; i < _eventQueueEntries.size(); ++i)
         {
             EventQueueEntry* queueEntry = _eventQueueEntries[i];
@@ -300,23 +338,34 @@ namespace Spine
         _drainDisabled = false;
     }
     
-    void EventQueue::clear()
-    {
-        _eventQueueEntries.clear();
-    }
-    
     const int AnimationState::Subsequent = 0;
     const int AnimationState::First = 1;
     const int AnimationState::Dip = 2;
     const int AnimationState::DipMix = 3;
     
-    AnimationState::AnimationState() : _onAnimationEventFunc(dummyOnAnimationEventFunc)
+    AnimationState::AnimationState(AnimationStateData& data) :
+    _data(data),
+    _queue(EventQueue::newEventQueue(*this, _trackEntryPool)),
+    _onAnimationEventFunc(dummyOnAnimationEventFunc),
+    _timeScale(1)
     {
         // Empty
     }
     
+    AnimationState::~AnimationState()
+    {
+        DESTROY(EventQueue, _queue);
+    }
+    
     void AnimationState::setOnAnimationEventFunc(OnAnimationEventFunc inValue)
     {
         _onAnimationEventFunc = inValue;
     }
+    
+    Animation& AnimationState::getEmptyAnimation()
+    {
+        static Vector<Timeline*> timelines;
+        static Animation ret(std::string("<empty>"), timelines, 0);
+        return ret;
+    }
 }

+ 2 - 2
spine-cpp/spine-cpp/src/spine/Atlas.cpp

@@ -138,7 +138,7 @@ namespace Spine
                 }
                 strcpy(path + dirLength + needsSlash, name);
 
-                AtlasPage* page = MALLOC(AtlasPage, 1);
+                AtlasPage* page = NEW(AtlasPage);
                 new (page) AtlasPage(std::string(name));
 
                 FREE(name);
@@ -188,7 +188,7 @@ namespace Spine
             }
             else
             {
-                AtlasRegion* region = MALLOC(AtlasRegion, 1);
+                AtlasRegion* region = NEW(AtlasRegion);
                 new (region) AtlasRegion();
 
                 region->page = page;

+ 6 - 6
spine-cpp/spine-cpp/src/spine/AtlasAttachmentLoader.cpp

@@ -56,7 +56,7 @@ namespace Spine
         
         AtlasRegion& region = *regionP;
         
-        RegionAttachment* attachmentP = MALLOC(RegionAttachment, 1);
+        RegionAttachment* attachmentP = NEW(RegionAttachment);
         new (attachmentP) RegionAttachment(name);
         
         RegionAttachment& attachment = *attachmentP;
@@ -79,7 +79,7 @@ namespace Spine
         
         AtlasRegion& region = *regionP;
         
-        MeshAttachment* attachmentP = MALLOC(MeshAttachment, 1);
+        MeshAttachment* attachmentP = NEW(MeshAttachment);
         new (attachmentP) MeshAttachment(name);
         
         MeshAttachment& attachment = *attachmentP;
@@ -101,7 +101,7 @@ namespace Spine
     
     BoundingBoxAttachment* AtlasAttachmentLoader::newBoundingBoxAttachment(Skin& skin, std::string name)
     {
-        BoundingBoxAttachment* attachmentP = MALLOC(BoundingBoxAttachment, 1);
+        BoundingBoxAttachment* attachmentP = NEW(BoundingBoxAttachment);
         new (attachmentP) BoundingBoxAttachment(name);
         
         return attachmentP;
@@ -109,7 +109,7 @@ namespace Spine
     
     PathAttachment* AtlasAttachmentLoader::newPathAttachment(Skin& skin, std::string name)
     {
-        PathAttachment* attachmentP = MALLOC(PathAttachment, 1);
+        PathAttachment* attachmentP = NEW(PathAttachment);
         new (attachmentP) PathAttachment(name);
         
         return attachmentP;
@@ -117,7 +117,7 @@ namespace Spine
     
     PointAttachment* AtlasAttachmentLoader::newPointAttachment(Skin& skin, std::string name)
     {
-        PointAttachment* attachmentP = MALLOC(PointAttachment, 1);
+        PointAttachment* attachmentP = NEW(PointAttachment);
         new (attachmentP) PointAttachment(name);
         
         return attachmentP;
@@ -125,7 +125,7 @@ namespace Spine
     
     ClippingAttachment* AtlasAttachmentLoader::newClippingAttachment(Skin& skin, std::string name)
     {
-        ClippingAttachment* attachmentP = MALLOC(ClippingAttachment, 1);
+        ClippingAttachment* attachmentP = NEW(ClippingAttachment);
         new (attachmentP) ClippingAttachment(name);
         
         return attachmentP;

+ 6 - 6
spine-cpp/spine-cpp/src/spine/Skeleton.cpp

@@ -75,13 +75,13 @@ namespace Spine
             Bone* bone;
             if (data->getParent() == NULL)
             {
-                bone = MALLOC(Bone, 1);
+                bone = NEW(Bone);
                 new (bone) Bone(*data, *this, NULL);
             }
             else
             {
                 Bone* parent = _bones[data->getParent()->getIndex()];
-                bone = MALLOC(Bone, 1);
+                bone = NEW(Bone);
                 new (bone) Bone(*data, *this, parent);
                 parent->getChildren().push_back(bone);
             }
@@ -96,7 +96,7 @@ namespace Spine
             SlotData* data = (*i);
             
             Bone* bone = _bones[data->getBoneData().getIndex()];
-            Slot* slot = MALLOC(Slot, 1);
+            Slot* slot = NEW(Slot);
             new (slot) Slot(*data, *bone);
             
             _slots.push_back(slot);
@@ -108,7 +108,7 @@ namespace Spine
         {
             IkConstraintData* data = (*i);
             
-            IkConstraint* constraint = MALLOC(IkConstraint, 1);
+            IkConstraint* constraint = NEW(IkConstraint);
             new (constraint) IkConstraint(*data, *this);
             
             _ikConstraints.push_back(constraint);
@@ -119,7 +119,7 @@ namespace Spine
         {
             TransformConstraintData* data = (*i);
             
-            TransformConstraint* constraint = MALLOC(TransformConstraint, 1);
+            TransformConstraint* constraint = NEW(TransformConstraint);
             new (constraint) TransformConstraint(*data, *this);
             
             _transformConstraints.push_back(constraint);
@@ -130,7 +130,7 @@ namespace Spine
         {
             PathConstraintData* data = (*i);
             
-            PathConstraint* constraint = MALLOC(PathConstraint, 1);
+            PathConstraint* constraint = NEW(PathConstraint);
             new (constraint) PathConstraint(*data, *this);
             
             _pathConstraints.push_back(constraint);

+ 1 - 1
spine-cpp/spine-cpp/src/spine/SkeletonBounds.cpp

@@ -76,7 +76,7 @@ namespace Spine
             }
             else
             {
-                Polygon* polygonP = MALLOC(Polygon, 1);
+                Polygon* polygonP = NEW(Polygon);
                 new (polygonP) Polygon();
             }