From 38588b8895343add96526e99ee6f2a603cff0c7a Mon Sep 17 00:00:00 2001 From: badlogic Date: Fri, 19 May 2017 12:28:06 +0200 Subject: [PATCH] [lua] Ported animation state changes. --- spine-lua/AnimationState.lua | 290 ++++++++++++++++------------------- 1 file changed, 130 insertions(+), 160 deletions(-) diff --git a/spine-lua/AnimationState.lua b/spine-lua/AnimationState.lua index 524d64fc5..955301668 100644 --- a/spine-lua/AnimationState.lua +++ b/spine-lua/AnimationState.lua @@ -34,6 +34,7 @@ local utils = require "spine-lua.utils" local Animation = require "spine-lua.Animation" local AnimationStateData = require "spine-lua.AnimationStateData" local math_min = math.min +local math_max = math.max local math_abs = math.abs local math_signum = utils.signum local math_floor = math.floor @@ -45,6 +46,9 @@ local function zlen(array) end local EMPTY_ANIMATION = Animation.new("", {}, 0) +local SUBSEQUENT = 0 +local FIRST = 1 +local DIP = 2 local EventType = { start = 0, @@ -167,15 +171,63 @@ function TrackEntry.new () eventThreshold = 0, attachmentThreshold = 0, drawOrderThreshold = 0, animationStart = 0, animationEnd = 0, animationLast = 0, nextAnimationLast = 0, delay = 0, trackTime = 0, trackLast = 0, nextTrackLast = 0, trackEnd = 0, timeScale = 0, - alpha = 0, mixTime = 0, mixDuration = 0, mixAlpha = 0, - timelinesFirst = {}, - timelinesLast = {}, + alpha = 0, mixTime = 0, mixDuration = 0, interruptAlpha = 0, + timelineData = {}, + timelineDipMix = {}, timelinesRotation = {} } setmetatable(self, TrackEntry) return self end +function TrackEntry:setTimelineData(to, mixingToArray, propertyIDs) + if to then table_insert(mixingToArray, to) end + local lastEntry = self + if self.mixingFrom then lastEntry = self.mixingFrom:setTimelineData(self, mixingToArray, propertyIDs) end + if to then mixingToArray[#mixingToArray] = nil end + + local mixingTo = mixingToArray + local mixingToLast = #mixingToArray + local timelines = self.animation.timelines + local timelinesCount = #self.animation.timelines + local timelineData = self.timelineData + local timelineDipMix = self.timelineDipMix + + local i = 1 + while i <= timelinesCount do + local id = "" .. timelines[i]:getPropertyId() + if not (propertyIDs[id] == nil) then + timelineData[i] = SUBSEQUENT + elseif (to == nil or not to:hasTimeline(id)) then + timelineData[i] = FIRST + else + timelineData[i] = DIP + local ii = mixingToLast + while ii > 0 do + local entry = mixingTo[ii] + local skip = false + if not entry:hasTimeline(id) then + if entry.mixDuration > 0 then timelineDipMix[i] = entry end + skip = true + break + end + ii = ii - 1 + end + if not skip then timelineDipMix[i] = nil end + end + i = i + 1 + end + return lastEntry +end + +function TrackEntry:hasTimeline(id) + local timelines = self.animation.timelines + for i,timeline in ipairs(timelines) do + if timeline:getPropertyId() == id then return true end + end + return false +end + function TrackEntry:getAnimationTime () if self.loop then local duration = self.animationEnd - self.animationStart @@ -204,7 +256,7 @@ function AnimationState.new (data) propertyIDs = {}, animationsChanged = false, timeScale = 1, - mixingMultiple = false + mixingTo = {} } self.queue = EventQueue.new(self) setmetatable(self, AnimationState) @@ -260,8 +312,17 @@ function AnimationState:update (delta) end end - if not skip then - self:updateMixingFrom(current, delta) + if not skip then + if current.mixingFrom and self:updateMixingFrom(current, delta, 2) then + -- End mixing from entries once all have completed. + local from = current.mixingFrom + current.mixingFrom = nil + while from do + queue:_end(from) + from = from.mixingFrom + end + end + current.trackTime = current.trackTime + currentDelta end end @@ -271,23 +332,30 @@ function AnimationState:update (delta) queue:drain() end -function AnimationState:updateMixingFrom (entry, delta) +function AnimationState:updateMixingFrom (entry, delta, animationCount) local from = entry.mixingFrom - if from == nil then return end + if from == nil then return true end - self:updateMixingFrom(from, delta) - - local queue = self.queue - if entry.mixTime >= entry.mixDuration and from.mixingFrom == nil and entry.mixTime > 0 then - entry.mixingFrom = null - queue:_end(from) - return + local finished = self:updateMixingFrom(from, delta, animationCount + 1) + + -- Require mixTime > 0 to ensure the mixing from entry was applied at least once. + if (entry.mixTime > 0 and (entry.mixTime >= entry.mixDuration or entry.timeScale == 0)) then + if (animationCount > 5 and from.mixingFrom == nil) then + -- Limit linked list by speeding up and removing old entries. + entry.interruptAlpha = math_max(0, entry.interruptAlpha - delta * 0.66) + if entry.interruptAlpha <= 0 then + entry.mixingFrom = nil + queue._end(from) + end + end + return finished end from.animationLast = from.nextAnimationLast from.trackLast = from.nextTrackLast - from.trackTime = from.trackTime + delta * from.timeScale; - entry.mixTime = entry.mixTime + delta * entry.timeScale; + from.trackTime = from.trackTime + delta * from.timeScale + entry.mixTime = entry.mixTime + delta * entry.timeScale + return false; end function AnimationState:apply (skeleton) @@ -317,15 +385,16 @@ function AnimationState:apply (skeleton) timeline:apply(skeleton, animationLast, animationTime, events, 1, true, false) end else + local timelineData = current.timelineData local firstFrame = #current.timelinesRotation == 0 - local timelinesRotation = current.timelinesRotation; - local timelinesFirst = current.timelinesFirst + local timelinesRotation = current.timelinesRotation + for i,timeline in ipairs(timelines) do if timeline.type == Animation.TimelineType.rotate then - self:applyRotateTimeline(timeline, skeleton, animationTime, mix, timelinesFirst[i], timelinesRotation, i * 2, + self:applyRotateTimeline(timeline, skeleton, animationTime, mix, timelineData[i] > 0, timelinesRotation, i * 2, firstFrame) -- FIXME passing ii * 2, indexing correct? else - timeline:apply(skeleton, animationLast, animationTime, events, mix, timelinesFirst[i], false) + timeline:apply(skeleton, animationLast, animationTime, events, mix, timelineData[i] > 0, false) end end end @@ -339,15 +408,15 @@ function AnimationState:apply (skeleton) queue:drain() end -function AnimationState:applyMixingFrom (entry, skeleton) - local from = entry.mixingFrom +function AnimationState:applyMixingFrom (to, skeleton) + local from = to.mixingFrom if from.mixingFrom then self:applyMixingFrom(from, skeleton) end local mix = 0 - if entry.mixDuration == 0 then -- Single frame mix to undo mixingFrom changes. + if to.mixDuration == 0 then -- Single frame mix to undo mixingFrom changes. mix = 1 else - mix = entry.mixTime / entry.mixDuration + mix = to.mixTime / to.mixDuration if mix > 1 then mix = 1 end end @@ -358,36 +427,45 @@ function AnimationState:applyMixingFrom (entry, skeleton) local animationLast = from.animationLast local animationTime = from:getAnimationTime() local timelines = from.animation.timelines - local timelinesFirst = from.timelinesFirst - local timelinesLast = nil - if (self.multipleMixing == false) then timelinesLast = from.timelinesLast end - local alphaBase = from.alpha * entry.mixAlpha - local alphaMix = alphaBase * (1 - mix) + local timelineData = from.timelineData + local timelineDipMix = from.timelineDipMix local firstFrame = #from.timelinesRotation == 0 local timelinesRotation = from.timelinesRotation + local first = false + local alphaDip = from.alpha * to.interruptAlpha + local alphaMix = alphaDip * (1 - mix) + local alpha = 0 + local skip = false for i,timeline in ipairs(timelines) do - local setupPose = timelinesFirst[i] - local alpha = 1; - if (timelinesLast ~= nil and setupPose and not timelinesLast[i]) then - alpha = alphaBase - else + + if timelineData[i] == SUBSEQUENT then + first = false + alpha = alphaMix + elseif timelineData[i] == FIRST then + first = true alpha = alphaMix - end - if timeline.type == Animation.TimelineType.rotate then - self:applyRotateTimeline(timeline, skeleton, animationTime, alpha, setupPose, timelinesRotation, i * 2, firstFrame) -- FIXME passing i * 2, correct indexing? else - if not setupPose then + first = true + alpha = alphaDip + local dipMix = timelineDipMix[i] + if dipMix then alpha = alpha * math_max(0, 1 - dipMix.mixtime / dipMix.mixDuration) end + end + + if timeline.type == Animation.TimelineType.rotate then + self:applyRotateTimeline(timeline, skeleton, animationTime, alpha, first, timelinesRotation, i * 2, firstFrame) + else + if not first then if not attachments and timeline.type == Animation.TimelineType.attackment then skip = true end if not drawOrder and timeline.type == Animation.TimelineType.drawOrder then skip = true end end - if not skip then timeline:apply(skeleton, animationLast, animationTime, events, alpha, setupPose, true) end + if not skip then timeline:apply(skeleton, animationLast, animationTime, events, alpha, first, true) end end end - if (entry.mixDuration > 0) then self:queueEvents(from, animationTime) end + if (to.mixDuration > 0) then self:queueEvents(from, animationTime) end self.events = {}; from.nextAnimationLast = animationTime from.nextTrackLast = from.trackTime @@ -555,24 +633,8 @@ function AnimationState:setCurrent (index, current, interrupt) current.mixingFrom = from current.mixTime = 0 - local mixingFrom = from.mixingFrom - if (mixingFrom ~= nil and from.mixDuration > 0) then - if (self.multipleMixing) then - current.mixAlpha = current.mixAlpha * math_min(from.mixTime / from.mixDuration, 1) - else - if (from.mixTime / from.mixDuration < 0.5 and mixingFrom.animation ~= EMPTY_ANIMATION) then - current.mixingFrom = mixingFrom - mixingFrom.mixingFrom = from - mixingFrom.mixTime = from.mixDuration - from.mixTime - mixingFrom.mixDuration = from.mixDuration - from.mixingFrom = nil - from = mixingFrom - end - - from.mixAlpha = 0; - from.mixTime = 0; - from.mixDuration = 0; - end + if from.mixingFrom and from.mixDuration > 0 then + current.interruptAlpha = current.interruptAlpha * math_min(1, from.mixTime / from.mixDuration) end from.timelinesRotation = {}; @@ -705,7 +767,7 @@ function AnimationState:trackEntry (trackIndex, animation, loop, last) entry.timeScale = 1 entry.alpha = 1 - entry.mixAlpha = 1 + entry.interruptAlpha = 1 entry.mixTime = 0 if not last then entry.mixDuration = 0 @@ -726,111 +788,19 @@ function AnimationState:disposeNext (entry) end function AnimationState:_animationsChanged () - self.animationsChanged = false; + self.animationsChanged = false self.propertyIDs = {} - local propertyIDs = self.propertyIDs; - - -- need to get the highest index cause Lua is funny - local highest = -1 - local tracks = self.tracks - 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 = 0 - local n = highest + 1 - 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 + local propertyIDs = self.propertyIDs + local mixingTo = self.mixingTo - if (self.multipleMixing) then return end - - self.propertyIDs = {} - local lowestMixingFrom = n - i = 0; - while i < n do - entry = self.tracks[i] - if not (entry == nil or entry.mixingFrom == nil) then - lowestMixingFrom = i - i = n + 1 -- break + local lastEntry = nil + for i, entry in pairs(self.tracks) do + if entry then + entry:setTimelineData(lastEntry, mixingTo, propertyIDs) + lastEntry = entry end - i = i + 1 end - i = n - 1 - while i >= lowestMixingFrom do - local entry = self.tracks[i] - if (entry) then - local propertyIDs = self.propertyIDs - local timelines = entry.animation.timelines - local ii = 1 - local nn = #entry.animation.timelines; - while ii <= nn do - local id = "" .. timelines[ii]:getPropertyId() - propertyIDs[id] = id - ii = ii + 1 - end - - entry = entry.mixingFrom - while (entry) do - self:checkTimelinesUsage(entry, entry.timelinesLast) - entry = entry.mixingFrom; - end - 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:checkTimelinesFirst (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 - local i = 1 - 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)