ソースを参照

[lua] Ported animation state changes.

badlogic 8 年 前
コミット
38588b8895
1 ファイル変更128 行追加158 行削除
  1. 128 158
      spine-lua/AnimationState.lua

+ 128 - 158
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("<empty>", {}, 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
+		else
+			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, setupPose, timelinesRotation, i * 2, firstFrame) -- FIXME passing i * 2, correct indexing?
+			self:applyRotateTimeline(timeline, skeleton, animationTime, alpha, first, timelinesRotation, i * 2, firstFrame)
 		else
-			if not setupPose then
+			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)