|
@@ -37,6 +37,12 @@ local math_min = math.min
|
|
|
local math_abs = math.abs
|
|
|
local math_signum = utils.signum
|
|
|
|
|
|
+local function zlen(array)
|
|
|
+ return #array + 1
|
|
|
+end
|
|
|
+
|
|
|
+local EMPTY_ANIMATION = Animation.new("<empty>", {}, 0)
|
|
|
+
|
|
|
local EventType = {
|
|
|
start = 0,
|
|
|
interrupt = 1,
|
|
@@ -371,6 +377,400 @@ function AnimationState:applyMixingFrom (entry, skeleton)
|
|
|
return mix
|
|
|
end
|
|
|
|
|
|
--- CONTINUE WITH applyRotateTimeline here
|
|
|
+function AnimationState:applyRotateTimeline (timeline, skeleton, time, alpha, setupPose, timelinesRotation, i, firstFrame)
|
|
|
+ if alpha == 1 then
|
|
|
+ timeline:apply(skeleton, 0, time, nil, 1, setupPose, false)
|
|
|
+ return
|
|
|
+ end
|
|
|
+
|
|
|
+ local rotateTimeline = timeline
|
|
|
+ local frames = rotateTimeline.frames
|
|
|
+ if time < frames[0] then return end -- Time is before first frame.
|
|
|
+
|
|
|
+ local bone = skeleton.bones[rotateTimeline.boneIndex]
|
|
|
+
|
|
|
+ local r2 = 0
|
|
|
+ if time >= frames[zlen(frames) - Animation.RotateTimeline.ENTRIES] then -- Time is after last frame.
|
|
|
+ r2 = bone.data.rotation + frames[zlen(frames) + PREV_ROTATION]
|
|
|
+ else
|
|
|
+ -- Interpolate between the previous frame and the current frame.
|
|
|
+ local frame = Animation.binarySearch(frames, time, ENTRIES)
|
|
|
+ local prevRotation = frames[frame + PREV_ROTATION]
|
|
|
+ local frameTime = frames[frame]
|
|
|
+ local percent = rotateTimeline:getCurvePercent(math_floor(frame / 2) - 1,
|
|
|
+ 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
|
|
|
+
|
|
|
+ r2 = frames[frame + ROTATION] - prevRotation
|
|
|
+ r2 = r2 - (16384 - math_floor(16384.499999999996 - r2 / 360)) * 360
|
|
|
+ r2 = prevRotation + r2 * percent + bone.data.rotation
|
|
|
+ r2 = r2 - (16384 - math_floor(16384.499999999996 - r2 / 360)) * 360
|
|
|
+ end
|
|
|
+
|
|
|
+ -- Mix between rotations using the direction of the shortest route on the first frame while detecting crosses.
|
|
|
+ local r1 = bone.rotation
|
|
|
+ if setupPose then r1 = bone.data.rotation end
|
|
|
+ local total = 0
|
|
|
+ local diff = r2 - r1
|
|
|
+ if diff == 0 then
|
|
|
+ if firstFrame then
|
|
|
+ timelinesRotation[i] = 0
|
|
|
+ total = 0
|
|
|
+ else
|
|
|
+ total = timelinesRotation[i]
|
|
|
+ end
|
|
|
+ else
|
|
|
+ diff = diff - (16384 - math_floor(16384.499999999996 - diff / 360)) * 360
|
|
|
+ local lastTotal = 0
|
|
|
+ local lastDiff = 0
|
|
|
+ if firstFrame then
|
|
|
+ lastTotal = 0
|
|
|
+ lastDiff = diff
|
|
|
+ else
|
|
|
+ lastTotal = timelinesRotation[i] -- Angle and direction of mix, including loops.
|
|
|
+ lastDiff = timelinesRotation[i + 1] -- Difference between bones.
|
|
|
+ end
|
|
|
+ local current = diff > 0
|
|
|
+ local dir = lastTotal >= 0
|
|
|
+ -- Detect cross at 0 (not 180).
|
|
|
+ if math_signum(lastDiff) ~= math_signum(diff) and math_abs(lastDiff) <= 90 then
|
|
|
+ -- A cross after a 360 rotation is a loop.
|
|
|
+ if math_abs(lastTotal) > 180 then lastTotal = lastTotal + 360 * math_signum(lastTotal) end
|
|
|
+ dir = current
|
|
|
+ end
|
|
|
+ total = diff + lastTotal - math_ceil(lastTotal / 360 - 0.5) * 360 -- FIXME used to be %360, store loops as part of lastTotal.
|
|
|
+ if dir ~= current then total = total + 360 * Math.signum(lastTotal) end
|
|
|
+ timelinesRotation[i] = total
|
|
|
+ end
|
|
|
+ timelinesRotation[i + 1] = diff
|
|
|
+ r1 = r1 + total * alpha
|
|
|
+ bone.rotation = r1 - (16384 - math_floor(16384.499999999996 - r1 / 360)) * 360
|
|
|
+end
|
|
|
+
|
|
|
+function AnimationState:queueEvents (entry, animationTime)
|
|
|
+ local animationStart = entry.animationStart
|
|
|
+ local animationEnd = entry.animationEnd
|
|
|
+ local duration = animationEnd - animationStart
|
|
|
+ local trackLastWrapped = entry.trackLast % duration
|
|
|
+
|
|
|
+ -- Queue events before complete.
|
|
|
+ local events = self.events
|
|
|
+ local queue = self.queue
|
|
|
+ local i = 1
|
|
|
+ local n = #events
|
|
|
+ while i <= n do
|
|
|
+ local event = events[i]
|
|
|
+ if event.time < trackLastWrapped then break end
|
|
|
+ if not (event.time > animationEnd) then -- Discard events outside animation start/end.
|
|
|
+ queue:event(entry, event)
|
|
|
+ end
|
|
|
+ i = i + 1
|
|
|
+ end
|
|
|
+
|
|
|
+ -- Queue complete if completed a loop iteration or the animation.
|
|
|
+ local queueComplete = false
|
|
|
+ if entry.loop then
|
|
|
+ queueComplete = (trackLastWrapped > entry.trackTime % duration)
|
|
|
+ else
|
|
|
+ queueComplete = (animationTime >= animationEnd and entry.animationLast < animationEnd)
|
|
|
+ end
|
|
|
+ if queueComplete then
|
|
|
+ queue:complete(entry)
|
|
|
+ end
|
|
|
+
|
|
|
+ -- Queue events after complete.
|
|
|
+ while i <= n do
|
|
|
+ local event = events[i]
|
|
|
+ if not (event.time < animationStart) then --// Discard events outside animation start/end.
|
|
|
+ queue.event(entry, event)
|
|
|
+ end
|
|
|
+ i = i + 1
|
|
|
+ end
|
|
|
+ events = {}
|
|
|
+end
|
|
|
+
|
|
|
+function AnimationState:clearTracks ()
|
|
|
+ local queue = self.queue
|
|
|
+ local tracks = self.tracks
|
|
|
+ queue.drainDisabled = true;
|
|
|
+ for i,track in pairs(tracks) do
|
|
|
+ self:clearTrack(i)
|
|
|
+ end
|
|
|
+ tracks = {}
|
|
|
+ queue.drainDisabled = false;
|
|
|
+ queue:drain();
|
|
|
+end
|
|
|
+
|
|
|
+function AnimationState:clearTrack (trackIndex)
|
|
|
+ local tracks = self.tracks
|
|
|
+ local queue = self.queue
|
|
|
+ local current = tracks[trackIndex]
|
|
|
+ if current == nil then return end
|
|
|
+
|
|
|
+ queue:_end(current)
|
|
|
+
|
|
|
+ self:disposeNext(current)
|
|
|
+
|
|
|
+ local entry = current;
|
|
|
+ while (true) do
|
|
|
+ local from = entry.mixingFrom
|
|
|
+ if from == nil then break end
|
|
|
+ queue:_end(from)
|
|
|
+ entry.mixingFrom = nil
|
|
|
+ entry = from
|
|
|
+ end
|
|
|
+
|
|
|
+ tracks[current.trackIndex] = nil
|
|
|
+
|
|
|
+ queue:drain()
|
|
|
+end
|
|
|
+
|
|
|
+function AnimationState:setCurrent (index, current)
|
|
|
+ local from = self:expandToIndex(index)
|
|
|
+ local tracks = self.tracks
|
|
|
+ local queue = self.queue
|
|
|
+ tracks[index] = current
|
|
|
+
|
|
|
+ if from then
|
|
|
+ queue:interrupt(from)
|
|
|
+ current.mixingFrom = from
|
|
|
+ current.mixTime = 0
|
|
|
+
|
|
|
+ -- If not completely mixed in, set mixAlpha so mixing out happens from current mix to zero.
|
|
|
+ if from.mixingFrom then current.mixAlpha = current.mixAlpha * math_min(from.mixTime / from.mixDuration, 1) end
|
|
|
+ end
|
|
|
+
|
|
|
+ queue:start(current)
|
|
|
+end
|
|
|
+
|
|
|
+function AnimationState:setAnimationByName (trackIndex, animationName, loop)
|
|
|
+ local animation = self.data.skeletonData:findAnimation(animationName)
|
|
|
+ if not animation then error("Animation not found: " .. animationName, 2) end
|
|
|
+ return self:setAnimation(trackIndex, animation, loop)
|
|
|
+end
|
|
|
+
|
|
|
+function AnimationState:setAnimation (trackIndex, animation, loop)
|
|
|
+ if not animation then error("animation cannot be null.") end
|
|
|
+ local current = self:expandToIndex(trackIndex)
|
|
|
+ local queue = self.queue
|
|
|
+ if current then
|
|
|
+ if current.nextTrackLast == -1 then
|
|
|
+ -- Don't mix from an entry that was never applied.
|
|
|
+ tracks[trackIndex] = nil
|
|
|
+ queue:interrupt(current)
|
|
|
+ queue:_end(current)
|
|
|
+ self:disposeNext(current)
|
|
|
+ current = nil
|
|
|
+ else
|
|
|
+ self:disposeNext(current)
|
|
|
+ end
|
|
|
+ end
|
|
|
+ local entry = self:trackEntry(trackIndex, animation, loop, current)
|
|
|
+ self:setCurrent(trackIndex, entry)
|
|
|
+ queue:drain()
|
|
|
+ return entry
|
|
|
+end
|
|
|
+
|
|
|
+function AnimationState:addAnimationByName (trackIndex, animationName, loop, delay)
|
|
|
+ local animation = data.skeletonData:findAnimation(animationName)
|
|
|
+ if not animation then error("Animation not found: " + animationName) end
|
|
|
+ return self:addAnimation(trackIndex, animation, loop, delay)
|
|
|
+end
|
|
|
+
|
|
|
+function AnimationState:addAnimation (trackIndex, animationName, loop, delay)
|
|
|
+ if not nimation then error("animation cannot be null.") end
|
|
|
+
|
|
|
+ local last = self:expandToIndex(trackIndex)
|
|
|
+ if last then
|
|
|
+ while last.next do
|
|
|
+ last = last.next
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ local entry = self:trackEntry(trackIndex, animation, loop, last)
|
|
|
+ local queue = self.queue
|
|
|
+
|
|
|
+ if not last then
|
|
|
+ self:setCurrent(trackIndex, entry)
|
|
|
+ queue:drain()
|
|
|
+ else
|
|
|
+ last.next = entry
|
|
|
+ if delay <= 0 then
|
|
|
+ local duration = last.animationEnd - last.animationStart
|
|
|
+ if duration ~= 0 then
|
|
|
+ delay = delay + duration * (1 + math_floor(last.trackTime / duration)) - data:getMix(last.animation, animation)
|
|
|
+ else
|
|
|
+ delay = 0
|
|
|
+ end
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ entry.delay = delay
|
|
|
+ return entry
|
|
|
+end
|
|
|
+
|
|
|
+function AnimationState:setEmptyAnimation (trackIndex, mixDuration)
|
|
|
+ local entry = self:setAnimation(trackIndex, EMPTY_ANIMATION, false)
|
|
|
+ entry.mixDuration = mixDuration
|
|
|
+ entry.trackEnd = mixDuration
|
|
|
+ return entry
|
|
|
+end
|
|
|
+
|
|
|
+function AnimationState:addEmptyAnimation (trackIndex, mixDuration, delay)
|
|
|
+ if delay <= 0 then delay = delay - mixDuration end
|
|
|
+ local entry = self:addAnimation(trackIndex, EMPTY_ANIMATION, false, delay)
|
|
|
+ entry.mixDuration = mixDuration
|
|
|
+ entry.trackEnd = mixDuration
|
|
|
+ return entry
|
|
|
+end
|
|
|
+
|
|
|
+function AnimationState:setEmptyAnimations (mixDuration)
|
|
|
+ local queue = self.queue
|
|
|
+ queue.drainDisabled = true
|
|
|
+ for i,current in pairs(self.tracks) do
|
|
|
+ if current then self:setEmptyAnimation(current.trackIndex, mixDuration) end
|
|
|
+ end
|
|
|
+ queue.drainDisabled = false
|
|
|
+ queue:drain()
|
|
|
+end
|
|
|
+
|
|
|
+function AnimationState:expandToIndex (index)
|
|
|
+ return self.tracks[index]
|
|
|
+end
|
|
|
+
|
|
|
+function AnimationState:trackEntry (trackIndex, animation, loop, last)
|
|
|
+ local data = self.data
|
|
|
+ local entry = TrackEntry.new()
|
|
|
+ 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
|
|
|
+ if loop then
|
|
|
+ entry.trackEnd = 999999999
|
|
|
+ else
|
|
|
+ entry.trackEnd = entry.animationEnd
|
|
|
+ end
|
|
|
+ entry.timeScale = 1
|
|
|
+
|
|
|
+ entry.alpha = 1
|
|
|
+ entry.mixAlpha = 1
|
|
|
+ entry.mixTime = 0
|
|
|
+ if not last then
|
|
|
+ entry.mixDuration = 0
|
|
|
+ else
|
|
|
+ entry.mixDuration = data:getMix(last.animation, animation)
|
|
|
+ end
|
|
|
+ return entry
|
|
|
+end
|
|
|
+
|
|
|
+function AnimationState:disposeNext (entry)
|
|
|
+ local _next = entry.next
|
|
|
+ local queue = self.queue
|
|
|
+ while _next do
|
|
|
+ queue:dispose(_next)
|
|
|
+ _next = next.next
|
|
|
+ end
|
|
|
+ entry.next = nil
|
|
|
+end
|
|
|
+
|
|
|
+function AnimationState:animationsChanged ()
|
|
|
+ self.animationsChanged = false;
|
|
|
+
|
|
|
+ self.propertyIDs = {}
|
|
|
+ local propertyIDs = self.propertyIDs;
|
|
|
+
|
|
|
+ -- need to get the highest index cause Lua is funny
|
|
|
+ local highest = -1
|
|
|
+ for i,entry in pairs(tracks) do
|
|
|
+ if i > highest then highest = i end
|
|
|
+ end
|
|
|
+
|
|
|
+ -- Set timelinesFirst for all entries, from lowest track to highest.
|
|
|
+ local i = 1
|
|
|
+ local n = highest
|
|
|
+ local tracks = self.tracks
|
|
|
+ while i <= n do
|
|
|
+ local entry = tracks[i]
|
|
|
+ if entry then
|
|
|
+ self:setTimelinesFirst(entry);
|
|
|
+ i = i + 1
|
|
|
+ break;
|
|
|
+ end
|
|
|
+ i = i + 1
|
|
|
+ end
|
|
|
+ while i <= n do
|
|
|
+ local entry = tracks[i]
|
|
|
+ if entry then self:checkTimelinesFirst(entry) end
|
|
|
+ i = i + 1
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+function AnimationState:setTimelinesFirst (entry)
|
|
|
+ if entry.mixingFrom then
|
|
|
+ self:setTimelinesFirst(entry.mixingFrom)
|
|
|
+ self:checkTimelinesUsage(entry, entry.timelinesFirst)
|
|
|
+ return
|
|
|
+ end
|
|
|
+ local propertyIDs = self.propertyIDs
|
|
|
+ local n = #entry.animation.timelines
|
|
|
+ local timelines = entry.animation.timelines
|
|
|
+ entry.timelinesFirst = {}
|
|
|
+ local usage = entry.timelinesFirst;
|
|
|
+ local i = 1
|
|
|
+ while i <= n do
|
|
|
+ local id = "" .. timelines[i]:getPropertyId()
|
|
|
+ propertyIDs[id] = id
|
|
|
+ usage[i] = true;
|
|
|
+ i = i + 1
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+function AnimationState:checkTimlinesFirst (entry)
|
|
|
+ if entry.mixingFrom then self:checkTimelinesFirst(entry.mixingFrom) end
|
|
|
+ self:checkTimelinesUsage(entry, entry.timelinesFirst)
|
|
|
+end
|
|
|
+
|
|
|
+function AnimationState:checkTimelinesUsage (entry, usageArray)
|
|
|
+ local propertyIDs = self.propertyIDs
|
|
|
+ local n = #entry.animation.timelines
|
|
|
+ local timelines = entry.animation.timelines
|
|
|
+ local usage = usageArray
|
|
|
+ while i <= n do
|
|
|
+ local id = "" .. timelines[i]:getPropertyId()
|
|
|
+ local contained = propertyIDs[id] == id
|
|
|
+ propertyIDs[id] = id
|
|
|
+ usage[i] = not contained
|
|
|
+ i = i + 1
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+function AnimationState:getCurrent (trackIndex)
|
|
|
+ return self.tracks[trackIndex]
|
|
|
+end
|
|
|
+
|
|
|
+function AnimationState:clearListeners ()
|
|
|
+ self.onStart = nil
|
|
|
+ self.onInterrupt = nil
|
|
|
+ self.onEnd = nil
|
|
|
+ self.onComplete = nil
|
|
|
+ self.onDispose = nil
|
|
|
+ self.onEvent = nil
|
|
|
+end
|
|
|
+
|
|
|
+function AnimationState:clearListenerNotificatin ()
|
|
|
+ self.queue:clear()
|
|
|
+end
|
|
|
|
|
|
return AnimationState
|