From e8b489f765f7ee70f0483f4b7865dd17c5fb072f Mon Sep 17 00:00:00 2001 From: badlogic Date: Thu, 27 Oct 2016 15:54:41 +0200 Subject: [PATCH] [as3] Ported AnimationState changes to AS3 --- spine-as3/spine-as3/src/spine/MathUtils.as | 4 + spine-as3/spine-as3/src/spine/Pool.as | 60 ++ spine-as3/spine-as3/src/spine/Poolable.as | 5 + .../src/spine/animation/Animation.as | 20 +- .../src/spine/animation/AnimationState.as | 666 +++++++++++++----- .../src/spine/animation/AttachmentTimeline.as | 16 +- .../src/spine/animation/ColorTimeline.as | 49 +- .../src/spine/animation/CurveTimeline.as | 6 +- .../src/spine/animation/DeformTimeline.as | 83 ++- .../src/spine/animation/DrawOrderTimeline.as | 12 +- .../src/spine/animation/EventQueue.as | 122 ++++ .../src/spine/animation/EventTimeline.as | 8 +- .../src/spine/animation/EventType.as | 41 ++ .../spine/animation/IkConstraintTimeline.as | 25 +- .../animation/PathConstraintMixTimeline.as | 41 +- .../PathConstraintPositionTimeline.as | 34 +- .../PathConstraintSpacingTimeline.as | 33 +- .../src/spine/animation/RotateTimeline.as | 56 +- .../src/spine/animation/ScaleTimeline.as | 64 +- .../src/spine/animation/ShearTimeline.as | 47 +- .../spine-as3/src/spine/animation/Timeline.as | 4 +- .../src/spine/animation/TimelineType.as | 56 ++ .../src/spine/animation/TrackEntry.as | 49 +- .../animation/TransformConstraintTimeline.as | 62 +- .../src/spine/animation/TranslateTimeline.as | 46 +- 25 files changed, 1229 insertions(+), 380 deletions(-) create mode 100644 spine-as3/spine-as3/src/spine/Pool.as create mode 100644 spine-as3/spine-as3/src/spine/Poolable.as create mode 100644 spine-as3/spine-as3/src/spine/animation/EventQueue.as create mode 100644 spine-as3/spine-as3/src/spine/animation/EventType.as create mode 100644 spine-as3/spine-as3/src/spine/animation/TimelineType.as diff --git a/spine-as3/spine-as3/src/spine/MathUtils.as b/spine-as3/spine-as3/src/spine/MathUtils.as index eea1da167..bf05ba406 100644 --- a/spine-as3/spine-as3/src/spine/MathUtils.as +++ b/spine-as3/spine-as3/src/spine/MathUtils.as @@ -47,6 +47,10 @@ public class MathUtils { if (value > max) return max; return value; } + + static public function signum (value: Number):Number { + return value > 0 ? 1 : value < 0 ? -1 : 0; + } } } diff --git a/spine-as3/spine-as3/src/spine/Pool.as b/spine-as3/spine-as3/src/spine/Pool.as new file mode 100644 index 000000000..8ac2af62f --- /dev/null +++ b/spine-as3/spine-as3/src/spine/Pool.as @@ -0,0 +1,60 @@ +/****************************************************************************** + * Spine Runtimes Software License v2.5 + * + * Copyright (c) 2013-2016, Esoteric Software + * All rights reserved. + * + * You are granted a perpetual, non-exclusive, non-sublicensable, and + * non-transferable license to use, install, execute, and perform the Spine + * Runtimes software and derivative works solely for personal or internal + * use. Without the written permission of Esoteric Software (see Section 2 of + * the Spine Software License Agreement), you may not (a) modify, translate, + * adapt, or develop new applications using the Spine Runtimes or otherwise + * create derivative works or improvements of the Spine Runtimes or (b) remove, + * delete, alter, or obscure any trademarks or any copyright, trademark, patent, + * or other intellectual property or proprietary rights notices on or in the + * Software, including any copy thereof. Redistributions in binary or source + * form must include this license and terms. + * + * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 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. + *****************************************************************************/ + +package spine { + +public class Pool { + internal var items:Vector. = new Vector.(); + internal var instantiator:Function; + + public function Pool(instantiator:Function) { + this.instantiator = instantiator; + } + + public function obtain (): Object { + return this.items.length > 0 ? this.items.pop() : this.instantiator(); + } + + public function free (item:Object):void { + if (item is Poolable) Poolable(item).reset(); + items.push(item); + } + + public function freeAll (items:Vector):void { + for (var i:int = 0; i < items.length; i++) { + free(items[i]); + } + } + + public function clear ():void { + items.length = 0; + } +} +} \ No newline at end of file diff --git a/spine-as3/spine-as3/src/spine/Poolable.as b/spine-as3/spine-as3/src/spine/Poolable.as new file mode 100644 index 000000000..9abbb3325 --- /dev/null +++ b/spine-as3/spine-as3/src/spine/Poolable.as @@ -0,0 +1,5 @@ +package spine { +public interface Poolable { + function reset ():void; +} +} \ No newline at end of file diff --git a/spine-as3/spine-as3/src/spine/animation/Animation.as b/spine-as3/spine-as3/src/spine/animation/Animation.as index 03bbe806c..db3d7cd98 100644 --- a/spine-as3/spine-as3/src/spine/animation/Animation.as +++ b/spine-as3/spine-as3/src/spine/animation/Animation.as @@ -34,7 +34,7 @@ import spine.Skeleton; public class Animation { internal var _name:String; - private var _timelines:Vector.; + public var _timelines:Vector.; public var duration:Number; public function Animation (name:String, timelines:Vector., duration:Number) { @@ -50,7 +50,7 @@ public class Animation { } /** Poses the skeleton at the specified time for this animation. */ - public function apply (skeleton:Skeleton, lastTime:Number, time:Number, loop:Boolean, events:Vector.) : void { + public function apply (skeleton:Skeleton, lastTime:Number, time:Number, loop:Boolean, events:Vector., alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void { if (skeleton == null) throw new ArgumentError("skeleton cannot be null."); if (loop && duration != 0) { @@ -59,21 +59,7 @@ public class Animation { } for (var i:int = 0, n:int = timelines.length; i < n; i++) - timelines[i].apply(skeleton, lastTime, time, events, 1); - } - - /** Poses the skeleton at the specified time for this animation mixed with the current pose. - * @param alpha The amount of this animation that affects the current pose. */ - public function mix (skeleton:Skeleton, lastTime:Number, time:Number, loop:Boolean, events:Vector., alpha:Number) : void { - if (skeleton == null) throw new ArgumentError("skeleton cannot be null."); - - if (loop && duration != 0) { - time %= duration; - if (lastTime > 0) lastTime %= duration; - } - - for (var i:int = 0, n:int = timelines.length; i < n; i++) - timelines[i].apply(skeleton, lastTime, time, events, alpha); + timelines[i].apply(skeleton, lastTime, time, events, alpha, setupPose, mixingOut); } public function get name () : String { diff --git a/spine-as3/spine-as3/src/spine/animation/AnimationState.as b/spine-as3/spine-as3/src/spine/animation/AnimationState.as index bf8996056..531e88be3 100644 --- a/spine-as3/spine-as3/src/spine/animation/AnimationState.as +++ b/spine-as3/spine-as3/src/spine/animation/AnimationState.as @@ -29,213 +29,557 @@ *****************************************************************************/ package spine.animation { - +import spine.MathUtils; +import spine.Bone; +import spine.Pool; +import flash.utils.Dictionary; import spine.Event; import spine.Skeleton; public class AnimationState { - private var _data:AnimationStateData; - private var _tracks:Vector. = new Vector.(); - private var _events:Vector. = new Vector.(); + internal static var emptyAnimation:Animation = new Animation("", new Vector.(), 0); + + public var data:AnimationStateData; + public var tracks:Vector. = new Vector.(); + internal var events:Vector. = new Vector.(); public var onStart:Listeners = new Listeners(); + public var onInterrupt:Listeners = new Listeners(); public var onEnd:Listeners = new Listeners(); + public var onDispose:Listeners = new Listeners(); public var onComplete:Listeners = new Listeners(); public var onEvent:Listeners = new Listeners(); + internal var queue:EventQueue; + internal var propertyIDs:Dictionary = new Dictionary(); + internal var animationsChanged:Boolean; public var timeScale:Number = 1; - - public function AnimationState (data:AnimationStateData) { - if (!data) throw new ArgumentError("data cannot be null."); - _data = data; + internal var trackEntryPool:Pool; + + public function AnimationState(data:AnimationStateData) { + if (data == null) throw new ArgumentError("data can not be null"); + this.data = data; + this.queue = new EventQueue(this); + this.trackEntryPool = new Pool(function():Object { + return new TrackEntry(); + }); } - - public function update (delta:Number) : void { + + public function update (delta:Number):void { delta *= timeScale; - for (var i:int = 0; i < _tracks.length; i++) { - var current:TrackEntry = _tracks[i]; - if (!current) continue; + for (var i:int = 0, n:int = tracks.length; i < n; i++) { + var current:TrackEntry = tracks[i]; + if (current == null) continue; - current.time += delta * current.timeScale; - if (current.previous) { - var previousDelta:Number = delta * current.previous.timeScale; - current.previous.time += previousDelta; - current.mixTime += previousDelta; + current.animationLast = current.nextAnimationLast; + current.trackLast = current.nextTrackLast; + + var currentDelta:Number = delta * current.timeScale; + + if (current.delay > 0) { + current.delay -= currentDelta; + if (current.delay > 0) continue; + currentDelta = -current.delay; + current.delay = 0; } var next:TrackEntry = current.next; - if (next) { - next.time = current.lastTime - next.delay; - if (next.time >= 0) setCurrent(i, next); - } else { - // End non-looping animation when it reaches its end time and there is no next entry. - if (!current.loop && current.lastTime >= current.endTime) clearTrack(i); - } - } - } - - public function apply (skeleton:Skeleton) : void { - for (var i:int = 0; i < _tracks.length; i++) { - var current:TrackEntry = _tracks[i]; - if (!current) continue; - - _events.length = 0; - - var time:Number = current.time; - var lastTime:Number = current.lastTime; - var endTime:Number = current.endTime; - var loop:Boolean = current.loop; - if (!loop && time > endTime) time = endTime; - - var previous:TrackEntry = current.previous; - if (!previous) { - if (current.mix == 1) - current.animation.apply(skeleton, current.lastTime, time, loop, _events); - else - current.animation.mix(skeleton, current.lastTime, time, loop, _events, current.mix); - } else { - var previousTime:Number = previous.time; - if (!previous.loop && previousTime > previous.endTime) previousTime = previous.endTime; - previous.animation.apply(skeleton, previousTime, previousTime, previous.loop, null); - - var alpha:Number = current.mixTime / current.mixDuration * current.mix; - if (alpha >= 1) { - alpha = 1; - current.previous = null; + if (next != null) { + // When the next entry's delay is passed, change to the next entry, preserving leftover time. + var nextTime:Number = current.trackLast - next.delay; + if (nextTime >= 0) { + next.delay = 0; + next.trackTime = nextTime + delta * next.timeScale; + current.trackTime += currentDelta; + setCurrent(i, next); + while (next.mixingFrom != null) { + next.mixTime += currentDelta; + next = next.mixingFrom; + } + continue; + } + updateMixingFrom(current, delta, true); + } else { + updateMixingFrom(current, delta, true); + // 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 == null) { + tracks[i] = null; + queue.end(current); + disposeNext(current); + continue; } - current.animation.mix(skeleton, current.lastTime, time, loop, _events, alpha); } - for each (var event:Event in _events) { - if (current.onEvent != null) current.onEvent(i, event); - onEvent.invoke(i, event); - } - - // Check if completed the animation or a loop iteration. - if (loop ? (lastTime % endTime > time % endTime) : (lastTime < endTime && time >= endTime)) { - var count:int = (int)(time / endTime); - if (current.onComplete != null) current.onComplete(i, count); - onComplete.invoke(i, count); - } - - current.lastTime = current.time; + current.trackTime += currentDelta; } - } - public function clearTracks () : void { - for (var i:int = 0, n:int = _tracks.length; i < n; i++) + queue.drain(); + } + + private function updateMixingFrom (entry:TrackEntry, delta:Number, canEnd:Boolean):void { + var from:TrackEntry = entry.mixingFrom; + if (from == null) return; + + if (canEnd && entry.mixTime >= entry.mixDuration && entry.mixTime > 0) { + queue.end(from); + var newFrom:TrackEntry = from.mixingFrom; + entry.mixingFrom = newFrom; + if (newFrom == null) return; + entry.mixTime = from.mixTime; + entry.mixDuration = from.mixDuration; + from = newFrom; + } + + from.animationLast = from.nextAnimationLast; + from.trackLast = from.nextTrackLast; + var mixingFromDelta:Number = delta * from.timeScale; + from.trackTime += mixingFromDelta; + entry.mixTime += mixingFromDelta; + + updateMixingFrom(from, delta, canEnd && from.alpha == 1); + } + + public function apply (skeleton:Skeleton):void { + if (skeleton == null) throw new ArgumentError("skeleton cannot be null."); + if (animationsChanged) _animationsChanged(); + + var events:Vector. = this.events; + + for (var i:int = 0, n:int = tracks.length; i < n; i++) { + var current:TrackEntry = tracks[i]; + if (current == null || current.delay > 0) continue; + + // Apply mixing from entries first. + var mix:Number = current.alpha; + if (current.mixingFrom != null) mix = applyMixingFrom(current, skeleton, mix); + + // Apply current entry. + var animationLast:Number = current.animationLast, animationTime:Number = current.getAnimationTime(); + var timelineCount:int = current.animation.timelines.length; + var timelines:Vector. = current.animation.timelines; + var ii:int = 0; + if (mix == 1) { + for (ii = 0; ii < timelineCount; ii++) + Timeline(timelines[ii]).apply(skeleton, animationLast, animationTime, events, 1, true, false); + } else { + var firstFrame:Boolean = current.timelinesRotation.length == 0; + if (firstFrame) current.timelinesRotation.length = timelineCount << 1; + var timelinesRotation:Vector. = current.timelinesRotation; + + var timelinesFirst:Vector. = current.timelinesFirst; + for (ii = 0; ii < timelineCount; ii++) { + var timeline:Timeline = timelines[ii]; + if (timeline is RotateTimeline) { + applyRotateTimeline(timeline, skeleton, animationTime, mix, timelinesFirst[ii], timelinesRotation, ii << 1, + firstFrame); + } else + timeline.apply(skeleton, animationLast, animationTime, events, mix, timelinesFirst[ii], false); + } + } + queueEvents(current, animationTime); + current.nextAnimationLast = animationTime; + current.nextTrackLast = current.trackTime; + } + + queue.drain(); + } + + private function applyMixingFrom (entry:TrackEntry, skeleton:Skeleton, alpha:Number):Number { + var from:TrackEntry = entry.mixingFrom; + if (from.mixingFrom != null) applyMixingFrom(from, skeleton, alpha); + + var mix:Number = 0; + if (entry.mixDuration == 0) // Single frame mix to undo mixingFrom changes. + mix = 1; + else { + mix = entry.mixTime / entry.mixDuration; + if (mix > 1) mix = 1; + mix *= alpha; + } + + var events:Vector. = mix < from.eventThreshold ? this.events : null; + var attachments:Boolean = mix < from.attachmentThreshold, drawOrder:Boolean = mix < from.drawOrderThreshold; + var animationLast:Number = from.animationLast, animationTime:Number = from.getAnimationTime(); + alpha = from.alpha * (1 - mix); + var timelineCount:int = from.animation.timelines.length; + var timelines:Vector. = from.animation.timelines; + var timelinesFirst:Vector. = from.timelinesFirst; + + var firstFrame:Boolean = from.timelinesRotation.length == 0; + if (firstFrame) from.timelinesRotation.length = timelineCount << 1; + var timelinesRotation:Vector. = from.timelinesRotation; + + for (var i:int = 0; i < timelineCount; i++) { + var timeline:Timeline = timelines[i]; + var setupPose:Boolean = timelinesFirst[i]; + if (timeline is RotateTimeline) + applyRotateTimeline(timeline, skeleton, animationTime, alpha, setupPose, timelinesRotation, i << 1, firstFrame); + else { + if (!setupPose) { + if (!attachments && timeline is AttachmentTimeline) continue; + if (!drawOrder && timeline is DrawOrderTimeline) continue; + } + timeline.apply(skeleton, animationLast, animationTime, events, alpha, setupPose, true); + } + } + + queueEvents(from, animationTime); + from.nextAnimationLast = animationTime; + from.nextTrackLast = from.trackTime; + + return mix; + } + + private function applyRotateTimeline (timeline:Timeline, skeleton:Skeleton, time:Number, alpha:Number, setupPose:Boolean, + timelinesRotation:Vector., i:int, firstFrame:Boolean):void { + if (alpha == 1) { + timeline.apply(skeleton, 0, time, null, 1, setupPose, false); + return; + } + + var rotateTimeline:RotateTimeline = RotateTimeline(timeline); + var frames:Vector. = rotateTimeline.frames; + if (time < frames[0]) return; // Time is before first frame. + + var bone:Bone = skeleton.bones[rotateTimeline.boneIndex]; + + var r2:Number; + if (time >= frames[frames.length - RotateTimeline.ENTRIES]) // Time is after last frame. + r2 = bone.data.rotation + frames[frames.length + RotateTimeline.PREV_ROTATION]; + else { + // Interpolate between the previous frame and the current frame. + var frame:int = Animation.binarySearch(frames, time, RotateTimeline.ENTRIES); + var prevRotation:Number = frames[frame + RotateTimeline.PREV_ROTATION]; + var frameTime:Number = frames[frame]; + var percent:Number = 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. + var r1:Number = setupPose ? bone.data.rotation : bone.rotation; + var total:Number, diff:Number = 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; + var lastTotal:Number, lastDiff:Number; + if (firstFrame) { + lastTotal = 0; + lastDiff = diff; + } else { + lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops. + lastDiff = timelinesRotation[i + 1]; // Difference between bones. + } + var current:Boolean = diff > 0, dir:Boolean = lastTotal >= 0; + // Detect cross at 0 (not 180). + if (MathUtils.signum(lastDiff) != MathUtils.signum(diff) && Math.abs(lastDiff) <= 90) { + // A cross after a 360 rotation is a loop. + if (Math.abs(lastTotal) > 180) lastTotal += 360 * MathUtils.signum(lastTotal); + dir = current; + } + total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal. + if (dir != current) total += 360 * MathUtils.signum(lastTotal); + timelinesRotation[i] = total; + } + timelinesRotation[i + 1] = diff; + r1 += total * alpha; + bone.rotation = r1 - (16384 - int((16384.499999999996 - r1 / 360))) * 360; + } + + private function queueEvents (entry:TrackEntry, animationTime:Number):void { + var animationStart:Number = entry.animationStart, animationEnd:Number = entry.animationEnd; + var duration:Number = animationEnd - animationStart; + var trackLastWrapped:Number = entry.trackLast % duration; + + // Queue events before complete. + var events:Vector. = this.events; + var event:Event; + var i:int = 0, n:int = events.length; + for (; i < n; i++) { + event = events[i]; + if (event.time < trackLastWrapped) break; + if (event.time > animationEnd) continue; // Discard events outside animation start/end. + queue.event(entry, event); + } + + // Queue complete if completed a loop iteration or the animation. + if (entry.loop ? (trackLastWrapped > entry.trackTime % duration) + : (animationTime >= animationEnd && entry.animationLast < animationEnd)) { + queue.complete(entry); + } + + // Queue events after complete. + for (; i < n; i++) { + event = events[i]; + if (event.time < animationStart) continue; // Discard events outside animation start/end. + queue.event(entry, events[i]); + } + events.length = 0; + } + + public function clearTracks ():void { + queue.drainDisabled = true; + for (var i:int = 0, n:int = tracks.length; i < n; i++) clearTrack(i); - _tracks.length = 0; + tracks.length = 0; + queue.drainDisabled = false; + queue.drain(); } + + public function clearTrack (trackIndex:int):void { + if (trackIndex >= tracks.length) return; + var current:TrackEntry = tracks[trackIndex]; + if (current == null) return; - public function clearTrack (trackIndex:int) : void { - if (trackIndex >= _tracks.length) return; - var current:TrackEntry = _tracks[trackIndex]; - if (!current) return; + queue.end(current); - if (current.onEnd != null) current.onEnd(trackIndex); - onEnd.invoke(trackIndex); + disposeNext(current); - _tracks[trackIndex] = null; - } - - private function expandToIndex (index:int) : TrackEntry { - if (index < _tracks.length) return _tracks[index]; - while (index >= _tracks.length) - _tracks[_tracks.length] = null; - return null; - } - - private function setCurrent (index:int, entry:TrackEntry) : void { - var current:TrackEntry = expandToIndex(index); - if (current) { - var previous:TrackEntry = current.previous; - current.previous = null; - - if (current.onEnd != null) current.onEnd(index); - onEnd.invoke(index); - - entry.mixDuration = _data.getMix(current.animation, entry.animation); - if (entry.mixDuration > 0) { - entry.mixTime = 0; - // If a mix is in progress, mix from the closest animation. - if (previous != null && current.mixTime / current.mixDuration < 0.5) { - entry.previous = previous; - previous = current; - } else - entry.previous = current; - } + var entry:TrackEntry = current; + while (true) { + var from:TrackEntry = entry.mixingFrom; + if (from == null) break; + queue.end(from); + entry.mixingFrom = null; + entry = from; } - _tracks[index] = entry; + tracks[current.trackIndex] = null; - if (entry.onStart != null) entry.onStart(index); - onStart.invoke(index); + queue.drain(); } + + + private function setCurrent (index:int, current:TrackEntry):void { + var from:TrackEntry = expandToIndex(index); + tracks[index] = current; - public function setAnimationByName (trackIndex:int, animationName:String, loop:Boolean) : TrackEntry { - var animation:Animation = _data._skeletonData.findAnimation(animationName); - if (!animation) throw new ArgumentError("Animation not found: " + animationName); + if (from != null) { + queue.interrupt(from); + current.mixingFrom = from; + current.mixTime = 0; + + from.timelinesRotation.length = 0; + + // If not completely mixed in, set alpha so mixing out happens from current mix to zero. + if (from.mixingFrom != null) from.alpha *= Math.min(from.mixTime / from.mixDuration, 1); + } + + queue.start(current); + } + + public function setAnimationByName (trackIndex:int, animationName:String, loop:Boolean):TrackEntry { + var animation:Animation = data.skeletonData.findAnimation(animationName); + if (animation == null) throw new ArgumentError("Animation not found: " + animationName); return setAnimation(trackIndex, animation, loop); } - - /** Set the current animation. Any queued animations are cleared. */ - public function setAnimation (trackIndex:int, animation:Animation, loop:Boolean) : TrackEntry { - var entry:TrackEntry = new TrackEntry(); - entry.animation = animation; - entry.loop = loop; - entry.endTime = animation.duration; + + public function setAnimation (trackIndex:int, animation:Animation, loop:Boolean):TrackEntry { + if (animation == null) throw new ArgumentError("animation cannot be null."); + var current:TrackEntry = expandToIndex(trackIndex); + if (current != null) { + if (current.nextTrackLast == -1) { + // Don't mix from an entry that was never applied. + tracks[trackIndex] = null; + queue.interrupt(current); + queue.end(current); + disposeNext(current); + current = null; + } else + disposeNext(current); + } + var entry:TrackEntry = trackEntry(trackIndex, animation, loop, current); setCurrent(trackIndex, entry); + queue.drain(); return entry; } - - public function addAnimationByName (trackIndex:int, animationName:String, loop:Boolean, delay:Number) : TrackEntry { - var animation:Animation = _data._skeletonData.findAnimation(animationName); - if (!animation) throw new ArgumentError("Animation not found: " + animationName); + + public function addAnimationByName (trackIndex:int, animationName:String, loop:Boolean, delay:Number):TrackEntry { + var animation:Animation = data.skeletonData.findAnimation(animationName); + if (animation == null) throw new ArgumentError("Animation not found: " + animationName); return addAnimation(trackIndex, animation, loop, delay); } - - /** Adds an animation to be played delay seconds after the current or last queued animation. - * @param delay May be <= 0 to use duration of previous animation minus any mix duration plus the negative delay. */ - public function addAnimation (trackIndex:int, animation:Animation, loop:Boolean, delay:Number) : TrackEntry { - var entry:TrackEntry = new TrackEntry(); - entry.animation = animation; - entry.loop = loop; - entry.endTime = animation.duration; + + public function addAnimation (trackIndex:int, animation:Animation, loop:Boolean, delay:Number):TrackEntry { + if (animation == null) throw new ArgumentError("animation cannot be null."); var last:TrackEntry = expandToIndex(trackIndex); - if (last) { - while (last.next) + if (last != null) { + while (last.next != null) last = last.next; - last.next = entry; - } else - _tracks[trackIndex] = entry; - - if (delay <= 0) { - if (last) - delay += last.endTime - _data.getMix(last.animation, animation); - else - delay = 0; } - entry.delay = delay; + var entry:TrackEntry = trackEntry(trackIndex, animation, loop, last); + + if (last == null) { + setCurrent(trackIndex, entry); + queue.drain(); + } else { + last.next = entry; + if (delay <= 0) { + var duration:Number = 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; } - - /** May be null. */ - public function getCurrent (trackIndex:int) : TrackEntry { - if (trackIndex >= _tracks.length) return null; - return _tracks[trackIndex]; + + public function setEmptyAnimation (trackIndex:int, mixDuration:Number):TrackEntry { + var entry:TrackEntry = setAnimation(trackIndex, emptyAnimation, false); + entry.mixDuration = mixDuration; + entry.trackEnd = mixDuration; + return entry; } - - public function toString () : String { - var buffer:String = ""; - for each (var entry:TrackEntry in _tracks) { - if (!entry) continue; - if (buffer.length > 0) buffer += ", "; - buffer += entry.toString(); + + public function addEmptyAnimation (trackIndex:int, mixDuration:Number, delay:Number):TrackEntry { + if (delay <= 0) delay -= mixDuration; + var entry:TrackEntry = addAnimation(trackIndex, emptyAnimation, false, delay); + entry.mixDuration = mixDuration; + entry.trackEnd = mixDuration; + return entry; + } + + public function setEmptyAnimations (mixDuration:Number):void { + queue.drainDisabled = true; + for (var i:int = 0, n:int = tracks.length; i < n; i++) { + var current:TrackEntry = tracks[i]; + if (current != null) setEmptyAnimation(current.trackIndex, mixDuration); } - if (buffer.length == 0) return ""; - return buffer; + queue.drainDisabled = false; + queue.drain(); + } + + private function expandToIndex (index:int):TrackEntry { + if (index < tracks.length) return tracks[index]; + tracks.length = index + 1; + return null; + } + + private function trackEntry (trackIndex:int, animation:Animation, loop:Boolean, last:TrackEntry):TrackEntry { + var entry:TrackEntry = TrackEntry(trackEntryPool.obtain()); + 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 ? int.MAX_VALUE : entry.animationEnd; + entry.timeScale = 1; + + entry.alpha = 1; + entry.mixTime = 0; + entry.mixDuration = last == null ? 0 : data.getMix(last.animation, animation); + return entry; + } + + private function disposeNext (entry:TrackEntry):void{ + var next:TrackEntry = entry.next; + while (next != null) { + queue.dispose(next); + next = next.next; + } + entry.next = null; + } + + private function _animationsChanged ():void { + animationsChanged = false; + + var propertyIDs:Dictionary = this.propertyIDs = new Dictionary(); + + // Compute timelinesFirst from lowest to highest track entries. + var i:int = 0, n:int = tracks.length; + for (var key:String in propertyIDs) { + delete propertyIDs[key]; + } + var entry:TrackEntry; + for (; i < n; i++) { // Find first non-null entry. + entry = tracks[i]; + if (entry == null) continue; + setTimelinesFirst(entry); + i++; + break; + } + for (; i < n; i++) { // Rest of entries. + entry = tracks[i]; + if (entry != null) checkTimelinesFirst(entry); + } + } + + private function setTimelinesFirst (entry:TrackEntry):void { + if (entry.mixingFrom != null) { + setTimelinesFirst(entry.mixingFrom); + checkTimelinesUsage(entry, entry.timelinesFirst); + return; + } + var propertyIDs:Dictionary = this.propertyIDs; + var timelines:Vector. = entry.animation.timelines; + var n:int = timelines.length; + var usage:Vector. = entry.timelinesFirst; + usage.length = n; + for (var i:int = 0; i < n; i++) { + var id:String = timelines[i].getPropertyId().toString(); + propertyIDs[id] = id; + usage[i] = true; + } + } + + private function checkTimelinesFirst (entry:TrackEntry):void { + if (entry.mixingFrom != null) checkTimelinesFirst(entry.mixingFrom); + checkTimelinesUsage(entry, entry.timelinesFirst); + } + + private function checkTimelinesUsage (entry:TrackEntry, usageArray:Vector.):void { + var propertyIDs:Dictionary = this.propertyIDs; + var timelines:Vector. = entry.animation.timelines; + var n:int = timelines.length; + var usage:Vector. = usageArray; + usageArray.length = n; + for (var i:int = 0; i < n; i++) { + var id:String = timelines[i].getPropertyId().toString(); + usage[i] = !propertyIDs.hasOwnProperty(id); + propertyIDs[id] = id; + } + } + + public function getCurrent (trackIndex:int):TrackEntry { + if (trackIndex >= tracks.length) return null; + return tracks[trackIndex]; + } + + public function clearListeners ():void { + onStart.listeners.length = 0; + onInterrupt.listeners.length = 0; + onEnd.listeners.length = 0; + onDispose.listeners.length = 0; + onComplete.listeners.length = 0; + onEvent.listeners.length = 0; + } + + public function clearListenerNotifications ():void { + queue.clear(); } } - } diff --git a/spine-as3/spine-as3/src/spine/animation/AttachmentTimeline.as b/spine-as3/spine-as3/src/spine/animation/AttachmentTimeline.as index c1bcba8c2..1a8e3a23a 100644 --- a/spine-as3/spine-as3/src/spine/animation/AttachmentTimeline.as +++ b/spine-as3/spine-as3/src/spine/animation/AttachmentTimeline.as @@ -29,6 +29,7 @@ *****************************************************************************/ package spine.animation { +import spine.Slot; import spine.Event; import spine.Skeleton; @@ -45,6 +46,10 @@ public class AttachmentTimeline implements Timeline { public function get frameCount () : int { return frames.length; } + + public function getPropertyId () : int { + return (TimelineType.attachment.ordinal << 24) + slotIndex; + } /** Sets the time and value of the specified keyframe. */ public function setFrame (frameIndex:int, time:Number, attachmentName:String) : void { @@ -52,7 +57,14 @@ public class AttachmentTimeline implements Timeline { attachmentNames[frameIndex] = attachmentName; } - public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number) : void { + public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void { + var attachmentName:String; + if (mixingOut && setupPose) { + var slot:Slot = skeleton.slots[slotIndex]; + attachmentName = slot.data.attachmentName; + slot.attachment = attachmentName == null ? null : skeleton.getAttachmentForSlotIndex(slotIndex, attachmentName); + return; + } var frames:Vector. = this.frames; if (time < frames[0]) return; // Time is before first frame. @@ -62,7 +74,7 @@ public class AttachmentTimeline implements Timeline { else frameIndex = Animation.binarySearch(frames, time, 1) - 1; - var attachmentName:String = attachmentNames[frameIndex]; + attachmentName = attachmentNames[frameIndex]; skeleton.slots[slotIndex].attachment = attachmentName == null ? null : skeleton.getAttachmentForSlotIndex(slotIndex, attachmentName); } } diff --git a/spine-as3/spine-as3/src/spine/animation/ColorTimeline.as b/spine-as3/spine-as3/src/spine/animation/ColorTimeline.as index 5f791933f..a875cd2ae 100644 --- a/spine-as3/spine-as3/src/spine/animation/ColorTimeline.as +++ b/spine-as3/spine-as3/src/spine/animation/ColorTimeline.as @@ -45,6 +45,10 @@ public class ColorTimeline extends CurveTimeline { super(frameCount); frames = new Vector.(frameCount * 5, true); } + + override public function getPropertyId () : int { + return (TimelineType.color.ordinal << 24) + slotIndex; + } /** Sets the time and value of the specified keyframe. */ public function setFrame (frameIndex:int, time:Number, r:Number, g:Number, b:Number, a:Number) : void { @@ -56,28 +60,27 @@ public class ColorTimeline extends CurveTimeline { frames[int(frameIndex + A)] = a; } - override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number) : void { - if (time < frames[0]) - return; // Time is before first frame. + override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void { + var frames:Vector. = this.frames; + if (time < frames[0]) return; // Time is before first frame. var r:Number, g:Number, b:Number, a:Number; - if (time >= frames[int(frames.length - ENTRIES)]) { - // Time is after last frame. + if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. var i:int = frames.length; - r = frames[int(i + PREV_R)]; - g = frames[int(i + PREV_G)]; - b = frames[int(i + PREV_B)]; - a = frames[int(i + PREV_A)]; + r = frames[i + PREV_R]; + g = frames[i + PREV_G]; + b = frames[i + PREV_B]; + a = frames[i + PREV_A]; } else { // Interpolate between the previous frame and the current frame. var frame:int = Animation.binarySearch(frames, time, ENTRIES); - r = frames[int(frame + PREV_R)]; - g = frames[int(frame + PREV_G)]; - b = frames[int(frame + PREV_B)]; - a = frames[int(frame + PREV_A)]; + r = frames[frame + PREV_R]; + g = frames[frame + PREV_G]; + b = frames[frame + PREV_B]; + a = frames[frame + PREV_A]; var frameTime:Number = frames[frame]; var percent:Number = getCurvePercent(frame / ENTRIES - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); r += (frames[frame + R] - r) * percent; g += (frames[frame + G] - g) * percent; @@ -85,16 +88,22 @@ public class ColorTimeline extends CurveTimeline { a += (frames[frame + A] - a) * percent; } var slot:Slot = skeleton.slots[slotIndex]; - if (alpha < 1) { - slot.r += (r - slot.r) * alpha; - slot.g += (g - slot.g) * alpha; - slot.b += (b - slot.b) * alpha; - slot.a += (a - slot.a) * alpha; - } else { + if (alpha == 1) { slot.r = r; slot.g = g; slot.b = b; slot.a = a; + } else { + if (setupPose) { + slot.r = slot.data.r; + slot.g = slot.data.g; + slot.b = slot.data.b; + slot.a = slot.data.a; + } + slot.r += (r - slot.r) * alpha; + slot.g += (g - slot.g) * alpha; + slot.b += (b - slot.b) * alpha; + slot.a += (a - slot.a) * alpha; } } } diff --git a/spine-as3/spine-as3/src/spine/animation/CurveTimeline.as b/spine-as3/spine-as3/src/spine/animation/CurveTimeline.as index cfbef3434..13968ba0e 100644 --- a/spine-as3/spine-as3/src/spine/animation/CurveTimeline.as +++ b/spine-as3/spine-as3/src/spine/animation/CurveTimeline.as @@ -46,7 +46,11 @@ public class CurveTimeline implements Timeline { curves = new Vector.((frameCount - 1) * BEZIER_SIZE, true); } - public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number) : void { + public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void { + } + + public function getPropertyId () : int { + return 0; } public function get frameCount () : int { diff --git a/spine-as3/spine-as3/src/spine/animation/DeformTimeline.as b/spine-as3/spine-as3/src/spine/animation/DeformTimeline.as index d1dfa49e5..0cf832298 100644 --- a/spine-as3/spine-as3/src/spine/animation/DeformTimeline.as +++ b/spine-as3/spine-as3/src/spine/animation/DeformTimeline.as @@ -29,6 +29,7 @@ *****************************************************************************/ package spine.animation { +import spine.attachments.Attachment; import spine.attachments.VertexAttachment; import spine.Event; import spine.Skeleton; @@ -45,6 +46,10 @@ public class DeformTimeline extends CurveTimeline { frames = new Vector.(frameCount, true); frameVertices = new Vector.>(frameCount, true); } + + override public function getPropertyId () : int { + return (TimelineType.deform.ordinal << 24) + slotIndex; + } /** Sets the time and value of the specified keyframe. */ public function setFrame (frameIndex:int, time:Number, vertices:Vector.) : void { @@ -52,10 +57,10 @@ public class DeformTimeline extends CurveTimeline { frameVertices[frameIndex] = vertices; } - override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number) : void { + override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void { var slot:Slot = skeleton.slots[slotIndex]; - var slotAttachment:VertexAttachment = slot.attachment as VertexAttachment; - if (!slotAttachment || !slotAttachment.applyDeform(attachment)) return; + var slotAttachment:Attachment = slot.attachment; + if (!(slotAttachment is VertexAttachment) || !(VertexAttachment(slotAttachment)).applyDeform(attachment)) return; var frames:Vector. = this.frames; if (time < frames[0]) return; // Time is before first frame. @@ -63,41 +68,79 @@ public class DeformTimeline extends CurveTimeline { var frameVertices:Vector.> = this.frameVertices; var vertexCount:int = frameVertices[0].length; - var vertices:Vector. = slot.attachmentVertices; - if (vertices.length != vertexCount) alpha = 1; // Don't mix from uninitialized slot vertices. - vertices.length = vertexCount; + var verticesArray:Vector. = slot.attachmentVertices; + if (verticesArray.length != vertexCount) alpha = 1; // Don't mix from uninitialized slot vertices. + verticesArray.length = vertexCount; + var vertices:Vector. = verticesArray; - var i:int; + var i:int, n:int; + var vertexAttachment:VertexAttachment; + var setupVertices:Vector.; + var setup:Number, prev:Number; if (time >= frames[frames.length - 1]) { // Time is after last frame. - var lastVertices:Vector. = frameVertices[int(frames.length - 1)]; - if (alpha < 1) { + var lastVertices:Vector. = frameVertices[frames.length - 1]; + if (alpha == 1) { + // Vertex positions or deform offsets, no alpha. + for (i = 0, n = vertexCount; i < n; i++) + vertices[i] = lastVertices[i]; + } else if (setupPose) { + vertexAttachment = VertexAttachment(slotAttachment); + if (vertexAttachment.bones == null) { + // Unweighted vertex positions, with alpha. + setupVertices = vertexAttachment.vertices; + for (i = 0; i < vertexCount; i++) { + setup = setupVertices[i]; + vertices[i] = setup + (lastVertices[i] - setup) * alpha; + } + } else { + // Weighted deform offsets, with alpha. + for (i = 0; i < vertexCount; i++) + vertices[i] = lastVertices[i] * alpha; + } + } else { + // Vertex positions or deform offsets, with alpha. for (i = 0; i < vertexCount; i++) vertices[i] += (lastVertices[i] - vertices[i]) * alpha; - } else { - for (i = 0; i < vertexCount; i++) - vertices[i] = lastVertices[i]; } return; } // Interpolate between the previous frame and the current frame. var frame:int = Animation.binarySearch1(frames, time); - var prevVertices:Vector. = frameVertices[int(frame - 1)]; + var prevVertices:Vector. = frameVertices[frame - 1]; var nextVertices:Vector. = frameVertices[frame]; var frameTime:Number = frames[frame]; var percent:Number = getCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime)); - var prev:Number; - if (alpha < 1) { - for (i = 0; i < vertexCount; i++) { - prev = prevVertices[i]; - vertices[i] += (prev + (nextVertices[i] - prev) * percent - vertices[i]) * alpha; - } - } else { + if (alpha == 1) { + // Vertex positions or deform offsets, no alpha. for (i = 0; i < vertexCount; i++) { prev = prevVertices[i]; vertices[i] = prev + (nextVertices[i] - prev) * percent; } + } else if (setupPose) { + vertexAttachment = VertexAttachment(slotAttachment); + if (vertexAttachment.bones == null) { + // Unweighted vertex positions, with alpha. + setupVertices = vertexAttachment.vertices; + for (i = 0; i < vertexCount; i++) { + prev = prevVertices[i]; + setup = setupVertices[i]; + vertices[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha; + } + } else { + // Weighted deform offsets, with alpha. + for (i = 0; i < vertexCount; i++) { + prev = prevVertices[i]; + vertices[i] = (prev + (nextVertices[i] - prev) * percent) * alpha; + } + } + } else { + // Vertex positions or deform offsets, with alpha. + for (i = 0; i < vertexCount; i++) { + prev = prevVertices[i]; + vertices[i] += (prev + (nextVertices[i] - prev) * percent - vertices[i]) * alpha; + } } } } diff --git a/spine-as3/spine-as3/src/spine/animation/DrawOrderTimeline.as b/spine-as3/spine-as3/src/spine/animation/DrawOrderTimeline.as index 50414ed7c..620c8ce02 100644 --- a/spine-as3/spine-as3/src/spine/animation/DrawOrderTimeline.as +++ b/spine-as3/spine-as3/src/spine/animation/DrawOrderTimeline.as @@ -45,6 +45,10 @@ public class DrawOrderTimeline implements Timeline { public function get frameCount () : int { return frames.length; } + + public function getPropertyId () : int { + return TimelineType.drawOrder.ordinal << 24; + } /** Sets the time and value of the specified keyframe. */ public function setFrame (frameIndex:int, time:Number, drawOrder:Vector.) : void { @@ -52,7 +56,13 @@ public class DrawOrderTimeline implements Timeline { drawOrders[frameIndex] = drawOrder; } - public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number) : void { + public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void { + if (mixingOut && setupPose) { + for (var ii:int = 0, n:int = skeleton.slots.length; ii < n; ii++) + skeleton.drawOrder[ii] = skeleton.slots[ii]; + return; + } + if (time < frames[0]) return; // Time is before first frame. diff --git a/spine-as3/spine-as3/src/spine/animation/EventQueue.as b/spine-as3/spine-as3/src/spine/animation/EventQueue.as new file mode 100644 index 000000000..30288a6b9 --- /dev/null +++ b/spine-as3/spine-as3/src/spine/animation/EventQueue.as @@ -0,0 +1,122 @@ +/****************************************************************************** + * Spine Runtimes Software License v2.5 + * + * Copyright (c) 2013-2016, Esoteric Software + * All rights reserved. + * + * You are granted a perpetual, non-exclusive, non-sublicensable, and + * non-transferable license to use, install, execute, and perform the Spine + * Runtimes software and derivative works solely for personal or internal + * use. Without the written permission of Esoteric Software (see Section 2 of + * the Spine Software License Agreement), you may not (a) modify, translate, + * adapt, or develop new applications using the Spine Runtimes or otherwise + * create derivative works or improvements of the Spine Runtimes or (b) remove, + * delete, alter, or obscure any trademarks or any copyright, trademark, patent, + * or other intellectual property or proprietary rights notices on or in the + * Software, including any copy thereof. Redistributions in binary or source + * form must include this license and terms. + * + * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 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. + *****************************************************************************/ + +package spine.animation { +import spine.Event; + +public class EventQueue { + internal var objects:Vector. = new Vector.(); + internal var animationState:AnimationState; + public var drainDisabled:Boolean; + + public function EventQueue(animationState:AnimationState) { + this.animationState = animationState; + } + + public function start (entry:TrackEntry):void { + objects.push(EventType.start); + objects.push(entry); + animationState.animationsChanged = true; + } + + public function interrupt (entry:TrackEntry):void { + objects.push(EventType.interrupt); + objects.push(entry); + } + + public function end (entry:TrackEntry):void { + objects.push(EventType.end); + objects.push(entry); + animationState.animationsChanged = true; + } + + public function dispose (entry:TrackEntry):void { + objects.push(EventType.dispose); + objects.push(entry); + } + + public function complete (entry:TrackEntry):void { + objects.push(EventType.complete); + objects.push(entry); + } + + public function event (entry:TrackEntry, event:Event):void { + objects.push(EventType.event); + objects.push(entry); + objects.push(event); + } + + public function drain ():void { + if (drainDisabled) return; // Not reentrant. + drainDisabled = true; + + var objects:Vector. = this.objects; + for (var i:int = 0; i < objects.length; i += 2) { + var type:EventType = EventType(objects[i]); + var entry:TrackEntry = TrackEntry(objects[i + 1]); + switch (type) { + case EventType.start: + entry.onStart.invoke(entry); + animationState.onStart.invoke(entry); + break; + case EventType.interrupt: + entry.onInterrupt.invoke(entry); + animationState.onInterrupt.invoke(entry); + break; + case EventType.end: + entry.onEnd.invoke(entry); + animationState.onEnd.invoke(entry); + // Fall through. + case EventType.dispose: + entry.onDispose.invoke(entry); + animationState.onDispose.invoke(entry); + animationState.trackEntryPool.free(entry); + break; + case EventType.complete: + entry.onComplete.invoke(entry); + animationState.onComplete.invoke(entry); + break; + case EventType.event: + var event:Event = Event(objects[i++ + 2]); + entry.onEvent.invoke(entry, event); + animationState.onEvent.invoke(entry, event); + break; + } + } + clear(); + + drainDisabled = false; + } + + public function clear ():void { + objects.length = 0; + } +} +} diff --git a/spine-as3/spine-as3/src/spine/animation/EventTimeline.as b/spine-as3/spine-as3/src/spine/animation/EventTimeline.as index 9a6d6b629..9b86336d5 100644 --- a/spine-as3/spine-as3/src/spine/animation/EventTimeline.as +++ b/spine-as3/spine-as3/src/spine/animation/EventTimeline.as @@ -44,6 +44,10 @@ public class EventTimeline implements Timeline { public function get frameCount () : int { return frames.length; } + + public function getPropertyId () : int { + return TimelineType.event.ordinal << 24; + } /** Sets the time and value of the specified keyframe. */ public function setFrame (frameIndex:int, event:Event) : void { @@ -52,11 +56,11 @@ public class EventTimeline implements Timeline { } /** Fires events for frames > lastTime and <= time. */ - public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number) : void { + public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void { if (!firedEvents) return; if (lastTime > time) { // Fire events after last time for looped animations. - apply(skeleton, lastTime, int.MAX_VALUE, firedEvents, alpha); + apply(skeleton, lastTime, int.MAX_VALUE, firedEvents, alpha, setupPose, mixingOut); lastTime = -1; } else if (lastTime >= frames[int(frameCount - 1)]) // Last time is after last frame. return; diff --git a/spine-as3/spine-as3/src/spine/animation/EventType.as b/spine-as3/spine-as3/src/spine/animation/EventType.as new file mode 100644 index 000000000..02a95a61c --- /dev/null +++ b/spine-as3/spine-as3/src/spine/animation/EventType.as @@ -0,0 +1,41 @@ +/****************************************************************************** + * Spine Runtimes Software License v2.5 + * + * Copyright (c) 2013-2016, Esoteric Software + * All rights reserved. + * + * You are granted a perpetual, non-exclusive, non-sublicensable, and + * non-transferable license to use, install, execute, and perform the Spine + * Runtimes software and derivative works solely for personal or internal + * use. Without the written permission of Esoteric Software (see Section 2 of + * the Spine Software License Agreement), you may not (a) modify, translate, + * adapt, or develop new applications using the Spine Runtimes or otherwise + * create derivative works or improvements of the Spine Runtimes or (b) remove, + * delete, alter, or obscure any trademarks or any copyright, trademark, patent, + * or other intellectual property or proprietary rights notices on or in the + * Software, including any copy thereof. Redistributions in binary or source + * form must include this license and terms. + * + * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 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. + *****************************************************************************/ + +package spine.animation { + +public class EventType { + public static const start:EventType = new EventType(); + public static const interrupt:EventType = new EventType(); + public static const end:EventType = new EventType(); + public static const dispose:EventType = new EventType(); + public static const complete:EventType = new EventType(); + public static const event:EventType = new EventType(); +} +} diff --git a/spine-as3/spine-as3/src/spine/animation/IkConstraintTimeline.as b/spine-as3/spine-as3/src/spine/animation/IkConstraintTimeline.as index 9e48b02ad..3e0fbdf77 100644 --- a/spine-as3/spine-as3/src/spine/animation/IkConstraintTimeline.as +++ b/spine-as3/spine-as3/src/spine/animation/IkConstraintTimeline.as @@ -45,6 +45,10 @@ public class IkConstraintTimeline extends CurveTimeline { super(frameCount); frames = new Vector.(frameCount * ENTRIES, true); } + + override public function getPropertyId () : int { + return (TimelineType.ikConstraint.ordinal << 24) + ikConstraintIndex; + } /** Sets the time, mix and bend direction of the specified keyframe. */ public function setFrame (frameIndex:int, time:Number, mix:Number, bendDirection:int) : void { @@ -54,14 +58,20 @@ public class IkConstraintTimeline extends CurveTimeline { frames[int(frameIndex + BEND_DIRECTION)] = bendDirection; } - override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number) : void { + override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void { if (time < frames[0]) return; // Time is before first frame. var constraint:IkConstraint = skeleton.ikConstraints[ikConstraintIndex]; if (time >= frames[int(frames.length - ENTRIES)]) { // Time is after last frame. - constraint.mix += (frames[int(frames.length + PREV_MIX)] - constraint.mix) * alpha; - constraint.bendDirection = int(frames[int(frames.length + PREV_BEND_DIRECTION)]); + if (setupPose) { + constraint.mix = constraint.data.mix + (frames[frames.length + PREV_MIX] - constraint.data.mix) * alpha; + constraint.bendDirection = mixingOut ? constraint.data.bendDirection + : int(frames[frames.length + PREV_BEND_DIRECTION]); + } else { + constraint.mix += (frames[frames.length + PREV_MIX] - constraint.mix) * alpha; + if (!mixingOut) constraint.bendDirection = int(frames[frames.length + PREV_BEND_DIRECTION]); + } return; } @@ -71,8 +81,13 @@ public class IkConstraintTimeline extends CurveTimeline { var frameTime:Number = frames[frame]; var percent:Number = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha; - constraint.bendDirection = int(frames[frame + PREV_BEND_DIRECTION]); + if (setupPose) { + constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha; + constraint.bendDirection = mixingOut ? constraint.data.bendDirection : int(frames[frame + PREV_BEND_DIRECTION]); + } else { + constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha; + if (!mixingOut) constraint.bendDirection = int(frames[frame + PREV_BEND_DIRECTION]); + } } } diff --git a/spine-as3/spine-as3/src/spine/animation/PathConstraintMixTimeline.as b/spine-as3/spine-as3/src/spine/animation/PathConstraintMixTimeline.as index 020b00daf..3fc632113 100644 --- a/spine-as3/spine-as3/src/spine/animation/PathConstraintMixTimeline.as +++ b/spine-as3/spine-as3/src/spine/animation/PathConstraintMixTimeline.as @@ -46,6 +46,10 @@ public class PathConstraintMixTimeline extends CurveTimeline { super(frameCount); frames = new Vector.(frameCount * ENTRIES, true); } + + override public function getPropertyId () : int { + return (TimelineType.pathConstraintMix.ordinal << 24) + pathConstraintIndex; + } /** Sets the time and mixes of the specified keyframe. */ public function setFrame (frameIndex:int, time:Number, rotateMix:Number, translateMix:Number) : void { @@ -55,28 +59,35 @@ public class PathConstraintMixTimeline extends CurveTimeline { frames[frameIndex + TRANSLATE] = translateMix; } - override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number) : void { + override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void { if (time < frames[0]) return; // Time is before first frame. var constraint:PathConstraint = skeleton.pathConstraints[pathConstraintIndex]; + var rotate:Number, translate:Number; if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. - var i:int = frames.length; - constraint.rotateMix += (frames[i + PREV_ROTATE] - constraint.rotateMix) * alpha; - constraint.translateMix += (frames[i + PREV_TRANSLATE] - constraint.translateMix) * alpha; - return; + rotate = frames[frames.length + PREV_ROTATE]; + translate = frames[frames.length + PREV_TRANSLATE]; + } else { + // Interpolate between the previous frame and the current frame. + var frame:int = Animation.binarySearch(frames, time, ENTRIES); + rotate = frames[frame + PREV_ROTATE]; + translate = frames[frame + PREV_TRANSLATE]; + var frameTime:Number = frames[frame]; + var percent:Number = getCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + rotate += (frames[frame + ROTATE] - rotate) * percent; + translate += (frames[frame + TRANSLATE] - translate) * percent; } - // Interpolate between the previous frame and the current frame. - var frame:int = Animation.binarySearch(frames, time, ENTRIES); - var rotate:Number = frames[frame + PREV_ROTATE]; - var translate:Number = frames[frame + PREV_TRANSLATE]; - var frameTime:Number = frames[frame]; - var percent:Number = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - constraint.rotateMix += (rotate + (frames[frame + ROTATE] - rotate) * percent - constraint.rotateMix) * alpha; - constraint.translateMix += (translate + (frames[frame + TRANSLATE] - translate) * percent - constraint.translateMix) - * alpha; + if (setupPose) { + constraint.rotateMix = constraint.data.rotateMix + (rotate - constraint.data.rotateMix) * alpha; + constraint.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha; + } else { + constraint.rotateMix += (rotate - constraint.rotateMix) * alpha; + constraint.translateMix += (translate - constraint.translateMix) * alpha; + } } } } diff --git a/spine-as3/spine-as3/src/spine/animation/PathConstraintPositionTimeline.as b/spine-as3/spine-as3/src/spine/animation/PathConstraintPositionTimeline.as index d6c99331b..cb420dee5 100644 --- a/spine-as3/spine-as3/src/spine/animation/PathConstraintPositionTimeline.as +++ b/spine-as3/spine-as3/src/spine/animation/PathConstraintPositionTimeline.as @@ -46,6 +46,10 @@ public class PathConstraintPositionTimeline extends CurveTimeline { super(frameCount); frames = new Vector.(frameCount * ENTRIES, true); } + + override public function getPropertyId () : int { + return (TimelineType.pathConstraintPosition.ordinal << 24) + pathConstraintIndex; + } /** Sets the time and value of the specified keyframe. */ public function setFrame (frameIndex:int, time:Number, value:Number) : void { @@ -54,24 +58,28 @@ public class PathConstraintPositionTimeline extends CurveTimeline { frames[frameIndex + VALUE] = value; } - override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number) : void { + override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void { if (time < frames[0]) return; // Time is before first frame. var constraint:PathConstraint = skeleton.pathConstraints[pathConstraintIndex]; - if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. - var i:int = frames.length; - constraint.position += (frames[i + PREV_VALUE] - constraint.position) * alpha; - return; + var position:Number; + if (time >= frames[frames.length - ENTRIES]) // Time is after last frame. + position = frames[frames.length + PREV_VALUE]; + else { + // Interpolate between the previous frame and the current frame. + var frame:int = Animation.binarySearch(frames, time, ENTRIES); + position = frames[frame + PREV_VALUE]; + var frameTime:Number = frames[frame]; + var percent:Number = getCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + position += (frames[frame + VALUE] - position) * percent; } - - // Interpolate between the previous frame and the current frame. - var frame:int = Animation.binarySearch(frames, time, ENTRIES); - var position:Number = frames[frame + PREV_VALUE]; - var frameTime:Number = frames[frame]; - var percent:Number = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - constraint.position += (position + (frames[frame + VALUE] - position) * percent - constraint.position) * alpha; + if (setupPose) + constraint.position = constraint.data.position + (position - constraint.data.position) * alpha; + else + constraint.position += (position - constraint.position) * alpha; } } } diff --git a/spine-as3/spine-as3/src/spine/animation/PathConstraintSpacingTimeline.as b/spine-as3/spine-as3/src/spine/animation/PathConstraintSpacingTimeline.as index 76193c83e..6f7ac730b 100644 --- a/spine-as3/spine-as3/src/spine/animation/PathConstraintSpacingTimeline.as +++ b/spine-as3/spine-as3/src/spine/animation/PathConstraintSpacingTimeline.as @@ -37,25 +37,34 @@ public class PathConstraintSpacingTimeline extends PathConstraintPositionTimelin public function PathConstraintSpacingTimeline (frameCount:int) { super(frameCount); } + + override public function getPropertyId () : int { + return (TimelineType.pathConstraintSpacing.ordinal << 24) + pathConstraintIndex; + } - override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number) : void { + override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void { if (time < frames[0]) return; // Time is before first frame. var constraint:PathConstraint = skeleton.pathConstraints[pathConstraintIndex]; - if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. - var i:int = frames.length; - constraint.spacing += (frames[i + PREV_VALUE] - constraint.spacing) * alpha; - return; + var spacing:Number; + if (time >= frames[frames.length - ENTRIES]) // Time is after last frame. + spacing = frames[frames.length + PREV_VALUE]; + else { + // Interpolate between the previous frame and the current frame. + var frame:int = Animation.binarySearch(frames, time, ENTRIES); + spacing = frames[frame + PREV_VALUE]; + var frameTime:Number = frames[frame]; + var percent:Number = getCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + spacing += (frames[frame + VALUE] - spacing) * percent; } - // Interpolate between the previous frame and the current frame. - var frame:int = Animation.binarySearch(frames, time, ENTRIES); - var spacing:Number = frames[frame + PREV_VALUE]; - var frameTime:Number = frames[frame]; - var percent:Number = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - constraint.spacing += (spacing + (frames[frame + VALUE] - spacing) * percent - constraint.spacing) * alpha; + if (setupPose) + constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha; + else + constraint.spacing += (spacing - constraint.spacing) * alpha; } } } diff --git a/spine-as3/spine-as3/src/spine/animation/RotateTimeline.as b/spine-as3/spine-as3/src/spine/animation/RotateTimeline.as index f1bc55bcc..c2f615b17 100644 --- a/spine-as3/spine-as3/src/spine/animation/RotateTimeline.as +++ b/spine-as3/spine-as3/src/spine/animation/RotateTimeline.as @@ -35,8 +35,8 @@ import spine.Skeleton; public class RotateTimeline extends CurveTimeline { static public const ENTRIES:int = 2; - static private const PREV_TIME:int = -2, PREV_ROTATION:int = -1; - static private const ROTATION:int = 1; + static public const PREV_TIME:int = -2, PREV_ROTATION:int = -1; + static public const ROTATION:int = 1; public var boneIndex:int; public var frames:Vector.; // time, value, ... @@ -45,6 +45,10 @@ public class RotateTimeline extends CurveTimeline { super(frameCount); frames = new Vector.(frameCount * 2, true); } + + override public function getPropertyId () : int { + return (TimelineType.rotate.ordinal << 24) + boneIndex; + } /** Sets the time and angle of the specified keyframe. */ public function setFrame (frameIndex:int, time:Number, degrees:Number) : void { @@ -52,40 +56,42 @@ public class RotateTimeline extends CurveTimeline { frames[frameIndex] = time; frames[int(frameIndex + ROTATION)] = degrees; } - - override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number) : void { - if (time < frames[0]) - return; // Time is before first frame. + + override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void { + var frames:Vector. = this.frames; + if (time < frames[0]) return; // Time is before first frame. var bone:Bone = skeleton.bones[boneIndex]; + var r:Number; - if (time >= frames[int(frames.length - 2)]) { // Time is after last frame. - var amount:Number = bone.data.rotation + frames[int(frames.length + PREV_ROTATION)] - bone.rotation; - while (amount > 180) - amount -= 360; - while (amount < -180) - amount += 360; - bone.rotation += amount * alpha; + if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. + if (setupPose) + bone.rotation = bone.data.rotation + frames[frames.length + PREV_ROTATION] * alpha; + else { + r = bone.data.rotation + frames[frames.length + PREV_ROTATION] - bone.rotation; + r -= (16384 - int((16384.499999999996 - r / 360))) * 360; // Wrap within -180 and 180. + bone.rotation += r * alpha; + } return; } // Interpolate between the previous frame and the current frame. var frame:int = Animation.binarySearch(frames, time, ENTRIES); - var prevRotation:Number = frames[int(frame + PREV_ROTATION)]; + var prevRotation:Number = frames[frame + PREV_ROTATION]; var frameTime:Number = frames[frame]; var percent:Number = getCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - amount = frames[int(frame + ROTATION)] - prevRotation; - while (amount > 180) - amount -= 360; - while (amount < -180) - amount += 360; - amount = bone.data.rotation + (prevRotation + amount * percent) - bone.rotation; - while (amount > 180) - amount -= 360; - while (amount < -180) - amount += 360; - bone.rotation += amount * alpha; + r = frames[frame + ROTATION] - prevRotation; + r -= (16384 - int((16384.499999999996 - r / 360))) * 360; + r = prevRotation + r * percent; + if (setupPose) { + r -= (16384 - int((16384.499999999996 - r / 360))) * 360; + bone.rotation = bone.data.rotation + r * alpha; + } else { + r = bone.data.rotation + r - bone.rotation; + r -= (16384 - int((16384.499999999996 - r / 360))) * 360; + bone.rotation += r * alpha; + } } } diff --git a/spine-as3/spine-as3/src/spine/animation/ScaleTimeline.as b/spine-as3/spine-as3/src/spine/animation/ScaleTimeline.as index f65cedca6..4052f921f 100644 --- a/spine-as3/spine-as3/src/spine/animation/ScaleTimeline.as +++ b/spine-as3/spine-as3/src/spine/animation/ScaleTimeline.as @@ -29,6 +29,7 @@ *****************************************************************************/ package spine.animation { +import spine.MathUtils; import spine.Bone; import spine.Event; import spine.Skeleton; @@ -37,27 +38,56 @@ public class ScaleTimeline extends TranslateTimeline { public function ScaleTimeline (frameCount:int) { super(frameCount); } + + override public function getPropertyId () : int { + return (TimelineType.scale.ordinal << 24) + boneIndex; + } - override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number) : void { - if (time < frames[0]) - return; // Time is before first frame. + override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void { + var frames:Vector. = this.frames; + if (time < frames[0]) return; // Time is before first frame. var bone:Bone = skeleton.bones[boneIndex]; - if (time >= frames[int(frames.length - ENTRIES)]) { // Time is after last frame. - bone.scaleX += (bone.data.scaleX * frames[int(frames.length + PREV_X)] - bone.scaleX) * alpha; - bone.scaleY += (bone.data.scaleY * frames[int(frames.length + PREV_Y)] - bone.scaleY) * alpha; - return; + + var x:Number, y:Number; + if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. + x = frames[frames.length + PREV_X] * bone.data.scaleX; + y = frames[frames.length + PREV_Y] * bone.data.scaleY; + } else { + // Interpolate between the previous frame and the current frame. + var frame:int = Animation.binarySearch(frames, time, ENTRIES); + x = frames[frame + PREV_X]; + y = frames[frame + PREV_Y]; + var frameTime:Number = frames[frame]; + var percent:Number = getCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + x = (x + (frames[frame + X] - x) * percent) * bone.data.scaleX; + y = (y + (frames[frame + Y] - y) * percent) * bone.data.scaleY; + } + if (alpha == 1) { + bone.scaleX = x; + bone.scaleY = y; + } else { + var bx:Number, by:Number; + if (setupPose) { + bx = bone.data.scaleX; + by = bone.data.scaleY; + } else { + bx = bone.scaleX; + by = bone.scaleY; + } + // Mixing out uses sign of setup or current pose, else use sign of key. + if (mixingOut) { + x = Math.abs(x) * MathUtils.signum(bx); + y = Math.abs(y) * MathUtils.signum(by); + } else { + bx = Math.abs(bx) * MathUtils.signum(x); + by = Math.abs(by) * MathUtils.signum(y); + } + bone.scaleX = bx + (x - bx) * alpha; + bone.scaleY = by + (y - by) * alpha; } - - // Interpolate between the previous frame and the current frame. - var frame:int = Animation.binarySearch(frames, time, ENTRIES); - var prevX:Number = frames[frame + PREV_X]; - var prevY:Number = frames[frame + PREV_Y]; - var frameTime:Number = frames[frame]; - var percent:Number = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - bone.scaleX += (bone.data.scaleX * (prevX + (frames[frame + X] - prevX) * percent) - bone.scaleX) * alpha; - bone.scaleY += (bone.data.scaleY * (prevY + (frames[frame + Y] - prevY) * percent) - bone.scaleY) * alpha; } } diff --git a/spine-as3/spine-as3/src/spine/animation/ShearTimeline.as b/spine-as3/spine-as3/src/spine/animation/ShearTimeline.as index 7321eb746..79bbed794 100644 --- a/spine-as3/spine-as3/src/spine/animation/ShearTimeline.as +++ b/spine-as3/spine-as3/src/spine/animation/ShearTimeline.as @@ -29,35 +29,48 @@ *****************************************************************************/ package spine.animation { - import spine.Event; - import spine.Skeleton; - import spine.Bone; +import spine.Event; +import spine.Skeleton; +import spine.Bone; public class ShearTimeline extends TranslateTimeline { public function ShearTimeline (frameCount:int) { super(frameCount); } + + override public function getPropertyId () : int { + return (TimelineType.shear.ordinal << 24) + boneIndex; + } - override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number) : void { + override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void { var frames:Vector. = this.frames; if (time < frames[0]) return; // Time is before first frame. var bone:Bone = skeleton.bones[boneIndex]; + + var x:Number, y:Number; if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. - bone.shearX += (bone.data.shearX + frames[frames.length + PREV_X] - bone.shearX) * alpha; - bone.shearY += (bone.data.shearY + frames[frames.length + PREV_Y] - bone.shearY) * alpha; - return; + x = frames[frames.length + PREV_X]; + y = frames[frames.length + PREV_Y]; + } else { + // Interpolate between the previous frame and the current frame. + var frame:int = Animation.binarySearch(frames, time, ENTRIES); + x = frames[frame + PREV_X]; + y = frames[frame + PREV_Y]; + var frameTime:Number = frames[frame]; + var percent:Number = getCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + x = x + (frames[frame + X] - x) * percent; + y = y + (frames[frame + Y] - y) * percent; + } + if (setupPose) { + bone.shearX = bone.data.shearX + x * alpha; + bone.shearY = bone.data.shearY + y * alpha; + } else { + bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha; + bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha; } - - // Interpolate between the previous frame and the current frame. - var frame:int = Animation.binarySearch(frames, time, ENTRIES); - var prevX:Number = frames[frame + PREV_X]; - var prevY:Number = frames[frame + PREV_Y]; - var frameTime:Number = frames[frame]; - var percent:Number = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - bone.shearX += (bone.data.shearX + (prevX + (frames[frame + X] - prevX) * percent) - bone.shearX) * alpha; - bone.shearY += (bone.data.shearY + (prevY + (frames[frame + Y] - prevY) * percent) - bone.shearY) * alpha; } } diff --git a/spine-as3/spine-as3/src/spine/animation/Timeline.as b/spine-as3/spine-as3/src/spine/animation/Timeline.as index 87a680dd4..d9aaf4520 100644 --- a/spine-as3/spine-as3/src/spine/animation/Timeline.as +++ b/spine-as3/spine-as3/src/spine/animation/Timeline.as @@ -34,7 +34,9 @@ import spine.Skeleton; public interface Timeline { /** Sets the value(s) for the specified time. */ - function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number) : void; + function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void; + + function getPropertyId() : int; } } diff --git a/spine-as3/spine-as3/src/spine/animation/TimelineType.as b/spine-as3/spine-as3/src/spine/animation/TimelineType.as new file mode 100644 index 000000000..e20010027 --- /dev/null +++ b/spine-as3/spine-as3/src/spine/animation/TimelineType.as @@ -0,0 +1,56 @@ +/****************************************************************************** + * Spine Runtimes Software License v2.5 + * + * Copyright (c) 2013-2016, Esoteric Software + * All rights reserved. + * + * You are granted a perpetual, non-exclusive, non-sublicensable, and + * non-transferable license to use, install, execute, and perform the Spine + * Runtimes software and derivative works solely for personal or internal + * use. Without the written permission of Esoteric Software (see Section 2 of + * the Spine Software License Agreement), you may not (a) modify, translate, + * adapt, or develop new applications using the Spine Runtimes or otherwise + * create derivative works or improvements of the Spine Runtimes or (b) remove, + * delete, alter, or obscure any trademarks or any copyright, trademark, patent, + * or other intellectual property or proprietary rights notices on or in the + * Software, including any copy thereof. Redistributions in binary or source + * form must include this license and terms. + * + * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 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. + *****************************************************************************/ + +package spine.animation { + +public class TimelineType { + public var ordinal:int; + + public function TimelineType(order:int) { + this.ordinal = order; + } + + public static const rotate:TimelineType = new TimelineType(0); + public static const translate:TimelineType = new TimelineType(1); + public static const scale:TimelineType = new TimelineType(2); + public static const shear:TimelineType = new TimelineType(3); + public static const attachment:TimelineType = new TimelineType(4); + public static const color:TimelineType = new TimelineType(5); + public static const deform:TimelineType = new TimelineType(6); + public static const event:TimelineType = new TimelineType(7); + public static const drawOrder:TimelineType = new TimelineType(8); + public static const ikConstraint:TimelineType = new TimelineType(9); + public static const transformConstraint:TimelineType = new TimelineType(10); + public static const pathConstraintPosition:TimelineType = new TimelineType(11); + public static const pathConstraintSpacing:TimelineType = new TimelineType(12); + public static const pathConstraintMix:TimelineType = new TimelineType(13); +} + +} diff --git a/spine-as3/spine-as3/src/spine/animation/TrackEntry.as b/spine-as3/spine-as3/src/spine/animation/TrackEntry.as index 9daae7f09..a1f206135 100644 --- a/spine-as3/spine-as3/src/spine/animation/TrackEntry.as +++ b/spine-as3/spine-as3/src/spine/animation/TrackEntry.as @@ -29,22 +29,47 @@ *****************************************************************************/ package spine.animation { +import spine.Poolable; -public class TrackEntry { - public var next:TrackEntry; - internal var previous:TrackEntry; +public class TrackEntry implements Poolable { public var animation:Animation; + public var next:TrackEntry, mixingFrom:TrackEntry; + public var onStart:Listeners = new Listeners(); + public var onInterrupt:Listeners = new Listeners(); + public var onEnd:Listeners = new Listeners(); + public var onDispose:Listeners = new Listeners(); + public var onComplete:Listeners = new Listeners(); + public var onEvent:Listeners = new Listeners(); + public var trackIndex:int; public var loop:Boolean; - public var delay:Number, time:Number = 0, lastTime:Number = -1, endTime:Number, timeScale:Number = 1; - internal var mixTime:Number, mixDuration:Number, mix:Number = 1; - public var onStart:Function, onEnd:Function, onComplete:Function, onEvent:Function; - - public function TrackEntry () { + public var eventThreshold:Number, attachmentThreshold:Number, drawOrderThreshold:Number; + public var animationStart:Number, animationEnd:Number, animationLast:Number, nextAnimationLast:Number; + public var delay:Number, trackTime:Number, trackLast:Number, nextTrackLast:Number, trackEnd:Number, timeScale:Number; + public var alpha:Number, mixTime:Number, mixDuration:Number, mixAlpha:Number; + public var timelinesFirst:Vector. = new Vector.(); + public var timelinesRotation:Vector. = new Vector.(); + + public function getAnimationTime():Number { + if (loop) { + var duration:Number = animationEnd - animationStart; + if (duration == 0) return animationStart; + return (trackTime % duration) + animationStart; + } + return Math.min(trackTime + animationStart, animationEnd); } - - public function toString () : String { - return animation == null ? "" : animation.name; + + public function reset ():void { + next = null; + mixingFrom = null; + animation = null; + onStart.listeners.length = 0; + onInterrupt.listeners.length = 0; + onEnd.listeners.length = 0; + onDispose.listeners.length = 0; + onComplete.listeners.length = 0; + onEvent.listeners.length = 0; + timelinesFirst.length = 0; + timelinesRotation.length = 0; } } - } diff --git a/spine-as3/spine-as3/src/spine/animation/TransformConstraintTimeline.as b/spine-as3/spine-as3/src/spine/animation/TransformConstraintTimeline.as index 9f605f764..9636be357 100644 --- a/spine-as3/spine-as3/src/spine/animation/TransformConstraintTimeline.as +++ b/spine-as3/spine-as3/src/spine/animation/TransformConstraintTimeline.as @@ -29,6 +29,7 @@ *****************************************************************************/ package spine.animation { +import spine.TransformConstraintData; import spine.Event; import spine.Skeleton; import spine.TransformConstraint; @@ -45,6 +46,10 @@ public class TransformConstraintTimeline extends CurveTimeline { super(frameCount); frames = new Vector.(frameCount * ENTRIES, true); } + + override public function getPropertyId () : int { + return (TimelineType.transformConstraint.ordinal << 24) + transformConstraintIndex; + } /** Sets the time and mixes of the specified keyframe. */ public function setFrame (frameIndex:int, time:Number, rotateMix:Number, translateMix:Number, scaleMix:Number, shearMix:Number) : void { @@ -56,34 +61,47 @@ public class TransformConstraintTimeline extends CurveTimeline { frames[frameIndex + SHEAR] = shearMix; } - override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number) : void { + override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void { + var frames:Vector. = this.frames; if (time < frames[0]) return; // Time is before first frame. - var constraint:TransformConstraint = skeleton.transformConstraints[transformConstraintIndex]; + var constraint:TransformConstraint = skeleton.transformConstraints[transformConstraintIndex]; + var rotate:Number, translate:Number, scale:Number, shear:Number; if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. var i:int = frames.length; - constraint.rotateMix += (frames[i + PREV_ROTATE] - constraint.rotateMix) * alpha; - constraint.translateMix += (frames[i + PREV_TRANSLATE] - constraint.translateMix) * alpha; - constraint.scaleMix += (frames[i + PREV_SCALE] - constraint.scaleMix) * alpha; - constraint.shearMix += (frames[i + PREV_SHEAR] - constraint.shearMix) * alpha; - return; + rotate = frames[i + PREV_ROTATE]; + translate = frames[i + PREV_TRANSLATE]; + scale = frames[i + PREV_SCALE]; + shear = frames[i + PREV_SHEAR]; + } else { + // Interpolate between the previous frame and the current frame. + var frame:int = Animation.binarySearch(frames, time, ENTRIES); + rotate = frames[frame + PREV_ROTATE]; + translate = frames[frame + PREV_TRANSLATE]; + scale = frames[frame + PREV_SCALE]; + shear = frames[frame + PREV_SHEAR]; + var frameTime:Number = frames[frame]; + var percent:Number = getCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + rotate += (frames[frame + ROTATE] - rotate) * percent; + translate += (frames[frame + TRANSLATE] - translate) * percent; + scale += (frames[frame + SCALE] - scale) * percent; + shear += (frames[frame + SHEAR] - shear) * percent; + } + if (setupPose) { + var data:TransformConstraintData = constraint.data; + constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha; + constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha; + constraint.scaleMix = data.scaleMix + (scale - data.scaleMix) * alpha; + constraint.shearMix = data.shearMix + (shear - data.shearMix) * alpha; + } else { + constraint.rotateMix += (rotate - constraint.rotateMix) * alpha; + constraint.translateMix += (translate - constraint.translateMix) * alpha; + constraint.scaleMix += (scale - constraint.scaleMix) * alpha; + constraint.shearMix += (shear - constraint.shearMix) * alpha; } - - // Interpolate between the previous frame and the current frame. - var frame:int = Animation.binarySearch(frames, time, ENTRIES); - var frameTime:Number = frames[frame]; - var percent:Number = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - var rotate:Number = frames[frame + PREV_ROTATE]; - var translate:Number = frames[frame + PREV_TRANSLATE]; - var scale:Number = frames[frame + PREV_SCALE]; - var shear:Number = frames[frame + PREV_SHEAR]; - constraint.rotateMix += (rotate + (frames[frame + ROTATE] - rotate) * percent - constraint.rotateMix) * alpha; - constraint.translateMix += (translate + (frames[frame + TRANSLATE] - translate) * percent - constraint.translateMix) - * alpha; - constraint.scaleMix += (scale + (frames[frame + SCALE] - scale) * percent - constraint.scaleMix) * alpha; - constraint.shearMix += (shear + (frames[frame + SHEAR] - shear) * percent - constraint.shearMix) * alpha; } } } diff --git a/spine-as3/spine-as3/src/spine/animation/TranslateTimeline.as b/spine-as3/spine-as3/src/spine/animation/TranslateTimeline.as index 71cabb36b..ee727c281 100644 --- a/spine-as3/spine-as3/src/spine/animation/TranslateTimeline.as +++ b/spine-as3/spine-as3/src/spine/animation/TranslateTimeline.as @@ -45,6 +45,10 @@ public class TranslateTimeline extends CurveTimeline { super(frameCount); frames = new Vector.(frameCount * ENTRIES, true); } + + override public function getPropertyId () : int { + return (TimelineType.translate.ordinal << 24) + boneIndex; + } /** Sets the time and value of the specified keyframe. */ public function setFrame (frameIndex:int, time:Number, x:Number, y:Number) : void { @@ -54,27 +58,35 @@ public class TranslateTimeline extends CurveTimeline { frames[int(frameIndex + Y)] = y; } - override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number) : void { - if (time < frames[0]) - return; // Time is before first frame. + override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector., alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void { + var frames:Vector. = this.frames; + if (time < frames[0]) return; // Time is before first frame. var bone:Bone = skeleton.bones[boneIndex]; - if (time >= frames[int(frames.length - ENTRIES)]) { // Time is after last frame. - bone.x += (bone.data.x + frames[int(frames.length + PREV_X)] - bone.x) * alpha; - bone.y += (bone.data.y + frames[int(frames.length + PREV_Y)] - bone.y) * alpha; - return; + var x:Number, y:Number; + if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame. + x = frames[frames.length + PREV_X]; + y = frames[frames.length + PREV_Y]; + } else { + // Interpolate between the previous frame and the current frame. + var frame:int = Animation.binarySearch(frames, time, ENTRIES); + x = frames[frame + PREV_X]; + y = frames[frame + PREV_Y]; + var frameTime:Number = frames[frame]; + var percent:Number = getCurvePercent(frame / ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + x += (frames[frame + X] - x) * percent; + y += (frames[frame + Y] - y) * percent; + } + if (setupPose) { + bone.x = bone.data.x + x * alpha; + bone.y = bone.data.y + y * alpha; + } else { + bone.x += (bone.data.x + x - bone.x) * alpha; + bone.y += (bone.data.y + y - bone.y) * alpha; } - - // Interpolate between the previous frame and the current frame. - var frame:int = Animation.binarySearch(frames, time, ENTRIES); - var prevX:Number = frames[frame + PREV_X]; - var prevY:Number = frames[frame + PREV_Y]; - var frameTime:Number = frames[frame]; - var percent:Number = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - bone.x += (bone.data.x + prevX + (frames[frame + X] - prevX) * percent - bone.x) * alpha; - bone.y += (bone.data.y + prevY + (frames[frame + Y] - prevY) * percent - bone.y) * alpha; } }