From 993e59f6bdbfe0fffb895a90d8456c1641c9b047 Mon Sep 17 00:00:00 2001 From: badlogic Date: Mon, 20 Apr 2020 18:01:01 +0200 Subject: [PATCH] [lua] Port AnimationState deform mixing while attachment timelines mix out. See #1653. --- spine-lua/Animation.lua | 33 +++++----- spine-lua/AnimationState.lua | 122 ++++++++++++++++++++--------------- spine-lua/Slot.lua | 1 + 3 files changed, 90 insertions(+), 66 deletions(-) diff --git a/spine-lua/Animation.lua b/spine-lua/Animation.lua index 02c3528ae..dad1c8b59 100644 --- a/spine-lua/Animation.lua +++ b/spine-lua/Animation.lua @@ -775,16 +775,22 @@ function Animation.AttachmentTimeline.new (frameCount) return TimelineType.attachment * SHL_24 + self.slotIndex end + function self:setAttachment(skeleton, slot, attachmentName) + attachmentName = slot.data.attachmentName + if not attachmentName then + slot:setAttachment(nil) + else + slot:setAttachment(skeleton:getAttachmentByIndex(self.slotIndex, attachmentName)) + end + end + function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction) local slot = skeleton.slots[self.slotIndex] if not slot.bone.active then return end local attachmentName - if direction == MixDirection.out and blend == MixBlend.setup then - attachmentName = slot.data.attachmentName - if not attachmentName then - slot:setAttachment(nil) - else - slot:setAttachment(skeleton:getAttachmentByIndex(self.slotIndex, attachmentName)) + if direction == MixDirection.out then + if blend == MixBlend.setup then + self:setAttachment(skeleton, slot, slot.data.attachmentName) end return; end @@ -792,12 +798,7 @@ function Animation.AttachmentTimeline.new (frameCount) local frames = self.frames if time < frames[0] then if blend == MixBlend.setup or blend == MixBlend.first then - attachmentName = slot.data.attachmentName - if not attachmentName then - slot:setAttachment(nil) - else - slot:setAttachment(skeleton:getAttachmentByIndex(self.slotIndex, attachmentName)) - end + self:setAttachment(skeleton, slot, slot.data.attachmentName) end return end @@ -1150,9 +1151,11 @@ function Animation.DrawOrderTimeline.new (frameCount) function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction) local drawOrder = skeleton.drawOrder local slots = skeleton.slots - if direction == MixDirection.out and blend == MixBlend.setup then - for i,slot in ipairs(slots) do - drawOrder[i] = slots[i] + if direction == MixDirection.out then + if blend == MixBlend.setup then + for i,slot in ipairs(slots) do + drawOrder[i] = slots[i] + end end return; end diff --git a/spine-lua/AnimationState.lua b/spine-lua/AnimationState.lua index e4b20acba..bab32d964 100644 --- a/spine-lua/AnimationState.lua +++ b/spine-lua/AnimationState.lua @@ -54,7 +54,9 @@ local SUBSEQUENT = 0 local FIRST = 1 local HOLD = 2 local HOLD_MIX = 3 -local NOT_LAST = 4 + +local SETUP = 1 +local CURRENT = 2 local EventType = { start = 0, @@ -214,7 +216,8 @@ function AnimationState.new (data) propertyIDs = {}, animationsChanged = false, timeScale = 1, - mixingTo = {} + mixingTo = {}, + unkeyedState = 0 } self.queue = EventQueue.new(self) setmetatable(self, AnimationState) @@ -358,7 +361,11 @@ function AnimationState:apply (skeleton) local timelines = current.animation.timelines if (i == 0 and mix == 1) or blend == MixBlend.add then for i,timeline in ipairs(timelines) do - timeline:apply(skeleton, animationLast, animationTime, self.events, mix, blend, MixDirection._in) + if timeline.type == Animation.TimelineType.attachment then + self:applyAttachmentTimeline(timeline, skeleton, animationTime, blend, true) + else + timeline:apply(skeleton, animationLast, animationTime, self.events, mix, blend, MixDirection._in) + end end else local timelineMode = current.timelineMode @@ -367,11 +374,13 @@ function AnimationState:apply (skeleton) for ii,timeline in ipairs(timelines) do local timelineBlend = MixBlend.setup - if clearBit(timelineMode[ii], NOT_LAST) == SUBSEQUENT then timelineBlend = blend end + if timelineMode[ii] == SUBSEQUENT then timelineBlend = blend end if timeline.type == Animation.TimelineType.rotate then self:applyRotateTimeline(timeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, ii * 2, firstFrame) + elseif timeline.type == Animation.TimelineType.attachment then + self:applyAttachmentTimeline(skeleton, animationTime, timelineBlend, true) else timeline:apply(skeleton, animationLast, animationTime, self.events, mix, timelineBlend, MixDirection._in) end @@ -386,6 +395,24 @@ function AnimationState:apply (skeleton) i = i + 1 end + -- Set slots attachments to the setup pose, if needed. This occurs if an animation that is mixing out sets attachments so + -- subsequent timelines see any deform, but the subsequent timelines don't set an attachment (eg they are also mixing out or + -- the time is before the first key). + local setupState = self.unkeyedState + SETUP + local slots = skeleton.slots; + for _, slot in ipairs(slots) do + if slot.attachmentState == setupState then + local attachmentName = slot.data.attachmentName + if attachmentName == nil then + slot.attachment = nil + else + slot.attachment = skeleton:getAttachmentByIndex(slot.data.index, attachmentName) + end + end + end + self.unkeyedState = self.unkeyedState + 2; -- Increasing after each use avoids the need to reset attachmentState for every slot. + + queue:drain() return applied end @@ -432,21 +459,14 @@ function AnimationState:applyMixingFrom (to, skeleton, blend) local direction = MixDirection.out; local timelineBlend = MixBlend.setup local alpha = 0 - if clearBit(timelineMode[i], NOT_LAST) == SUBSEQUENT then - timelineBlend = blend - if not attachments and timeline.type == Animation.TimelineType.attachment then - if testBit(timelineMode[i], NOT_LAST) then - skipSubsequent = true - else - timelineBlend = MixBlend.setup - end - end + if timelineMode[i] == SUBSEQUENT then if not drawOrder and timeline.type == Animation.TimelineType.drawOrder then skipSubsequent = true end + timelineBlend = blend alpha = alphaMix - elseif clearBit(timelineMode[i], NOT_LAST) == FIRST then + elseif timelineMode[i] == FIRST then timelineBlend = MixBlend.setup alpha = alphaMix - elseif clearBit(timelineMode[i], NOT_LAST) == HOLD then + elseif timelineMode[i] == HOLD then timelineBlend = MixBlend.setup alpha = alphaHold else @@ -459,13 +479,11 @@ function AnimationState:applyMixingFrom (to, skeleton, blend) from.totalAlpha = from.totalAlpha + alpha if timeline.type == Animation.TimelineType.rotate then self:applyRotateTimeline(timeline, skeleton, animationTime, alpha, timelineBlend, timelinesRotation, i * 2, firstFrame) + elseif timeline.type == Animation.TimelineType.attachment then + self:applyAttachmentTimeline(timeline, skeleton, animationTime, timelineBlend, attachments) else - if timelineBlend == MixBlend.setup then - if timeline.type == Animation.TimelineType.attachment then - if attachments or testBit(timelineMode[i], NOT_LAST) then direction = MixDirection._in end - elseif timeline.type == Animation.TimelineType.drawOrder then - if drawOrder then direction = MixDirection._in end - end + if (drawOrder and timeline.type == Animation.TimelineType.drawOrder and timelineBlend == MixBlend.setup) then + direction = MixDirection._in end timeline:apply(skeleton, animationLast, animationTime, self.events, alpha, timelineBlend, direction) end @@ -483,6 +501,38 @@ function AnimationState:applyMixingFrom (to, skeleton, blend) return mix end +function AnimationState:applyAttachmentTimeline(timeline, skeleton, time, blend, attachments) + local slot = skeleton.slots[timeline.slotIndex]; + if slot.bone.active == false then return end + + local frames = timeline.frames + if time < frames[0] then -- Time is before first frame. + if blend == MixBlend.setup or blend == MixBlend.first then + self:setAttachment(skeleton, slot, slot.data.attachmentName, attachments); + end + else + local frameIndex = 0 + if (time >= frames[zlen(frames) - 1]) then -- Time is after last frame. + frameIndex = zlen(frames) - 1; + else + frameIndex = Animation.binarySearch(frames, time, 1) - 1; + self:setAttachment(skeleton, slot, timeline.attachmentNames[frameIndex], attachments) + end + end + + -- If an attachment wasn't set (ie before the first frame or attachments is false), set the setup attachment later. + if slot.attachmentState <= self.unkeyedState then slot.attachmentState = self.unkeyedState + SETUP end +end + +function AnimationState:setAttachment(skeleton, slot, attachmentName, attachments) + if (attachmentName == nil) then + slot.attachment = nil + else + slot.attachment = skeleton:getAttachmentByIndex(slot.data.index, attachmentName) + end + if attachments then slot.attachmentState = self.unkeyedState + CURRENT end +end + function AnimationState:applyRotateTimeline (timeline, skeleton, time, alpha, blend, timelinesRotation, i, firstFrame) if firstFrame then timelinesRotation[i] = 0 @@ -866,36 +916,6 @@ function AnimationState:_animationsChanged () end i = i + 1 end - - self.propertyIDs = {} - for i = highestIndex, 0, -1 do - entry = self.tracks[i] - while entry do - self:computeNotLast(entry) - entry = entry.mixingFrom - end - end -end - -function AnimationState:computeNotLast(entry) - local timelines = entry.animation.timelines - local timelinesCount = #entry.animation.timelines - local timelineMode = entry.timelineMode - local propertyIDs = self.propertyIDs - - local i = 1 - while i <= timelinesCount do - local timeline = timelines[i] - if (timeline.type == Animation.TimelineType.attachment) then - local slotIndex = timeline.slotIndex - if not (propertyIDs[slotIndex] == nil) then - timelineMode[i] = setBit(timelineMode[i], NOT_LAST) - else - propertyIDs[slotIndex] = true - end - end - i = i + 1 - end end function AnimationState:computeHold(entry) diff --git a/spine-lua/Slot.lua b/spine-lua/Slot.lua index 0978edb5b..8230863b5 100644 --- a/spine-lua/Slot.lua +++ b/spine-lua/Slot.lua @@ -45,6 +45,7 @@ function Slot.new (data, bone) darkColor = nil, attachment = nil, attachmentTime = 0, + attachmentState = 0, deform = {} }