ソースを参照

[lua] Port AnimationState deform mixing while attachment timelines mix out. See #1653.

badlogic 5 年 前
コミット
993e59f6bd
3 ファイル変更90 行追加66 行削除
  1. 18 15
      spine-lua/Animation.lua
  2. 71 51
      spine-lua/AnimationState.lua
  3. 1 0
      spine-lua/Slot.lua

+ 18 - 15
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

+ 71 - 51
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)

+ 1 - 0
spine-lua/Slot.lua

@@ -45,6 +45,7 @@ function Slot.new (data, bone)
 		darkColor = nil,
 		attachment = nil,
 		attachmentTime = 0,
+		attachmentState = 0,
 		deform = {}
 	}