Browse Source

[lua] 4.0 port, runs but LOVE is not rendering.

Nathan Sweet 4 năm trước cách đây
mục cha
commit
137c3f69ed

+ 1 - 1
spine-love/main.lua

@@ -113,8 +113,8 @@ end
 function love.load(arg)
 function love.load(arg)
 	if arg[#arg] == "-debug" then require("mobdebug").start() end
 	if arg[#arg] == "-debug" then require("mobdebug").start() end
 	skeletonRenderer = spine.SkeletonRenderer.new(true)
 	skeletonRenderer = spine.SkeletonRenderer.new(true)
-  table.insert(skeletons, loadSkeleton("mix-and-match-pro", "mix-and-match", "dance", nil, 0.5, 400, 500))
 	table.insert(skeletons, loadSkeleton("spineboy-pro", "spineboy", "walk", nil, 0.5, 400, 500))
 	table.insert(skeletons, loadSkeleton("spineboy-pro", "spineboy", "walk", nil, 0.5, 400, 500))
+	table.insert(skeletons, loadSkeleton("mix-and-match-pro", "mix-and-match", "dance", nil, 0.5, 400, 500))
 	table.insert(skeletons, loadSkeleton("stretchyman-pro", "stretchyman", "sneak", nil, 0.5, 200, 500))
 	table.insert(skeletons, loadSkeleton("stretchyman-pro", "stretchyman", "sneak", nil, 0.5, 200, 500))
 	table.insert(skeletons, loadSkeleton("coin-pro", "coin", "animation", nil, 0.5, 400, 300))
 	table.insert(skeletons, loadSkeleton("coin-pro", "coin", "animation", nil, 0.5, 400, 300))
 	table.insert(skeletons, loadSkeleton("raptor-pro", "raptor", "walk", nil, 0.3, 400, 500))
 	table.insert(skeletons, loadSkeleton("raptor-pro", "raptor", "walk", nil, 0.3, 400, 500))

+ 114 - 82
spine-lua/spine-lua/Animation.lua

@@ -37,7 +37,7 @@ local utils = require "spine-lua.utils"
 local AttachmentType = require "spine-lua.attachments.AttachmentType"
 local AttachmentType = require "spine-lua.attachments.AttachmentType"
 
 
 local setmetatable = setmetatable
 local setmetatable = setmetatable
-local math_floor = math_floor
+local math_floor = math.floor
 local math_abs = math.abs
 local math_abs = math.abs
 local math_signum = utils.signum
 local math_signum = utils.signum
 
 
@@ -64,14 +64,14 @@ function Animation.new (name, timelines, duration)
 		self.timelineIds = {}
 		self.timelineIds = {}
 		for i,timeline in ipairs(self.timelines) do
 		for i,timeline in ipairs(self.timelines) do
 			for _,id in ipairs(timeline.propertyIds) do
 			for _,id in ipairs(timeline.propertyIds) do
-				timelineIds[id] = true
+				self.timelineIds[id] = true
 			end
 			end
 		end
 		end
 	end
 	end
 
 
 	function self:hasTimeline (ids)
 	function self:hasTimeline (ids)
 		for _,id in ipairs(ids) do
 		for _,id in ipairs(ids) do
-			if timelineIds[id] then return true end
+			if self.timelineIds[id] then return true end
 		end
 		end
 		return false
 		return false
 	end
 	end
@@ -134,11 +134,28 @@ Animation.Property = {
 }
 }
 local Property = Animation.Property
 local Property = Animation.Property
 
 
+Animation.TimelineType = {
+	rotate = 0,
+	translate = 1, translateX = 2, translateY = 3,
+	scale = 4, scaleX = 5, scaleY = 6,
+	shear = 7, shearX = 8, shearY = 9,
+	rgba = 10, rgb = 11, alpha = 12, rgba2 = 13, rgb2 = 14,
+	attachment = 15,
+	deform = 16,
+	event = 17,
+	drawOrder = 18,
+	ikConstraint = 19,
+	transformConstraint = 20,
+	pathConstraintPosition = 21, pathConstraintSpacing = 22, pathConstraintMix = 23
+}
+local TimelineType = Animation.TimelineType
+
 Animation.Timeline = {}
 Animation.Timeline = {}
-function Animation.Timeline.new (frameCount, propertyIds)
+function Animation.Timeline.new (timelineType, frameEntries, frameCount, propertyIds)
 	local self = {
 	local self = {
+		timelineType = timelineType,
 		propertyIds = propertyIds,
 		propertyIds = propertyIds,
-		frames = utils.newNumberArrayZero((frameCount - 1) * self:getFrameEntries())
+		frames = utils.newNumberArrayZero((frameCount - 1) * frameEntries)
 	}
 	}
 
 
 	function self:getFrameEntries ()
 	function self:getFrameEntries ()
@@ -158,7 +175,8 @@ end
 
 
 local function search1 (frames, time)
 local function search1 (frames, time)
 	local n = zlen(frames)
 	local n = zlen(frames)
-	while i <= n do
+	local i = 1
+	while i < n do
 		if frames[i] > time then return i - 1 end
 		if frames[i] > time then return i - 1 end
 		i = i + 1
 		i = i + 1
 	end
 	end
@@ -169,7 +187,7 @@ Animation.Timeline.search1 = search1
 local function search (frames, time, step)
 local function search (frames, time, step)
 	local n = zlen(frames)
 	local n = zlen(frames)
 	local i = step
 	local i = step
-	while i <= n do
+	while i < n do
 		if frames[i] > time then return i - step end
 		if frames[i] > time then return i - step end
 		i = i + step
 		i = i + step
 	end
 	end
@@ -182,14 +200,15 @@ local BEZIER = 2
 local BEZIER_SIZE = 18
 local BEZIER_SIZE = 18
 
 
 Animation.CurveTimeline = {}
 Animation.CurveTimeline = {}
-function Animation.CurveTimeline.new (frameCount, bezierCount, propertyIds)
+function Animation.CurveTimeline.new (timelineType, frameEntries, frameCount, bezierCount, propertyIds)
 	local LINEAR = 0
 	local LINEAR = 0
 	local STEPPED = 1
 	local STEPPED = 1
 	local BEZIER = 2
 	local BEZIER = 2
 	local BEZIER_SIZE = 10 * 2 - 1
 	local BEZIER_SIZE = 10 * 2 - 1
 
 
-	local self = Animation.Timeline.new(frameCount, propertyIds)
+	local self = Animation.Timeline.new(timelineType, frameEntries, frameCount, propertyIds)
 	self.curves = utils.newNumberArrayZero(frameCount + bezierCount * BEZIER_SIZE)
 	self.curves = utils.newNumberArrayZero(frameCount + bezierCount * BEZIER_SIZE)
+	self.curves[frameCount - 1] = STEPPED
 
 
 	function self:getFrameCount ()
 	function self:getFrameCount ()
 		return math_floor(zlen(self.curves) / BEZIER_SIZE) + 1
 		return math_floor(zlen(self.curves) / BEZIER_SIZE) + 1
@@ -261,11 +280,11 @@ function Animation.CurveTimeline.new (frameCount, bezierCount, propertyIds)
 end
 end
 
 
 Animation.CurveTimeline1 = {}
 Animation.CurveTimeline1 = {}
-function Animation.CurveTimeline1.new (frameCount, bezierCount, propertyId)
+function Animation.CurveTimeline1.new (timelineType, frameCount, bezierCount, propertyId)
 	local ENTRIES = 2
 	local ENTRIES = 2
 	local VALUE = 1
 	local VALUE = 1
 
 
-	local self = Animation.CurveTimeline.new(frameCount, bezierCount, { propertyId })
+	local self = Animation.CurveTimeline.new(timelineType, ENTRIES, frameCount, bezierCount, { propertyId })
 
 
 	function self:getFrameEntries ()
 	function self:getFrameEntries ()
 		return ENTRIES
 		return ENTRIES
@@ -303,12 +322,12 @@ function Animation.CurveTimeline1.new (frameCount, bezierCount, propertyId)
 end
 end
 
 
 Animation.CurveTimeline2 = {}
 Animation.CurveTimeline2 = {}
-function Animation.CurveTimeline2.new (frameCount, bezierCount, propertyId1, propertyId2)
+function Animation.CurveTimeline2.new (timelineType, frameCount, bezierCount, propertyId1, propertyId2)
 	local ENTRIES = 3
 	local ENTRIES = 3
 	local VALUE1 = 1
 	local VALUE1 = 1
 	local VALUE2 = 2
 	local VALUE2 = 2
 
 
-	local self = Animation.CurveTimeline.new(frameCount, bezierCount, { propertyId1, propertyId2 })
+	local self = Animation.CurveTimeline.new(timelineType, ENTRIES, frameCount, bezierCount, { propertyId1, propertyId2 })
 
 
 	function self:getFrameEntries ()
 	function self:getFrameEntries ()
 		return ENTRIES
 		return ENTRIES
@@ -326,7 +345,7 @@ end
 
 
 Animation.RotateTimeline = {}
 Animation.RotateTimeline = {}
 function Animation.RotateTimeline.new (frameCount, bezierCount, boneIndex)
 function Animation.RotateTimeline.new (frameCount, bezierCount, boneIndex)
-	local self = Animation.CurveTimeline1.new(frameCount, bezierCount, Property.rotate.."|"..boneIndex)
+	local self = Animation.CurveTimeline1.new(TimelineType.rotate, frameCount, bezierCount, Property.rotate.."|"..boneIndex)
 	self.boneIndex = boneIndex
 	self.boneIndex = boneIndex
 
 
 	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
 	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
@@ -358,7 +377,11 @@ end
 
 
 Animation.TranslateTimeline = {}
 Animation.TranslateTimeline = {}
 function Animation.TranslateTimeline.new (frameCount, bezierCount, boneIndex)
 function Animation.TranslateTimeline.new (frameCount, bezierCount, boneIndex)
-	local self = Animation.CurveTimeline2.new(frameCount, bezierCount,
+	local ENTRIES = 3
+	local VALUE1 = 1
+	local VALUE2 = 2
+
+	local self = Animation.CurveTimeline2.new(TimelineType.translate, frameCount, bezierCount,
 		Property.x.."|"..boneIndex,
 		Property.x.."|"..boneIndex,
 		Property.y.."|"..boneIndex
 		Property.y.."|"..boneIndex
 	)
 	)
@@ -382,7 +405,7 @@ function Animation.TranslateTimeline.new (frameCount, bezierCount, boneIndex)
 
 
 		local x = 0
 		local x = 0
 		local y = 0
 		local y = 0
-		local frame = search2(frames, time, ENTRIES)
+		local i = search(frames, time, ENTRIES)
 		local curveType = self.curves[math_floor(i / ENTRIES)]
 		local curveType = self.curves[math_floor(i / ENTRIES)]
 		if curveType == LINEAR then
 		if curveType == LINEAR then
 			local before = frames[i]
 			local before = frames[i]
@@ -416,7 +439,7 @@ end
 
 
 Animation.TranslateXTimeline = {}
 Animation.TranslateXTimeline = {}
 function Animation.TranslateXTimeline.new (frameCount, bezierCount, boneIndex)
 function Animation.TranslateXTimeline.new (frameCount, bezierCount, boneIndex)
-	local self = Animation.CurveTimeline1.new(frameCount, bezierCount, Property.x.."|"..boneIndex)
+	local self = Animation.CurveTimeline1.new(TimelineType.translateX, frameCount, bezierCount, Property.x.."|"..boneIndex)
 	self.boneIndex = boneIndex
 	self.boneIndex = boneIndex
 
 
 	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
 	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
@@ -448,7 +471,7 @@ end
 
 
 Animation.TranslateYTimeline = {}
 Animation.TranslateYTimeline = {}
 function Animation.TranslateYTimeline.new (frameCount, bezierCount, boneIndex)
 function Animation.TranslateYTimeline.new (frameCount, bezierCount, boneIndex)
-	local self = Animation.CurveTimeline1.new(frameCount, bezierCount, Property.x.."|"..boneIndex)
+	local self = Animation.CurveTimeline1.new(TimelineType.translateY, frameCount, bezierCount, Property.x.."|"..boneIndex)
 	self.boneIndex = boneIndex
 	self.boneIndex = boneIndex
 
 
 	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
 	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
@@ -480,7 +503,11 @@ end
 
 
 Animation.ScaleTimeline = {}
 Animation.ScaleTimeline = {}
 function Animation.ScaleTimeline.new (frameCount, bezierCount, boneIndex)
 function Animation.ScaleTimeline.new (frameCount, bezierCount, boneIndex)
-	local self = Animation.CurveTimeline2.new(frameCount, bezierCount,
+	local ENTRIES = 3
+	local VALUE1 = 1
+	local VALUE2 = 2
+
+	local self = Animation.CurveTimeline2.new(TimelineType.scale, frameCount, bezierCount,
 		Property.scaleX.."|"..boneIndex,
 		Property.scaleX.."|"..boneIndex,
 		Property.scaleY.."|"..boneIndex
 		Property.scaleY.."|"..boneIndex
 	)
 	)
@@ -504,7 +531,7 @@ function Animation.ScaleTimeline.new (frameCount, bezierCount, boneIndex)
 
 
 		local x = 0
 		local x = 0
 		local y = 0
 		local y = 0
-		local i = search2(frames, time, ENTRIES)
+		local i = search(frames, time, ENTRIES)
 		local curveType = self.curves[math_floor(i / ENTRIES)]
 		local curveType = self.curves[math_floor(i / ENTRIES)]
 		if curveType == LINEAR then
 		if curveType == LINEAR then
 			local before = frames[i]
 			local before = frames[i]
@@ -577,7 +604,7 @@ end
 
 
 Animation.ScaleXTimeline = {}
 Animation.ScaleXTimeline = {}
 function Animation.ScaleXTimeline.new (frameCount, bezierCount, boneIndex)
 function Animation.ScaleXTimeline.new (frameCount, bezierCount, boneIndex)
-	local self = Animation.CurveTimeline1.new(frameCount, bezierCount, Property.scaleX.."|"..boneIndex)
+	local self = Animation.CurveTimeline1.new(TimelineType.scaleX, frameCount, bezierCount, Property.scaleX.."|"..boneIndex)
 	self.boneIndex = boneIndex
 	self.boneIndex = boneIndex
 
 
 	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
 	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
@@ -634,7 +661,7 @@ end
 
 
 Animation.ScaleYTimeline = {}
 Animation.ScaleYTimeline = {}
 function Animation.ScaleYTimeline.new (frameCount, bezierCount, boneIndex)
 function Animation.ScaleYTimeline.new (frameCount, bezierCount, boneIndex)
-	local self = Animation.CurveTimeline1.new(frameCount, bezierCount, Property.scaleY.."|"..boneIndex)
+	local self = Animation.CurveTimeline1.new(TimelineType.scaleY, frameCount, bezierCount, Property.scaleY.."|"..boneIndex)
 	self.boneIndex = boneIndex
 	self.boneIndex = boneIndex
 
 
 	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
 	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
@@ -690,8 +717,12 @@ function Animation.ScaleYTimeline.new (frameCount, bezierCount, boneIndex)
 end
 end
 
 
 Animation.ShearTimeline = {}
 Animation.ShearTimeline = {}
-function Animation.ShearTimeline.new (frameCount)
-	local self = Animation.CurveTimeline2.new(frameCount, bezierCount,
+function Animation.ShearTimeline.new (frameCount, bezierCount, boneIndex)
+	local ENTRIES = 3
+	local VALUE1 = 1
+	local VALUE2 = 2
+
+	local self = Animation.CurveTimeline2.new(TimelineType.shear, frameCount, bezierCount,
 		Property.shearX.."|"..boneIndex,
 		Property.shearX.."|"..boneIndex,
 		Property.shearY.."|"..boneIndex
 		Property.shearY.."|"..boneIndex
 	)
 	)
@@ -715,7 +746,7 @@ function Animation.ShearTimeline.new (frameCount)
 
 
 		local x = 0
 		local x = 0
 		local y = 0
 		local y = 0
-		local i = search2(frames, time, ENTRIES)
+		local i = search(frames, time, ENTRIES)
 		local curveType = self.curves[math_floor(i / ENTRIES)]
 		local curveType = self.curves[math_floor(i / ENTRIES)]
 		if curveType == LINEAR then
 		if curveType == LINEAR then
 			local before = frames[i]
 			local before = frames[i]
@@ -748,8 +779,8 @@ function Animation.ShearTimeline.new (frameCount)
 end
 end
 
 
 Animation.ShearXTimeline = {}
 Animation.ShearXTimeline = {}
-function Animation.ShearXTimeline.new (frameCount)
-	local self = Animation.CurveTimeline1.new(frameCount, bezierCount, Property.shearX.."|"..boneIndex)
+function Animation.ShearXTimeline.new (frameCount, bezierCount, boneIndex)
+	local self = Animation.CurveTimeline1.new(TimelineType.shearX, frameCount, bezierCount, Property.shearX.."|"..boneIndex)
 	self.boneIndex = boneIndex
 	self.boneIndex = boneIndex
 
 
 	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
 	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
@@ -780,8 +811,8 @@ function Animation.ShearXTimeline.new (frameCount)
 end
 end
 
 
 Animation.ShearYTimeline = {}
 Animation.ShearYTimeline = {}
-function Animation.ShearYTimeline.new (frameCount)
-	local self = Animation.CurveTimeline1.new(frameCount, bezierCount, Property.shearY.."|"..boneIndex)
+function Animation.ShearYTimeline.new (frameCount, bezierCount, boneIndex)
+	local self = Animation.CurveTimeline1.new(TimelineType.shearY, frameCount, bezierCount, Property.shearY.."|"..boneIndex)
 	self.boneIndex = boneIndex
 	self.boneIndex = boneIndex
 
 
 	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
 	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
@@ -819,7 +850,7 @@ function Animation.RGBATimeline.new (frameCount, bezierCount, slotIndex)
 	local B = 3
 	local B = 3
 	local A = 4
 	local A = 4
 
 
-	local self = Animation.CurveTimeline.new(frameCount, bezierCount, {
+	local self = Animation.CurveTimeline.new(TimelineType.rgba, ENTRIES, frameCount, bezierCount, {
 		Property.rgb.."|"..slotIndex,
 		Property.rgb.."|"..slotIndex,
 		Property.alpha.."|"..slotIndex
 		Property.alpha.."|"..slotIndex
 	})
 	})
@@ -856,7 +887,7 @@ function Animation.RGBATimeline.new (frameCount, bezierCount, slotIndex)
 		end
 		end
 
 
 		local r, g, b, a
 		local r, g, b, a
-		local i = search2(frames, time, ENTRIES)
+		local i = search(frames, time, ENTRIES)
 		local curveType = self.curves[i / ENTRIES]
 		local curveType = self.curves[i / ENTRIES]
 		if curveType == LINEAR then
 		if curveType == LINEAR then
 			local before = frames[i]
 			local before = frames[i]
@@ -899,7 +930,7 @@ function Animation.RGBTimeline.new (frameCount, bezierCount, slotIndex)
 	local G = 2
 	local G = 2
 	local B = 3
 	local B = 3
 
 
-	local self = Animation.CurveTimeline.new(frameCount, bezierCount, { Property.rgb.."|"..slotIndex })
+	local self = Animation.CurveTimeline.new(TimelineType.rgb, ENTRIES, frameCount, bezierCount, { Property.rgb.."|"..slotIndex })
 	self.slotIndex = slotIndex
 	self.slotIndex = slotIndex
 	
 	
 	function self:getFrameEntries ()
 	function self:getFrameEntries ()
@@ -935,7 +966,7 @@ function Animation.RGBTimeline.new (frameCount, bezierCount, slotIndex)
 		end
 		end
 
 
 		local r, g, b
 		local r, g, b
-		local i = search2(frames, time, ENTRIES)
+		local i = search(frames, time, ENTRIES)
 		local curveType = self.curves[i / ENTRIES]
 		local curveType = self.curves[i / ENTRIES]
 		if curveType == LINEAR then
 		if curveType == LINEAR then
 			local before = frames[i]
 			local before = frames[i]
@@ -978,7 +1009,7 @@ end
 
 
 Animation.AlphaTimeline = {}
 Animation.AlphaTimeline = {}
 function Animation.AlphaTimeline.new (frameCount, bezierCount, slotIndex)
 function Animation.AlphaTimeline.new (frameCount, bezierCount, slotIndex)
-	local self = Animation.CurveTimeline1.new(frameCount, bezierCount, Property.alpha.."|"..slotIndex)
+	local self = Animation.CurveTimeline1.new(TimelineType.alpha, frameCount, bezierCount, Property.alpha.."|"..slotIndex)
 	self.slotIndex = slotIndex
 	self.slotIndex = slotIndex
 
 
 	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
 	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
@@ -1020,7 +1051,7 @@ function Animation.RGBA2Timeline.new (frameCount, bezierCount, slotIndex)
 	local G2 = 6
 	local G2 = 6
 	local B2 = 7
 	local B2 = 7
 
 
-	local self = Animation.CurveTimeline.new(frameCount, bezierCount, {
+	local self = Animation.CurveTimeline.new(TimelineType.rgba2, ENTRIES, frameCount, bezierCount, {
 		Property.rgb.."|"..slotIndex,
 		Property.rgb.."|"..slotIndex,
 		Property.alpha.."|"..slotIndex,
 		Property.alpha.."|"..slotIndex,
 		Property.rgb2.."|"..slotIndex
 		Property.rgb2.."|"..slotIndex
@@ -1069,7 +1100,7 @@ function Animation.RGBA2Timeline.new (frameCount, bezierCount, slotIndex)
 		end
 		end
 
 
 		local r, g, b, a, r2, g2, b2
 		local r, g, b, a, r2, g2, b2
-		local i = search2(frames, time, ENTRIES)
+		local i = search(frames, time, ENTRIES)
 		local curveType = self.curves[math_floor(i / ENTRIES)]
 		local curveType = self.curves[math_floor(i / ENTRIES)]
 		if curveType == LINEAR then
 		if curveType == LINEAR then
 			local before = frames[i]
 			local before = frames[i]
@@ -1139,7 +1170,7 @@ function Animation.RGB2Timeline.new (frameCount, bezierCount, slotIndex)
 	local G2 = 5
 	local G2 = 5
 	local B2 = 6
 	local B2 = 6
 
 
-	local self = Animation.CurveTimeline.new(frameCount, bezierCount, {
+	local self = Animation.CurveTimeline.new(TimelineType.rgb2, ENTRIES, frameCount, bezierCount, {
 		Property.rgb.."|"..slotIndex,
 		Property.rgb.."|"..slotIndex,
 		Property.rgb2.."|"..slotIndex
 		Property.rgb2.."|"..slotIndex
 	})
 	})
@@ -1189,7 +1220,7 @@ function Animation.RGB2Timeline.new (frameCount, bezierCount, slotIndex)
 		end
 		end
 
 
 		local r, g, b, r2, g2, b2
 		local r, g, b, r2, g2, b2
-		local i = search2(frames, time, ENTRIES)
+		local i = search(frames, time, ENTRIES)
 		local curveType = self.curves[math_floor(i / ENTRIES)]
 		local curveType = self.curves[math_floor(i / ENTRIES)]
 		if curveType == LINEAR then
 		if curveType == LINEAR then
 			local before = frames[i]
 			local before = frames[i]
@@ -1254,7 +1285,7 @@ end
 
 
 Animation.AttachmentTimeline = {}
 Animation.AttachmentTimeline = {}
 function Animation.AttachmentTimeline.new (frameCount, bezierCount, slotIndex)
 function Animation.AttachmentTimeline.new (frameCount, bezierCount, slotIndex)
-	local self = Animation.Timeline.new(frameCount, { Property.attachment + "|" + slotIndex })
+	local self = Animation.Timeline.new(TimelineType.attachment, 1, frameCount, { Property.attachment.."|"..slotIndex })
 	self.slotIndex = slotIndex
 	self.slotIndex = slotIndex
 	self.attachmentNames = {}
 	self.attachmentNames = {}
 
 
@@ -1311,7 +1342,7 @@ end
 
 
 Animation.DeformTimeline = {}
 Animation.DeformTimeline = {}
 function Animation.DeformTimeline.new (frameCount, bezierCount, slotIndex, attachment)
 function Animation.DeformTimeline.new (frameCount, bezierCount, slotIndex, attachment)
-	local self = Animation.CurveTimeline.new(frameCount, bezierCount, { Property.deform + "|" + slotIndex + "|" + attachment.id })
+	local self = Animation.CurveTimeline.new(TimelineType.deform, 1, frameCount, bezierCount, { Property.deform.."|"..slotIndex.."|"..attachment.id })
 	self.slotIndex = slotIndex
 	self.slotIndex = slotIndex
 	self.attachment = attachment
 	self.attachment = attachment
 	self.vertices = {}
 	self.vertices = {}
@@ -1353,7 +1384,7 @@ function Animation.DeformTimeline.new (frameCount, bezierCount, slotIndex, attac
 		end
 		end
 	end
 	end
 
 
-	function getCurvePercent (time, frame)
+	function self:getCurvePercent (time, frame)
 		local curves = self.curves
 		local curves = self.curves
 		local i = curves[frame]
 		local i = curves[frame]
 		if i == LINEAR then
 		if i == LINEAR then
@@ -1387,7 +1418,7 @@ function Animation.DeformTimeline.new (frameCount, bezierCount, slotIndex, attac
 		if not slot.bone.active then return end
 		if not slot.bone.active then return end
 
 
 		local vertexAttachment = slot.attachment
 		local vertexAttachment = slot.attachment
-		if not vertexAttachment or not vertexAttachment.vertexAttachment or vertexAttachment.deformAttachment ~= self.attachment then return end
+		if not vertexAttachment or not vertexAttachment.isVertexAttachment or vertexAttachment.deformAttachment ~= self.attachment then return end
 
 
 		local frames = self.frames
 		local frames = self.frames
 		local deform = slot.deform
 		local deform = slot.deform
@@ -1604,7 +1635,7 @@ end
 Animation.EventTimeline = {}
 Animation.EventTimeline = {}
 local eventPropertyIds = { Property.event }
 local eventPropertyIds = { Property.event }
 function Animation.EventTimeline.new (frameCount)
 function Animation.EventTimeline.new (frameCount)
-	local self = Animation.Timeline.new(frameCount, eventPropertyIds)
+	local self = Animation.Timeline.new(TimelineType.event, 1, frameCount, eventPropertyIds)
 	self.events = {}
 	self.events = {}
 
 
 	function self:getFrameCount ()
 	function self:getFrameCount ()
@@ -1635,7 +1666,7 @@ function Animation.EventTimeline.new (frameCount)
 		if lastTime < frames[0] then
 		if lastTime < frames[0] then
 			i = 0
 			i = 0
 		else
 		else
-			i = binarySearch1(frames, lastTime)
+			i = search1(frames, lastTime) + 1
 			local i = frames[i]
 			local i = frames[i]
 			while i > 0 do -- Fire multiple events with the same frame.
 			while i > 0 do -- Fire multiple events with the same frame.
 				if frames[i - 1] ~= i then break end
 				if frames[i - 1] ~= i then break end
@@ -1654,7 +1685,7 @@ end
 Animation.DrawOrderTimeline = {}
 Animation.DrawOrderTimeline = {}
 local drawOrderPropertyIds = { Property.drawOrder }
 local drawOrderPropertyIds = { Property.drawOrder }
 function Animation.DrawOrderTimeline.new (frameCount)
 function Animation.DrawOrderTimeline.new (frameCount)
-	local self = Animation.Timeline.new(frameCount, drawOrderPropertyIds)
+	local self = Animation.Timeline.new(TimelineType.drawOrder, 1, frameCount, drawOrderPropertyIds)
 	self.drawOrders = {}
 	self.drawOrders = {}
 
 
 	function self:getFrameCount ()
 	function self:getFrameCount ()
@@ -1667,6 +1698,9 @@ function Animation.DrawOrderTimeline.new (frameCount)
 	end
 	end
 
 
 	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
 	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
+		local drawOrder = skeleton.drawOrder
+		local slots = skeleton.slots
+
 		if direction == MixDirection.mixOut then
 		if direction == MixDirection.mixOut then
 			if blend == MixBlend.setup then
 			if blend == MixBlend.setup then
 				for i,slot in ipairs(slots) do
 				for i,slot in ipairs(slots) do
@@ -1691,8 +1725,6 @@ function Animation.DrawOrderTimeline.new (frameCount)
 				drawOrder[i] = slots[i]
 				drawOrder[i] = slots[i]
 			end
 			end
 		else
 		else
-			local drawOrder = skeleton.drawOrder
-			local slots = skeleton.slots
 			for i,setupIndex in ipairs(drawOrderToSetupIndex) do
 			for i,setupIndex in ipairs(drawOrderToSetupIndex) do
 				drawOrder[i] = skeleton.slots[setupIndex]
 				drawOrder[i] = skeleton.slots[setupIndex]
 			end
 			end
@@ -1711,7 +1743,7 @@ function Animation.IkConstraintTimeline.new (frameCount, bezierCount, ikConstrai
 	local COMPRESS = 4
 	local COMPRESS = 4
 	local STRETCH = 5
 	local STRETCH = 5
 
 
-	local self = Animation.CurveTimeline.new(frameCount, bezierCount, { Property.ikConstraint + "|" + ikConstraintIndex })
+	local self = Animation.CurveTimeline.new(TimelineType.ikConstraint, ENTRIES, frameCount, bezierCount, { Property.ikConstraint.."|"..ikConstraintIndex })
 	self.ikConstraintIndex = ikConstraintIndex
 	self.ikConstraintIndex = ikConstraintIndex
 
 
 	function self:getFrameEntries ()
 	function self:getFrameEntries ()
@@ -1804,7 +1836,7 @@ function Animation.IkConstraintTimeline.new (frameCount, bezierCount, ikConstrai
 end
 end
 
 
 Animation.TransformConstraintTimeline = {}
 Animation.TransformConstraintTimeline = {}
-function Animation.TransformConstraintTimeline.new (frameCount, transformConstraintIndex)
+function Animation.TransformConstraintTimeline.new (frameCount, bezierCount, transformConstraintIndex)
 	local ENTRIES = 7
 	local ENTRIES = 7
 	local ROTATE = 1
 	local ROTATE = 1
 	local X = 2
 	local X = 2
@@ -1813,7 +1845,7 @@ function Animation.TransformConstraintTimeline.new (frameCount, transformConstra
 	local SCALEY = 5
 	local SCALEY = 5
 	local SHEARY = 6
 	local SHEARY = 6
 
 
-	local self = Animation.CurveTimeline.new(frameCount, bezierCount, { Property.transformConstraint + "|" + transformConstraintIndex })
+	local self = Animation.CurveTimeline.new(TimelineType.transformConstraint, ENTRIES, frameCount, bezierCount, { Property.transformConstraint.."|"..transformConstraintIndex })
 	self.transformConstraintIndex = transformConstraintIndex
 	self.transformConstraintIndex = transformConstraintIndex
 
 
 	function self:getFrameEntries ()
 	function self:getFrameEntries ()
@@ -1918,7 +1950,7 @@ end
 
 
 Animation.PathConstraintPositionTimeline = {}
 Animation.PathConstraintPositionTimeline = {}
 function Animation.PathConstraintPositionTimeline.new (frameCount, bezierCount, pathConstraintIndex)
 function Animation.PathConstraintPositionTimeline.new (frameCount, bezierCount, pathConstraintIndex)
-	local self = Animation.CurveTimeline1.new(frameCount, bezierCount, Property.pathConstraintPosition.."|"..pathConstraintIndex)
+	local self = Animation.CurveTimeline1.new(TimelineType.pathConstraintPosition, frameCount, bezierCount, Property.pathConstraintPosition.."|"..pathConstraintIndex)
 	self.pathConstraintIndex = pathConstraintIndex
 	self.pathConstraintIndex = pathConstraintIndex
 
 
 	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
 	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
@@ -1948,7 +1980,7 @@ end
 
 
 Animation.PathConstraintSpacingTimeline = {}
 Animation.PathConstraintSpacingTimeline = {}
 function Animation.PathConstraintSpacingTimeline.new (frameCount, bezierCount, pathConstraintIndex)
 function Animation.PathConstraintSpacingTimeline.new (frameCount, bezierCount, pathConstraintIndex)
-	local self = Animation.CurveTimeline1.new(frameCount, bezierCount, Property.pathConstraintSpacing.."|"..pathConstraintIndex)
+	local self = Animation.CurveTimeline1.new(TimelineType.pathConstraintSpacing, frameCount, bezierCount, Property.pathConstraintSpacing.."|"..pathConstraintIndex)
 	self.pathConstraintIndex = pathConstraintIndex
 	self.pathConstraintIndex = pathConstraintIndex
 
 
 	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
 	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
@@ -1983,7 +2015,7 @@ function Animation.PathConstraintMixTimeline.new (frameCount, bezierCount, pathC
 	local X = 2
 	local X = 2
 	local Y = 3
 	local Y = 3
 
 
-	local self = Animation.CurveTimeline.new(frameCount, bezierCount, Property.pathConstraintMix.."|"..pathConstraintIndex)
+	local self = Animation.CurveTimeline.new(TimelineType.pathConstraintMix, ENTRIES, frameCount, bezierCount, Property.pathConstraintMix.."|"..pathConstraintIndex)
 	self.pathConstraintIndex = pathConstraintIndex
 	self.pathConstraintIndex = pathConstraintIndex
 
 
 	function self:getFrameEntries ()
 	function self:getFrameEntries ()
@@ -2006,13 +2038,13 @@ function Animation.PathConstraintMixTimeline.new (frameCount, bezierCount, pathC
 		local frames = self.frames
 		local frames = self.frames
 		if time < frames[0] then
 		if time < frames[0] then
 			if blend == MixBlend.setup then
 			if blend == MixBlend.setup then
-				constraint.mixRotate = constraint.data.mixRotate;
-				constraint.mixX = constraint.data.mixX;
-				constraint.mixY = constraint.data.mixY;
+				constraint.mixRotate = constraint.data.mixRotate
+				constraint.mixX = constraint.data.mixX
+				constraint.mixY = constraint.data.mixY
 			elseif blend == MixBlend.first then
 			elseif blend == MixBlend.first then
-				constraint.mixRotate = constraint.mixRotate + (constraint.data.mixRotate - constraint.mixRotate) * alpha;
-				constraint.mixX = constraint.mixX + (constraint.data.mixX - constraint.mixX) * alpha;
-				constraint.mixY = constraint.mixY + (constraint.data.mixY - constraint.mixY) * alpha;
+				constraint.mixRotate = constraint.mixRotate + (constraint.data.mixRotate - constraint.mixRotate) * alpha
+				constraint.mixX = constraint.mixX + (constraint.data.mixX - constraint.mixX) * alpha
+				constraint.mixY = constraint.mixY + (constraint.data.mixY - constraint.mixY) * alpha
 			end
 			end
 			return
 			return
 		end
 		end
@@ -2020,36 +2052,36 @@ function Animation.PathConstraintMixTimeline.new (frameCount, bezierCount, pathC
 		local rotate
 		local rotate
 		local x
 		local x
 		local y
 		local y
-		local i = search(frames, time, ENTRIES);
-		local curveType = self.curves[math_floor(i / 4)];
+		local i = search(frames, time, ENTRIES)
+		local curveType = self.curves[math_floor(i / 4)]
 		if curveType == LINEAR then
 		if curveType == LINEAR then
-			local before = frames[i];
-			rotate = frames[i + ROTATE];
-			x = frames[i + X];
-			y = frames[i + Y];
-			local t = (time - before) / (frames[i + ENTRIES] - before);
-			rotate = rotate + (frames[i + ENTRIES + ROTATE] - rotate) * t;
-			x = x + (frames[i + ENTRIES + X] - x) * t;
-			y = y + (frames[i + ENTRIES + Y] - y) * t;
+			local before = frames[i]
+			rotate = frames[i + ROTATE]
+			x = frames[i + X]
+			y = frames[i + Y]
+			local t = (time - before) / (frames[i + ENTRIES] - before)
+			rotate = rotate + (frames[i + ENTRIES + ROTATE] - rotate) * t
+			x = x + (frames[i + ENTRIES + X] - x) * t
+			y = y + (frames[i + ENTRIES + Y] - y) * t
 		elseif curveType == STEPPED then
 		elseif curveType == STEPPED then
-			rotate = frames[i + ROTATE];
-			x = frames[i + X];
-			y = frames[i + Y];
+			rotate = frames[i + ROTATE]
+			x = frames[i + X]
+			y = frames[i + Y]
 		else
 		else
-			rotate = this.getBezierValue(time, i, ROTATE, curveType - BEZIER);
-			x = this.getBezierValue(time, i, X, curveType + BEZIER_SIZE - BEZIER);
-			y = this.getBezierValue(time, i, Y, curveType + BEZIER_SIZE * 2 - BEZIER);
+			rotate = this.getBezierValue(time, i, ROTATE, curveType - BEZIER)
+			x = this.getBezierValue(time, i, X, curveType + BEZIER_SIZE - BEZIER)
+			y = this.getBezierValue(time, i, Y, curveType + BEZIER_SIZE * 2 - BEZIER)
 		end
 		end
 
 
 		if blend == MixBlend.setup then
 		if blend == MixBlend.setup then
-			local data = constraint.data;
-			constraint.mixRotate = data.mixRotate + (rotate - data.mixRotate) * alpha;
-			constraint.mixX = data.mixX + (x - data.mixX) * alpha;
-			constraint.mixY = data.mixY + (y - data.mixY) * alpha;
+			local data = constraint.data
+			constraint.mixRotate = data.mixRotate + (rotate - data.mixRotate) * alpha
+			constraint.mixX = data.mixX + (x - data.mixX) * alpha
+			constraint.mixY = data.mixY + (y - data.mixY) * alpha
 		else
 		else
-			constraint.mixRotate = constraint.mixRotate + (rotate - constraint.mixRotate) * alpha;
-			constraint.mixX = constraint.mixX + (x - constraint.mixX) * alpha;
-			constraint.mixY = constraint.mixY + (y - constraint.mixY) * alpha;
+			constraint.mixRotate = constraint.mixRotate + (rotate - constraint.mixRotate) * alpha
+			constraint.mixX = constraint.mixX + (x - constraint.mixX) * alpha
+			constraint.mixY = constraint.mixY + (y - constraint.mixY) * alpha
 		end
 		end
 	end
 	end
 
 

+ 106 - 124
spine-lua/spine-lua/AnimationState.lua

@@ -198,12 +198,22 @@ function TrackEntry:getAnimationTime ()
 	return math_min(self.trackTime + self.animationStart, self.animationEnd)
 	return math_min(self.trackTime + self.animationStart, self.animationEnd)
 end
 end
 
 
+function TrackEntry:getTrackComplete ()
+	local duration = self.animationEnd - self.animationStart
+	if duration ~= 0 then
+		if self.loop then return duration * (1 + math_floor(self.trackTime / duration)) end -- Completion of next loop.
+		if self.trackTime < duration then return duration end -- Before duration.
+	end
+	return self.trackTime -- Next update.
+end
+
 function TrackEntry:resetRotationDirections ()
 function TrackEntry:resetRotationDirections ()
 	self.timelinesRotation = {}
 	self.timelinesRotation = {}
 end
 end
 
 
 local AnimationState = {}
 local AnimationState = {}
 AnimationState.__index = AnimationState
 AnimationState.__index = AnimationState
+AnimationState.TrackEntry = TrackEntry
 
 
 function AnimationState.new (data)
 function AnimationState.new (data)
 	if not data then error("data cannot be nil", 2) end
 	if not data then error("data cannot be nil", 2) end
@@ -225,8 +235,6 @@ function AnimationState.new (data)
 	return self
 	return self
 end
 end
 
 
-AnimationState.TrackEntry = TrackEntry
-
 function AnimationState:update (delta)
 function AnimationState:update (delta)
 	delta = delta * self.timeScale
 	delta = delta * self.timeScale
 	local tracks = self.tracks
 	local tracks = self.tracks
@@ -359,31 +367,35 @@ function AnimationState:apply (skeleton)
 				-- Apply current entry.
 				-- Apply current entry.
 				local animationLast = current.animationLast
 				local animationLast = current.animationLast
 				local animationTime = current:getAnimationTime()
 				local animationTime = current:getAnimationTime()
+				local applyTime = animationTime
+				local applyEvents = self.events
+				if current.reverse then
+					applyTime = current.animation.duration - applyTime
+					applyEvents = nil
+				end
 				local timelines = current.animation.timelines
 				local timelines = current.animation.timelines
 				if (i == 0 and mix == 1) or blend == MixBlend.add then
 				if (i == 0 and mix == 1) or blend == MixBlend.add then
 					for i,timeline in ipairs(timelines) do
 					for i,timeline in ipairs(timelines) do
 						if timeline.type == Animation.TimelineType.attachment then
 						if timeline.type == Animation.TimelineType.attachment then
-							self:applyAttachmentTimeline(timeline, skeleton, animationTime, blend, true)
+							self:applyAttachmentTimeline(timeline, skeleton, applyTime, blend, true)
 						else
 						else
-							timeline:apply(skeleton, animationLast, animationTime, self.events, mix, blend, MixDirection.mixIn)
+							timeline:apply(skeleton, animationLast, applyTime, applyEvents, mix, blend, MixDirection.mixIn)
 						end
 						end
 					end
 					end
 				else
 				else
 					local timelineMode = current.timelineMode
 					local timelineMode = current.timelineMode
-					local firstFrame = #current.timelinesRotation == 0
-					local timelinesRotation = current.timelinesRotation
+					local firstFrame = #current.timelinesRotation ~= #timelines * 2
 
 
 					for ii,timeline in ipairs(timelines) do
 					for ii,timeline in ipairs(timelines) do
 						local timelineBlend = MixBlend.setup
 						local timelineBlend = MixBlend.setup
 						if timelineMode[ii] == SUBSEQUENT then timelineBlend = blend end
 						if timelineMode[ii] == SUBSEQUENT then timelineBlend = blend end
 
 
 						if timeline.type == Animation.TimelineType.rotate then
 						if timeline.type == Animation.TimelineType.rotate then
-							self:applyRotateTimeline(timeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, ii * 2,
-									firstFrame)
+							self:applyRotateTimeline(timeline, skeleton, applyTime, mix, timelineBlend, current.timelinesRotation, ii * 2, firstFrame)
 						elseif timeline.type == Animation.TimelineType.attachment then
 						elseif timeline.type == Animation.TimelineType.attachment then
-							self:applyAttachmentTimeline(timeline, skeleton, animationTime, timelineBlend, true)
+							self:applyAttachmentTimeline(timeline, skeleton, applyTime, timelineBlend, true)
 						else
 						else
-							timeline:apply(skeleton, animationLast, animationTime, self.events, mix, timelineBlend, MixDirection.mixIn)
+							timeline:apply(skeleton, animationLast, applyTime, applyEvents, mix, timelineBlend, MixDirection.mixIn)
 						end
 						end
 					end
 					end
 				end
 				end
@@ -401,7 +413,7 @@ function AnimationState:apply (skeleton)
 	-- the time is before the first key).
 	-- the time is before the first key).
 	local setupState = self.unkeyedState + SETUP
 	local setupState = self.unkeyedState + SETUP
 	local slots = skeleton.slots
 	local slots = skeleton.slots
-	for _, slot in ipairs(slots) do
+	for _,slot in ipairs(slots) do
 		if slot.attachmentState == setupState then
 		if slot.attachmentState == setupState then
 			local attachmentName = slot.data.attachmentName
 			local attachmentName = slot.data.attachmentName
 			if attachmentName == nil then
 			if attachmentName == nil then
@@ -432,25 +444,31 @@ function AnimationState:applyMixingFrom (to, skeleton, blend)
 		if blend ~= MixBlend.first then blend = from.mixBlend end
 		if blend ~= MixBlend.first then blend = from.mixBlend end
 	end
 	end
 
 
-	local events = nil
-	if mix < from.eventThreshold then events = self.events end
+	
 	local attachments = mix < from.attachmentThreshold
 	local attachments = mix < from.attachmentThreshold
 	local drawOrder = mix < from.drawOrderThreshold
 	local drawOrder = mix < from.drawOrderThreshold
-	local animationLast = from.animationLast
-	local animationTime = from:getAnimationTime()
 	local timelines = from.animation.timelines
 	local timelines = from.animation.timelines
 	local alphaHold = from.alpha * to.interruptAlpha
 	local alphaHold = from.alpha * to.interruptAlpha
 	local alphaMix = alphaHold * (1 - mix)
 	local alphaMix = alphaHold * (1 - mix)
+	local animationLast = from.animationLast
+	local animationTime = from:getAnimationTime()
+	local applyTime = animationTime
+	local events = nil
+	if from.reverse then
+		applyTime = from.animation.duration - applyTime
+	elseif mix < from.eventThreshold then
+		events = self.events
+	end
 
 
 	if blend == MixBlend.add then
 	if blend == MixBlend.add then
 		for i,timeline in ipairs(timelines) do
 		for i,timeline in ipairs(timelines) do
-			timeline:apply(skeleton, animationLast, animationTime, events, alphaMix, blend, MixDirection.mixOut)
+			timeline:apply(skeleton, animationLast, applyTime, events, alphaMix, blend, MixDirection.mixOut)
 		end
 		end
 	else
 	else
 		local timelineMode = from.timelineMode
 		local timelineMode = from.timelineMode
 		local timelineHoldMix = from.timelineHoldMix
 		local timelineHoldMix = from.timelineHoldMix
 
 
-		local firstFrame = #from.timelinesRotation == 0
+		local firstFrame = #from.timelinesRotation ~= #timelines
 		local timelinesRotation = from.timelinesRotation
 		local timelinesRotation = from.timelinesRotation
 
 
 		from.totalAlpha = 0
 		from.totalAlpha = 0
@@ -473,7 +491,7 @@ function AnimationState:applyMixingFrom (to, skeleton, blend)
 			elseif timelineMode[i] == HOLD_FIRST then
 			elseif timelineMode[i] == HOLD_FIRST then
 				timelineBlend = MixBlend.setup
 				timelineBlend = MixBlend.setup
 				alpha = alphaHold
 				alpha = alphaHold
-			else
+			else -- HOLD_MIX
 				timelineBlend = MixBlend.setup
 				timelineBlend = MixBlend.setup
 				local holdMix = timelineHoldMix[i]
 				local holdMix = timelineHoldMix[i]
 				alpha = alphaHold * math_max(0, 1 - holdMix.mixtime / holdMix.mixDuration)
 				alpha = alphaHold * math_max(0, 1 - holdMix.mixtime / holdMix.mixDuration)
@@ -482,14 +500,14 @@ function AnimationState:applyMixingFrom (to, skeleton, blend)
 			if not skipSubsequent then
 			if not skipSubsequent then
 				from.totalAlpha = from.totalAlpha + alpha
 				from.totalAlpha = from.totalAlpha + alpha
 				if timeline.type == Animation.TimelineType.rotate then
 				if timeline.type == Animation.TimelineType.rotate then
-					self:applyRotateTimeline(timeline, skeleton, animationTime, alpha, timelineBlend, timelinesRotation, i * 2, firstFrame)
+					self:applyRotateTimeline(timeline, skeleton, applyTime, alpha, timelineBlend, timelinesRotation, i * 2, firstFrame)
 				elseif timeline.type == Animation.TimelineType.attachment then
 				elseif timeline.type == Animation.TimelineType.attachment then
-					self:applyAttachmentTimeline(timeline, skeleton, animationTime, timelineBlend, attachments)
+					self:applyAttachmentTimeline(timeline, skeleton, applyTime, timelineBlend, attachments)
 				else
 				else
 					if drawOrder and timeline.type == Animation.TimelineType.drawOrder and timelineBlend == MixBlend.setup then
 					if drawOrder and timeline.type == Animation.TimelineType.drawOrder and timelineBlend == MixBlend.setup then
 						direction = MixDirection.mixIn
 						direction = MixDirection.mixIn
 					end
 					end
-					timeline:apply(skeleton, animationLast, animationTime, self.events, alpha, timelineBlend, direction)
+					timeline:apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction)
 				end
 				end
 			end
 			end
 		end
 		end
@@ -509,19 +527,12 @@ function AnimationState:applyAttachmentTimeline(timeline, skeleton, time, blend,
 	local slot = skeleton.slots[timeline.slotIndex]
 	local slot = skeleton.slots[timeline.slotIndex]
 	if slot.bone.active == false then return end
 	if slot.bone.active == false then return end
 
 
-	local frames = timeline.frames
-	if time < frames[0] then -- Time is before first frame.
+	if time < timeline.frames[0] then -- Time is before first frame.
 		if blend == MixBlend.setup or blend == MixBlend.first then
 		if blend == MixBlend.setup or blend == MixBlend.first then
 			self:setAttachment(skeleton, slot, slot.data.attachmentName, attachments)
 			self:setAttachment(skeleton, slot, slot.data.attachmentName, attachments)
 		end
 		end
 	else
 	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
-		end
-		self:setAttachment(skeleton, slot, timeline.attachmentNames[frameIndex], attachments)
+		self:setAttachment(skeleton, slot, timeline.attachmentNames[Timeline.search1(timeline.frames, time)], attachments)
 	end
 	end
 
 
 	-- If an attachment wasn't set (ie before the first frame or attachments is false), set the setup attachment later.
 	-- If an attachment wasn't set (ie before the first frame or attachments is false), set the setup attachment later.
@@ -548,10 +559,9 @@ function AnimationState:applyRotateTimeline (timeline, skeleton, time, alpha, bl
 		return
 		return
 	end
 	end
 
 
-	local rotateTimeline = timeline
-	local frames = rotateTimeline.frames
-	local bone = skeleton.bones[rotateTimeline.boneIndex]
+	local bone = skeleton.bones[timeline.boneIndex]
 	if not bone.active then return end
 	if not bone.active then return end
+	local frames = timeline.frames
 	local r1 = 0
 	local r1 = 0
 	local r2 = 0
 	local r2 = 0
 	if time < frames[0] then
 	if time < frames[0] then
@@ -570,21 +580,7 @@ function AnimationState:applyRotateTimeline (timeline, skeleton, time, alpha, bl
 		else
 		else
 			r1 = bone.rotation
 			r1 = bone.rotation
 		end
 		end
-		if time >= frames[zlen(frames) - Animation.RotateTimeline.ENTRIES] then -- Time is after last frame.
-			r2 = bone.data.rotation + frames[zlen(frames) + Animation.RotateTimeline.PREV_ROTATION]
-		else
-			-- Interpolate between the previous frame and the current frame.
-			local frame = Animation.binarySearch(frames, time, Animation.RotateTimeline.ENTRIES)
-			local prevRotation = frames[frame + Animation.RotateTimeline.PREV_ROTATION]
-			local frameTime = frames[frame]
-			local percent = rotateTimeline:getCurvePercent(math_floor(frame / 2) - 1,
-				1 - (time - frameTime) / (frames[frame + Animation.RotateTimeline.PREV_TIME] - frameTime))
-
-			r2 = frames[frame + Animation.RotateTimeline.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
+		r2 = bone.data.rotation + timeline:getCurveValue(time)
 	end
 	end
 
 
 	-- Mix between rotations using the direction of the shortest route on the first frame while detecting crosses.
 	-- Mix between rotations using the direction of the shortest route on the first frame while detecting crosses.
@@ -616,8 +612,7 @@ function AnimationState:applyRotateTimeline (timeline, skeleton, time, alpha, bl
 		timelinesRotation[i] = total
 		timelinesRotation[i] = total
 	end
 	end
 	timelinesRotation[i + 1] = diff
 	timelinesRotation[i + 1] = diff
-	r1 = r1 + total * alpha
-	bone.rotation = r1 - (16384 - math_floor(16384.499999999996 - r1 / 360)) * 360
+	bone.rotation = r1 + total * alpha
 end
 end
 
 
 function AnimationState:queueEvents (entry, animationTime)
 function AnimationState:queueEvents (entry, animationTime)
@@ -654,7 +649,7 @@ function AnimationState:queueEvents (entry, animationTime)
 	-- Queue events after complete.
 	-- Queue events after complete.
 	while i <= n do
 	while i <= n do
 		local event = events[i]
 		local event = events[i]
-		if not (event.time < animationStart) then --// Discard events outside animation start/end.
+		if event.time >= animationStart then -- Discard events outside animation start/end.
 			queue:event(entry, event)
 			queue:event(entry, event)
 		end
 		end
 		i = i + 1
 		i = i + 1
@@ -701,14 +696,17 @@ function AnimationState:clearTrack (trackIndex)
 	queue:drain()
 	queue:drain()
 end
 end
 
 
+function AnimationState:clearNext (entry)
+	self:disposeNext(entry.next)
+end
+
 function AnimationState:setCurrent (index, current, interrupt)
 function AnimationState:setCurrent (index, current, interrupt)
 	local from = self:expandToIndex(index)
 	local from = self:expandToIndex(index)
-	local tracks = self.tracks
-	local queue = self.queue
-	tracks[index] = current
+	self.tracks[index] = current
+	current.previous = nil
 
 
 	if from then
 	if from then
-		if interrupt then queue:interrupt(from) end
+		if interrupt then self.queue:interrupt(from) end
 		current.mixingFrom = from
 		current.mixingFrom = from
 		from.mixingTo = current
 		from.mixingTo = current
 		current.mixTime = 0
 		current.mixTime = 0
@@ -720,7 +718,7 @@ function AnimationState:setCurrent (index, current, interrupt)
 		from.timelinesRotation = {}
 		from.timelinesRotation = {}
 	end
 	end
 
 
-	queue:start(current)
+	self.queue:start(current)
 end
 end
 
 
 function AnimationState:setAnimationByName (trackIndex, animationName, loop)
 function AnimationState:setAnimationByName (trackIndex, animationName, loop)
@@ -779,19 +777,8 @@ function AnimationState:addAnimation (trackIndex, animation, loop, delay)
 		queue:drain()
 		queue:drain()
 	else
 	else
 		last.next = entry
 		last.next = entry
-		if delay <= 0 then
-			local duration = last.animationEnd - last.animationStart
-			if duration ~= 0 then
-				if last.loop then
-					delay = delay + duration * (1 + math_floor(last.trackTime / duration))
-				else
-					delay = delay + math_max(duration, last.trackTime)
-				end
-				delay = delay - data:getMix(last.animation, animation)
-			else
-				delay = last.trackTime
-			end
-		end
+		entry.previous = last
+		if delay <= 0 then delay = delay + last:getTrackComplete() - entry.mixDuration end
 	end
 	end
 
 
 	entry.delay = delay
 	entry.delay = delay
@@ -806,10 +793,14 @@ function AnimationState:setEmptyAnimation (trackIndex, mixDuration)
 end
 end
 
 
 function AnimationState:addEmptyAnimation (trackIndex, mixDuration, delay)
 function AnimationState:addEmptyAnimation (trackIndex, mixDuration, delay)
-	if delay <= 0 then delay = delay - mixDuration end
-	local entry = self:addAnimation(trackIndex, EMPTY_ANIMATION, false, delay)
+	local addDelay = 1
+	if delay > 0 then addDelay = delay end
+	local entry = self:addAnimation(trackIndex, EMPTY_ANIMATION, false, addDelay)
 	entry.mixDuration = mixDuration
 	entry.mixDuration = mixDuration
 	entry.trackEnd = mixDuration
 	entry.trackEnd = mixDuration
+	if delay <= 0 and entry.previous then
+		entry.delay = entry.previous:getTrackComplete() - entry.mixDuration + delay
+	end
 	return entry
 	return entry
 end
 end
 
 
@@ -895,28 +886,19 @@ function AnimationState:_animationsChanged ()
 	self.animationsChanged = false
 	self.animationsChanged = false
 
 
 	self.propertyIDs = {}
 	self.propertyIDs = {}
-
-	local highestIndex = -1
 	local tracks = self.tracks
 	local tracks = self.tracks
-	local numTracks = getNumTracks(tracks)
 	local i = 0
 	local i = 0
-	while i <= numTracks do
-		entry = tracks[i]
+	local n = zlen(tracks)
+	while i < n do
+		local entry = tracks[i]
 		if entry then
 		if entry then
-			if i > highestIndex then highestIndex = i end
-
-			if entry then
-				while entry.mixingFrom do
-					entry = entry.mixingFrom
-				end
-
-				repeat
-					if entry.mixingTo == nil or entry.mixBlend ~= MixBlend.add then
-						self:computeHold(entry)
-					end
-					entry = entry.mixingTo
-				until (entry == nil)
+			while entry.mixingFrom do
+				entry = entry.mixingFrom
 			end
 			end
+			repeat
+				if not entry.mixingTo or entry.mixBlend ~= MixBlend.add then self:computeHold(entry) end
+				entry = entry.mixingTo
+			until not entry
 		end
 		end
 		i = i + 1
 		i = i + 1
 	end
 	end
@@ -925,57 +907,57 @@ end
 function AnimationState:computeHold(entry)
 function AnimationState:computeHold(entry)
 	local to = entry.mixingTo
 	local to = entry.mixingTo
 	local timelines = entry.animation.timelines
 	local timelines = entry.animation.timelines
-	local timelinesCount = #entry.animation.timelines
 	local timelineMode = entry.timelineMode
 	local timelineMode = entry.timelineMode
 	local timelineHoldMix = entry.timelineHoldMix
 	local timelineHoldMix = entry.timelineHoldMix
 	local propertyIDs = self.propertyIDs
 	local propertyIDs = self.propertyIDs
 
 
 	if to and to.holdPrevious then
 	if to and to.holdPrevious then
-		local i = 1
-		while i <= timelinesCount do
-			local id = "" .. timelines[i]:getPropertyId()
-			if propertyIDs[id] == nil then
-				propertyIDs[id] = id
-				timelineMode[i] = HOLD_FIRST
-			else
-				timelineMode[i] = HOLD_SUBSEQUENT
+		for i,timeline in ipairs(timelines) do
+			local mode = HOLD_SUBSEQUENT
+			for _,id in ipairs(timeline.propertyIds) do
+				if not propertyIDs[id] then
+					propertyIDs[id] = true
+					mode = HOLD_FIRST
+				end
 			end
 			end
+			timelineMode[i] = mode
 		end
 		end
 		return
 		return
 	end
 	end
 
 
-	local i = 1
-	local skip
-	while i <= timelinesCount do
-		local id = "" .. timelines[i]:getPropertyId()
-		if propertyIDs[id] then
+	for i,timeline in ipairs(timelines) do
+		local ids = timeline.propertyIds
+		local added = false
+		for _,id in ipairs(ids) do
+			if not propertyIDs[id] then
+				propertyIDs[id] = true
+				added = true
+			end
+		end
+		if not added then
 			timelineMode[i] = SUBSEQUENT
 			timelineMode[i] = SUBSEQUENT
+		elseif not to
+			or timeline.type == Animation.TimelineType.attachment
+			or timeline.type == Animation.TimelineType.drawOrder
+			or timeline.type == Animation.TimelineType.event
+			or not to.animation:hasTimeline(ids) then
+			timelineMode[i] = FIRST
 		else
 		else
-			propertyIDs[id] = id
-			local timeline = timelines[i]
-			if to == nil or timeline.type == Animation.TimelineType.attachment
-				or timeline.type == Animation.TimelineType.drawOrder
-				or timeline.type == Animation.TimelineType.event
-				or not to.animation:hasTimeline(id) then
-				timelineMode[i] = FIRST
-			else
-				local next = to.mixingTo
-				skip = false
-				while next do
-					if not next.animation:hasTimeline(id) then
-						if entry.mixDuration > 0 then
-							timelineMode[i] = HOLD_MIX
-							timelineHoldMix[i] = next
-							skip = true
-							break
-						end
+			local next = to.mixingTo
+			local set
+			while next do
+				if not next.animation:hasTimeline(ids) then
+					if next.mixDuration > 0 then
+						timelineMode[i] = HOLD_MIX
+						timelineHoldMix[i] = next
+						set = true
 					end
 					end
-					next = next.mixingTo
+					break
 				end
 				end
-				if not skip then 	timelineMode[i] = HOLD_FIRST end
+				next = next.mixingTo
 			end
 			end
+			if not set then timelineMode[i] = HOLD_FIRST end
 		end
 		end
-		i = i + 1
 	end
 	end
 end
 end
 
 

+ 0 - 104
spine-lua/spine-lua/Atlas.lua

@@ -1,104 +0,0 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local Atlas = {}
-
-function Atlas.parse(atlasPath, atlasBase)
-	local function parseIntTuple4( l )
-		local a,b,c,d = string.match( l , " ? ?%a+: ([+-]?%d+), ?([+-]?%d+), ?([+-]?%d+), ?([+-]?%d+)" )
-		a,b,c,d = tonumber( a ), tonumber( b ), tonumber( c ), tonumber( d )
-		return a and b and c and d and {a, b, c ,d}
-	end
-
-	local function parseIntTuple2( l )
-		local a,b = string.match( l , " ? ?%a+: ([+-]?%d+), ?([+-]?%d+)" )
-		a,b = tonumber( a ), tonumber( b )
-		return a and b and {a, b}
-	end
-
-	if not atlasPath then
-		error("Error: " .. atlasPath .. ".atlas" .. " doesn't exist!", 2)
-		return nil
-	end
-
-	local atlasLines = spine.utils.readFile( atlasPath, atlasBase )
-	if not atlasLines then
-		error("Error: " .. atlasPath .. ".atlas" .. " unable to read!", 2)
-		return nil
-	end
-
-	local pages = {}
-
-
-	local it = string.gmatch(atlasLines, "(.-)\r?\n") -- iterate over lines
-	for l in it do
-		if #l == 0 then
-			l = it()
-			if l then
-				local page = { name = l }
-				l = it()
-				page.size = parseIntTuple2( l )
-				if page.size then
-					l = it()
-				end
-				page.format = string.match( l, "%a+: (.+)" )
-				page.filter = {string.match( it(), "%a+: (.+),(.+)" )}
-				page.wrap = string.match( it(), "%a+: (.+)" )
-				page.regions = {}
-				table.insert( pages, page )
-			else
-				break
-			end
-		else
-			local region = {name = l}
-
-			region.rotate = string.match( it(), "%a+: (.+)" ) == "true"
-			region.xy = parseIntTuple2( it() )
-			region.size = parseIntTuple2( it() )
-			l = it()
-			region.splits = parseIntTuple4(l)
-			if region.splits then
-				l = it()
-				region.pad = parseIntTuple4(l)
-				if region.pad then
-					l = it()
-				end
-			end
-			region.orig = parseIntTuple2( l )
-			region.offset = parseIntTuple2( it() )
-			region.index = tonumber( string.match( it() , "%a+: ([+-]?%d+)" ) )
-
-			table.insert( pages[#pages].regions, region )
-		end
-	end
-
-	return pages
-end
-
-return Atlas

+ 8 - 26
spine-lua/spine-lua/Bone.lua

@@ -27,6 +27,8 @@
 -- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 -- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 -------------------------------------------------------------------------------
 -------------------------------------------------------------------------------
 
 
+local TransformMode = require "spine-lua.TransformMode"
+
 local setmetatable = setmetatable
 local setmetatable = setmetatable
 local math_rad = math.rad
 local math_rad = math.rad
 local math_deg = math.deg
 local math_deg = math.deg
@@ -37,19 +39,6 @@ local math_sqrt = math.sqrt
 local math_abs = math.abs
 local math_abs = math.abs
 local math_pi = math.pi
 local math_pi = math.pi
 
 
-local TransformMode = require "spine-lua.TransformMode"
-
-function math.sign(x)
-	if x < 0 then
-		return -1
-	elseif x > 0 then
-		return 1
-	end
-	return 0
-end
-
-local math_sign = math.sign
-
 local Bone = {}
 local Bone = {}
 Bone.__index = Bone
 Bone.__index = Bone
 
 
@@ -64,7 +53,6 @@ function Bone.new (data, skeleton, parent)
 		children = { },
 		children = { },
 		x = 0, y = 0, rotation = 0, scaleX = 1, scaleY = 1, shearX = 0, shearY = 0,
 		x = 0, y = 0, rotation = 0, scaleX = 1, scaleY = 1, shearX = 0, shearY = 0,
 		ax = 0, ay = 0, arotation = 0, ascaleX = 0, ascaleY = 0, ashearX = 0, ashearY = 0,
 		ax = 0, ay = 0, arotation = 0, ascaleX = 0, ascaleY = 0, ashearX = 0, ashearY = 0,
-		appliedValid = false,
 
 
 		a = 0, b = 0, worldX = 0, -- a b x
 		a = 0, b = 0, worldX = 0, -- a b x
 		c = 0, d = 0, worldY = 0, -- c d y
 		c = 0, d = 0, worldY = 0, -- c d y
@@ -78,7 +66,7 @@ function Bone.new (data, skeleton, parent)
 end
 end
 
 
 function Bone:update ()
 function Bone:update ()
-	self:updateWorldTransformWith(self.x, self.y, self.rotation, self.scaleX, self.scaleY, self.shearX, self.shearY)
+	self:updateWorldTransformWith(self.ax, self.ay, self.arotation, self.ascaleX, self.ascaleY, self.ashearX, self.ashearY)
 end
 end
 
 
 function Bone:updateWorldTransform ()
 function Bone:updateWorldTransform ()
@@ -93,13 +81,12 @@ function Bone:updateWorldTransformWith (x, y, rotation, scaleX, scaleY, shearX,
 	self.ascaleY = scaleY
 	self.ascaleY = scaleY
 	self.ashearX = shearX
 	self.ashearX = shearX
 	self.ashearY = shearY
 	self.ashearY = shearY
-	self.appliedValid = true
 
 
 	local sx = self.skeleton.scaleX
 	local sx = self.skeleton.scaleX
 	local sy = self.skeleton.scaleY
 	local sy = self.skeleton.scaleY
 
 
 	local parent = self.parent
 	local parent = self.parent
-	if parent == nil then
+	if not parent then
 		local rotationY = rotation + 90 + shearY
 		local rotationY = rotation + 90 + shearY
 		local rotationRad = math_rad(rotation + shearX)
 		local rotationRad = math_rad(rotation + shearX)
 		local rotationYRad = math_rad(rotationY)
 		local rotationYRad = math_rad(rotationY)
@@ -225,7 +212,7 @@ end
 
 
 function Bone:updateAppliedTransform ()
 function Bone:updateAppliedTransform ()
 	local parent = self.parent
 	local parent = self.parent
-	if parent == nil then
+	if not parent then
 		self.ax = self.worldX
 		self.ax = self.worldX
 		self.ay = self.worldY
 		self.ay = self.worldY
 		self.arotation = math_deg(math_atan2(self.c, self.a))
 		self.arotation = math_deg(math_atan2(self.c, self.a))
@@ -268,15 +255,11 @@ function Bone:updateAppliedTransform ()
 end
 end
 
 
 function Bone:worldToLocal (world)
 function Bone:worldToLocal (world)
-	local a = self.a
-	local b = self.b
-	local c = self.c
-	local d = self.d
-	local invDet = 1 / (a * d - b * c)
+	local invDet = 1 / (self.a * self.d - self.b * self.c)
 	local x = world[1] - self.worldX
 	local x = world[1] - self.worldX
 	local y = world[2] - self.worldY
 	local y = world[2] - self.worldY
-	world[1] = (x * d * invDet - y * b * invDet)
-	world[2] = (y * a * invDet - x * c * invDet)
+	world[1] = x * self.d * invDet - y * self.b * invDet
+	world[2] = y * self.a * invDet - x * self.c * invDet
 	return world
 	return world
 end
 end
 
 
@@ -313,7 +296,6 @@ function Bone:rotateWorld (degrees)
 	self.b = cos * b - sin * d
 	self.b = cos * b - sin * d
 	self.c = sin * a + cos * c
 	self.c = sin * a + cos * c
 	self.d = sin * b + cos * d
 	self.d = sin * b + cos * d
-	self.appliedValid = false
 end
 end
 
 
 return Bone
 return Bone

+ 1 - 8
spine-lua/spine-lua/IkConstraint.lua

@@ -76,6 +76,7 @@ function IkConstraint:apply ()
 end
 end
 
 
 function IkConstraint:update ()
 function IkConstraint:update ()
+	if self.mix == 0 then return end
 	local target = self.target
 	local target = self.target
 	local bones = self.bones
 	local bones = self.bones
 	local boneCount = #bones
 	local boneCount = #bones
@@ -87,9 +88,7 @@ function IkConstraint:update ()
 end
 end
 
 
 function IkConstraint:apply1 (bone, targetX, targetY, compress, stretch, uniform, alpha)
 function IkConstraint:apply1 (bone, targetX, targetY, compress, stretch, uniform, alpha)
-	if not bone.appliedValid then bone:updateAppliedTransform() end
 	local p = bone.parent
 	local p = bone.parent
-
 	local pa = p.a
 	local pa = p.a
 	local pb = p.b
 	local pb = p.b
 	local pc = p.c
 	local pc = p.c
@@ -148,12 +147,6 @@ function IkConstraint:apply1 (bone, targetX, targetY, compress, stretch, uniform
 	end
 	end
 
 
 function IkConstraint:apply2 (parent, child, targetX, targetY, bendDir, stretch, softness, alpha)
 function IkConstraint:apply2 (parent, child, targetX, targetY, bendDir, stretch, softness, alpha)
-	if alpha == 0 then
-		child:updateWorldTransform()
-		return
-	end
-	if not parent.appliedValid then parent:updateAppliedTransform() end
-	if not child.appliedValid then child:updateAppliedTransform() end
 	local px = parent.ax
 	local px = parent.ax
 	local py = parent.ay
 	local py = parent.ay
 	local psx = parent.ascaleX
 	local psx = parent.ascaleX

+ 110 - 80
spine-lua/spine-lua/PathConstraint.lua

@@ -36,7 +36,7 @@ local AttachmentType = require "spine-lua.attachments.AttachmentType"
 local PathConstraintData = require "spine-lua.PathConstraintData"
 local PathConstraintData = require "spine-lua.PathConstraintData"
 local utils = require "spine-lua.utils"
 local utils = require "spine-lua.utils"
 local math_pi = math.pi
 local math_pi = math.pi
-local math_pi2 = math.pi * 2
+local math_pi2 = math_pi * 2
 local math_atan2 = math.atan2
 local math_atan2 = math.atan2
 local math_sqrt = math.sqrt
 local math_sqrt = math.sqrt
 local math_acos = math.acos
 local math_acos = math.acos
@@ -51,10 +51,10 @@ local math_max = math.max
 local PathConstraint = {}
 local PathConstraint = {}
 PathConstraint.__index = PathConstraint
 PathConstraint.__index = PathConstraint
 
 
-PathConstraint.NONE = -1
-PathConstraint.BEFORE = -2
-PathConstraint.AFTER = -3
-PathConstraint.epsilon = 0.00001
+local NONE = -1
+local BEFORE = -2
+local AFTER = -3
+local epsilon = 0.00001
 
 
 function PathConstraint.new (data, skeleton)
 function PathConstraint.new (data, skeleton)
 	if not data then error("data cannot be nil", 2) end
 	if not data then error("data cannot be nil", 2) end
@@ -66,8 +66,9 @@ function PathConstraint.new (data, skeleton)
 		target = skeleton:findSlot(data.target.name),
 		target = skeleton:findSlot(data.target.name),
 		position = data.position,
 		position = data.position,
 		spacing = data.spacing,
 		spacing = data.spacing,
-		rotateMix = data.rotateMix,
-		translateMix = data.translateMix,
+		mixRotate = data.mixRotate,
+		mixX = data.mixX,
+		mixY = data.mixY,
 		spaces = {},
 		spaces = {},
 		positions = {},
 		positions = {},
 		world = {},
 		world = {},
@@ -85,81 +86,113 @@ function PathConstraint.new (data, skeleton)
 	return self
 	return self
 end
 end
 
 
-function PathConstraint:apply ()
-	self:update()
-end
-
 function PathConstraint:update ()
 function PathConstraint:update ()
 	local attachment = self.target.attachment
 	local attachment = self.target.attachment
-	if not attachment or not (attachment.type == AttachmentType.path) then return end
-
-	local rotateMix = self.rotateMix
-	local translateMix = self.translateMix
-	local translate = translateMix > 0
-	local rotate = rotateMix > 0
-	if not translate and not rotate then return end
+	if not attachment or attachment.type ~= AttachmentType.path then return end
 
 
+	local mixRotate = self.mixRotate
+	local mixX = self.mixX
+	local mixY = self.mixY
+	if mixRotate == 0 and mixX == 0 and mixY == 0 then return end
+	
 	local data = self.data
 	local data = self.data
-	local percentSpacing = data.spacingMode == PathConstraintData.SpacingMode.percent
-	local rotateMode = data.rotateMode
 	local tangents = rotateMode == PathConstraintData.RotateMode.tangent
 	local tangents = rotateMode == PathConstraintData.RotateMode.tangent
 	local scale = rotateMode == PathConstraintData.RotateMode.chainscale
 	local scale = rotateMode == PathConstraintData.RotateMode.chainscale
+
 	local bones = self.bones
 	local bones = self.bones
 	local boneCount = #bones
 	local boneCount = #bones
-	local spacesCount = boneCount + 1
-	if tangents then spacesCount = boneCount end
+	local spacesCount = boneCount
+	if tangents then spacesCount = spacesCount + 1 end
 	local spaces = utils.setArraySize(self.spaces, spacesCount)
 	local spaces = utils.setArraySize(self.spaces, spacesCount)
 	local lengths = nil
 	local lengths = nil
+	if scale then lengths = Utils.setArraySize(this.lengths, boneCount) end
 	local spacing = self.spacing
 	local spacing = self.spacing
-	if scale or not percentSpacing then
-		if scale then lengths = utils.setArraySize(self.lengths, boneCount) end
-		local lengthSpacing = data.spacingMode == PathConstraintData.SpacingMode.length
+
+	if data.spacingMode == PathConstraintData.SpacingMode.percent then
+		if scale then
+			local i = 0
+			local n = spacesCount - 1
+			while i < n do
+				local bone = bones[i]
+				local setupLength = bone.data.length
+				if setupLength < epsilon then
+					lengths[i] = 0
+				else
+					local x = setupLength * bone.a
+					local y = setupLength * bone.c
+					lengths[i] = math_sqrt(x * x + y * y)
+				end
+				i = i + 1
+			end
+		end
+		local i = 1
+		while i < spacesCount do
+			spaces[i] = spacing
+			i = i + 1
+		end
+	elseif data.spacingMode == PathConstraintData.SpacingMode.proportional then
+		local sum = 0
 		local i = 0
 		local i = 0
+		while i < boneCount do
+			local bone = bones[i]
+			local setupLength = bone.data.length
+			if setupLength < epsilon then
+				if scale then lengths[i] = 0 end
+				i = i + 1
+				spaces[i] = spacing
+			else
+				local x = setupLength * bone.a
+				local y = setupLength * bone.c
+				local length = math_sqrt(x * x + y * y)
+				if scale then lengths[i] = length end
+				i = i + 1
+				spaces[i] = length
+				sum = sum + length
+			end
+		end
+		if sum > 0 then
+			sum = spacesCount / sum * spacing
+			local i = 1
+			while i < spacesCount do
+				spaces[i] = spaces[i] * sum
+				i = i + 1
+			end
+		end
+	else
+		local lengthSpacing = data.spacingMode == PathConstraintData.SpacingMode.length
+		local i = 1
 		local n = spacesCount - 1
 		local n = spacesCount - 1
 		while i < n do
 		while i < n do
-			local bone = bones[i + 1]
+			local bone = bones[i]
 			local setupLength = bone.data.length
 			local setupLength = bone.data.length
-			if setupLength < PathConstraint.epsilon then
-				if scale then lengths[i + 1] = 0 end
+			if setupLength < epsilon then
+				if scale then lengths[i] = 0 end
 				i = i + 1
 				i = i + 1
-				spaces[i + 1] = 0
-			elseif percentSpacing then
-				if scale then
-					local x = setupLength * bone.a
-					local y = setupLength * bone.c
-					local length = math_sqrt(x * x + y * y)
-					lengths[i + 1] = length
-				end
-				i = i + 1
-				spaces[i + 1] = spacing
+				spaces[i] = spacing
 			else
 			else
-	 			local x = setupLength * bone.a
+				local x = setupLength * bone.a
 				local y = setupLength * bone.c
 				local y = setupLength * bone.c
 				local length = math_sqrt(x * x + y * y)
 				local length = math_sqrt(x * x + y * y)
-				if scale then lengths[i + 1] = length end
+				if scale then lengths[i] = length end
 				i = i + 1
 				i = i + 1
+				local s
 				if lengthSpacing then
 				if lengthSpacing then
-					spaces[i + 1] = (setupLength + spacing) * length / setupLength
+					s = setupLength + spacing
 				else
 				else
-					spaces[i + 1] = spacing * length / setupLength
+					s = spacing
 				end
 				end
+				spaces[i] = s * length / setupLength
 			end
 			end
 		end
 		end
-	else
-		local i = 1
-		while i < spacesCount do
-			spaces[i + 1] = spacing
-			i = i + 1
-		end
 	end
 	end
 
 
-	local positions = self:computeWorldPositions(attachment, spacesCount, tangents, data.positionMode == PathConstraintData.PositionMode.percent, percentSpacing)
+	local positions = self:computeWorldPositions(attachment, spacesCount, tangents)
 	local boneX = positions[1]
 	local boneX = positions[1]
 	local boneY = positions[2]
 	local boneY = positions[2]
 	local offsetRotation = data.offsetRotation
 	local offsetRotation = data.offsetRotation
 	local tip = false
 	local tip = false
 	if offsetRotation == 0 then
 	if offsetRotation == 0 then
-			tip = rotateMode == PathConstraintData.RotateMode.chain
+		tip = data.rotateMode == PathConstraintData.RotateMode.chain
 	else
 	else
 		tip = false
 		tip = false
 		local p = self.target.bone
 		local p = self.target.bone
@@ -174,8 +207,8 @@ function PathConstraint:update ()
 	local p = 3
 	local p = 3
 	while i < boneCount do
 	while i < boneCount do
 		local bone = bones[i + 1]
 		local bone = bones[i + 1]
-		bone.worldX = bone.worldX + (boneX - bone.worldX) * translateMix
-		bone.worldY = bone.worldY + (boneY - bone.worldY) * translateMix
+		bone.worldX = bone.worldX + (boneX - bone.worldX) * mixX
+		bone.worldY = bone.worldY + (boneY - bone.worldY) * mixY
 		local x = positions[p + 1]
 		local x = positions[p + 1]
 		local y = positions[p + 2]
 		local y = positions[p + 2]
 		local dx = x - boneX
 		local dx = x - boneX
@@ -183,14 +216,14 @@ function PathConstraint:update ()
 		if scale then
 		if scale then
 			local length = lengths[i + 1]
 			local length = lengths[i + 1]
 			if length ~= 0 then
 			if length ~= 0 then
-				local s = (math_sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1
+				local s = (math_sqrt(dx * dx + dy * dy) / length - 1) * mixRotate + 1
 				bone.a = bone.a * s
 				bone.a = bone.a * s
 				bone.c = bone.c * s
 				bone.c = bone.c * s
 			end
 			end
 		end
 		end
 		boneX = x
 		boneX = x
 		boneY = y
 		boneY = y
-		if rotate then
+		if mixRotate then
 			local a = bone.a
 			local a = bone.a
 			local b = bone.b
 			local b = bone.b
 			local c = bone.c
 			local c = bone.c
@@ -210,8 +243,8 @@ function PathConstraint:update ()
 				cos = math_cos(r)
 				cos = math_cos(r)
 				sin = math_sin(r)
 				sin = math_sin(r)
 				local length = bone.data.length
 				local length = bone.data.length
-				boneX = boneX + (length * (cos * a - sin * c) - dx) * rotateMix
-				boneY = boneY + (length * (sin * a + cos * c) - dy) * rotateMix
+				boneX = boneX + (length * (cos * a - sin * c) - dx) * mixRotate
+				boneY = boneY + (length * (sin * a + cos * c) - dy) * mixRotate
 			else
 			else
 				r = r + offsetRotation
 				r = r + offsetRotation
 			end
 			end
@@ -220,21 +253,21 @@ function PathConstraint:update ()
 			elseif r < -math_pi then
 			elseif r < -math_pi then
 				r = r + math_pi2
 				r = r + math_pi2
 			end
 			end
-			r = r * rotateMix
+			r = r * mixRotate
 			cos = math_cos(r)
 			cos = math_cos(r)
-			sin = math.sin(r)
+			sin = math_sin(r)
 			bone.a = cos * a - sin * c
 			bone.a = cos * a - sin * c
 			bone.b = cos * b - sin * d
 			bone.b = cos * b - sin * d
 			bone.c = sin * a + cos * c
 			bone.c = sin * a + cos * c
 			bone.d = sin * b + cos * d
 			bone.d = sin * b + cos * d
 		end
 		end
-		bone.appliedValid = false
+		bone:updateAppliedTransform()
 		i = i + 1
 		i = i + 1
 		p = p + 3
 		p = p + 3
 	end
 	end
 end
 end
 
 
-function PathConstraint:computeWorldPositions (path, spacesCount, tangents, percentPosition, percentSpacing)
+function PathConstraint:computeWorldPositions (path, spacesCount, tangents)
 	local target = self.target
 	local target = self.target
 	local position = self.position
 	local position = self.position
 	local spaces = self.spaces
 	local spaces = self.spaces
@@ -250,20 +283,20 @@ function PathConstraint:computeWorldPositions (path, spacesCount, tangents, perc
 		local lengths = path.lengths
 		local lengths = path.lengths
 		if closed then curveCount = curveCount - 1 else curveCount = curveCount - 2 end
 		if closed then curveCount = curveCount - 1 else curveCount = curveCount - 2 end
 		local pathLength = lengths[curveCount + 1]
 		local pathLength = lengths[curveCount + 1]
-		if percentPosition then position = position * pathLength end
-		if percentSpacing then
-			i = 1
-			while i < spacesCount do
-				spaces[i + 1] = spaces[i + 1] * pathLength
-				i = i + 1
-			end
+		if self.data.positionMode == PathConstraintData.PositionMode.percent then position = position * pathLength end
+
+		local multiplier = 1
+		if self.data.spacingMode == PathConstraintData.SpacingMode.percent then
+			multiplier = pathLength
+		elseif self.data.spacingMode == PathConstraintData.SpacingMode.proportional then
+			multiplier = pathLength / spacesCount
 		end
 		end
 		world = utils.setArraySize(self.world, 8)
 		world = utils.setArraySize(self.world, 8)
 		i = 0
 		i = 0
 		local o = 0
 		local o = 0
 		local curve = 0
 		local curve = 0
 		while i < spacesCount do
 		while i < spacesCount do
-			local space = spaces[i + 1]
+			local space = spaces[i + 1] * multiplier
 			position = position + space
 			position = position + space
 			local p = position
 			local p = position
 
 
@@ -389,17 +422,14 @@ function PathConstraint:computeWorldPositions (path, spacesCount, tangents, perc
 		i = i + 1
 		i = i + 1
 		w = w + 6
 		w = w + 6
 	end
 	end
-	if percentPosition then
-		position = position * pathLength
-	else
-		position = position * pathLength / path.lengths[curveCount]
-	end
-	if percentSpacing then
-		i = 1
-		while i < spacesCount do
-			spaces[i + 1] = spaces[i + 1] * pathLength
-			i = i + 1
-		end
+
+	if self.data.positionMode == PathConstraintData.PositionMode.percent then position = position * pathLength end
+
+	local multiplier = 1
+	if self.data.spacingMode == PathConstraintData.SpacingMode.percent then
+		multiplier = pathLength
+	elseif self.data.spacingMode == PathConstraintData.SpacingMode.proportional then
+		multiplier = pathLength / spacesCount
 	end
 	end
 
 
 	local segments = self.segments
 	local segments = self.segments
@@ -409,7 +439,7 @@ function PathConstraint:computeWorldPositions (path, spacesCount, tangents, perc
 	local curve = 0
 	local curve = 0
 	local segment = 0
 	local segment = 0
 	while i < spacesCount do
 	while i < spacesCount do
-		local space = spaces[i + 1]
+		local space = spaces[i + 1] * multiplier
 		position = position + space
 		position = position + space
 		local p = position
 		local p = position
 
 

+ 5 - 3
spine-lua/spine-lua/PathConstraintData.lua

@@ -43,8 +43,9 @@ function PathConstraintData.new (name)
 		offsetRotation = 0,
 		offsetRotation = 0,
 		position = 0,
 		position = 0,
 		spacing = 0,
 		spacing = 0,
-		rotateMix = 0,
-		translateMix = 0
+		mixRotate = 0,
+		mixX = 0,
+		mixY = 0
 	}
 	}
 
 
 	return self
 	return self
@@ -58,7 +59,8 @@ PathConstraintData.PositionMode = {
 PathConstraintData.SpacingMode = {
 PathConstraintData.SpacingMode = {
 	length = 0,
 	length = 0,
 	fixed = 1,
 	fixed = 1,
-	percent = 2
+	percent = 2,
+	proportional = 3
 }
 }
 
 
 PathConstraintData.RotateMode = {
 PathConstraintData.RotateMode = {

+ 23 - 30
spine-lua/spine-lua/Skeleton.lua

@@ -59,7 +59,6 @@ function Skeleton.new (data)
 		transformConstraints = {},
 		transformConstraints = {},
 		pathConstraints = {},
 		pathConstraints = {},
 		_updateCache = {},
 		_updateCache = {},
-		updateCacheReset = {},
 		skin = nil,
 		skin = nil,
 		color = Color.newWith(1, 1, 1, 1),
 		color = Color.newWith(1, 1, 1, 1),
 		time = 0,
 		time = 0,
@@ -110,7 +109,6 @@ end
 function Skeleton:updateCache ()
 function Skeleton:updateCache ()
 	local updateCache = {}
 	local updateCache = {}
 	self._updateCache = updateCache
 	self._updateCache = updateCache
-	self.updateCacheReset = {}
 
 
 	local bones = self.bones
 	local bones = self.bones
 	for _, bone in ipairs(bones) do
 	for _, bone in ipairs(bones) do
@@ -122,11 +120,11 @@ function Skeleton:updateCache ()
 		local skinBones = self.skin.bones
 		local skinBones = self.skin.bones
 		for i, boneData in ipairs(skinBones) do
 		for i, boneData in ipairs(skinBones) do
 			local bone = bones[boneData.index]
 			local bone = bones[boneData.index]
-			while bone do
+			repeat
 				bone.sorted = false
 				bone.sorted = false
 				bone.active = true
 				bone.active = true
 				bone = bone.parent
 				bone = bone.parent
-			end
+			until not bone
 		end
 		end
 	end
 	end
 
 
@@ -196,22 +194,18 @@ function Skeleton:sortIkConstraint (constraint)
 	local parent = constrained[1]
 	local parent = constrained[1]
 	self:sortBone(parent)
 	self:sortBone(parent)
 
 
-	if #constrained > 1 then
+	if #constrained == 1 then
+		table_insert(self._updateCache, constraint)
+		self:sortReset(parent.children)
+	else
 		local child = constrained[#constrained]
 		local child = constrained[#constrained]
-		local contains = false
-		for _, updatable in ipairs(self._updateCache) do
-			if updatable == child then
-				contains = true
-				break
-			end
-		end
-		if not contains then table_insert(self.updateCacheReset, child) end
-	end
+		self:sortBone(child)
 
 
-	table_insert(self._updateCache, constraint)
+		table_insert(self._updateCache, constraint)
 
 
-	self:sortReset(parent.children)
-	constrained[#constrained].sorted = true
+		self:sortReset(parent.children)
+		child.sorted = true
+	end
 end
 end
 
 
 function Skeleton:sortPathConstraint(constraint)
 function Skeleton:sortPathConstraint(constraint)
@@ -266,7 +260,7 @@ function Skeleton:sortTransformConstraint(constraint)
 					break
 					break
 				end
 				end
 			end
 			end
-			if not contains then table_insert(self.updateCacheReset, child) end
+			self:sortBone(child)
 		end
 		end
 	else
 	else
 		for _,bone in ipairs(constrained) do
 		for _,bone in ipairs(constrained) do
@@ -279,7 +273,6 @@ function Skeleton:sortTransformConstraint(constraint)
 	for _,bone in ipairs(constrained) do
 	for _,bone in ipairs(constrained) do
 		self:sortReset(bone.children)
 		self:sortReset(bone.children)
 	end
 	end
-
 	for _,bone in ipairs(constrained) do
 	for _,bone in ipairs(constrained) do
 		bone.sorted = true
 		bone.sorted = true
 	end
 	end
@@ -333,8 +326,7 @@ end
 
 
 -- Updates the world transform for each bone and applies IK constraints.
 -- Updates the world transform for each bone and applies IK constraints.
 function Skeleton:updateWorldTransform ()
 function Skeleton:updateWorldTransform ()
-	local updateCacheReset = self.updateCacheReset
-	for _,bone in ipairs(updateCacheReset) do
+	for _,bone in ipairs(self.bones) do
 		bone.ax = bone.x
 		bone.ax = bone.x
 		bone.ay = bone.y
 		bone.ay = bone.y
 		bone.arotation = bone.rotation
 		bone.arotation = bone.rotation
@@ -342,11 +334,9 @@ function Skeleton:updateWorldTransform ()
 		bone.ascaleY = bone.scaleY
 		bone.ascaleY = bone.scaleY
 		bone.ashearX = bone.shearX
 		bone.ashearX = bone.shearX
 		bone.ashearY = bone.shearY
 		bone.ashearY = bone.shearY
-		bone.appliedValid = true
 	end
 	end
 
 
-	local updateCache = self._updateCache
-	for _, updatable in ipairs(updateCache) do
+	for _, updatable in ipairs(self._updateCache) do
 		updatable:update()
 		updatable:update()
 	end
 	end
 end
 end
@@ -372,10 +362,12 @@ function Skeleton:setBonesToSetupPose ()
 	local transformConstraints = self.transformConstraints
 	local transformConstraints = self.transformConstraints
 	for _, constraint in ipairs(transformConstraints) do
 	for _, constraint in ipairs(transformConstraints) do
 		local data = constraint.data
 		local data = constraint.data
-		constraint.rotateMix = data.rotateMix
-		constraint.translateMix = data.translateMix
-		constraint.scaleMix = data.scaleMix
-		constraint.shearMix = data.shearMix
+		constraint.mixRotate = data.mixRotate
+		constraint.mixX = data.mixX
+		constraint.mixY = data.mixY
+		constraint.mixScaleX = data.mixScaleX
+		constraint.mixScaleY = data.mixScaleY
+		constraint.mixShearY = data.mixShearY
 	end
 	end
 
 
 	local pathConstraints = self.pathConstraints
 	local pathConstraints = self.pathConstraints
@@ -383,8 +375,9 @@ function Skeleton:setBonesToSetupPose ()
 		local data = constraint.data
 		local data = constraint.data
 		constraint.position = data.position
 		constraint.position = data.position
 		constraint.spacing = data.spacing
 		constraint.spacing = data.spacing
-		constraint.rotateMix = data.rotateMix
-		constraint.translateMix = data.translateMix
+		constraint.mixRotate = data.mixRotate
+		constraint.mixX = data.mixX
+		constraint.mixY = data.mixY
 	end
 	end
 end
 end
 
 

+ 12 - 11
spine-lua/spine-lua/SkeletonJson.lua

@@ -558,9 +558,9 @@ function SkeletonJson.new (attachmentLoader)
 				for timelineName,timelineMap in pairs(slotMap) do
 				for timelineName,timelineMap in pairs(slotMap) do
 					if not timelineMap then
 					if not timelineMap then
 					elseif timelineName == "attachment" then
 					elseif timelineName == "attachment" then
-						local timeline = Animation.AttachmentTimeline.new(#timelineMap, slotIndex)
+						local timeline = Animation.AttachmentTimeline.new(#timelineMap, #timelineMap, slotIndex)
 						for i,keyMap in ipairs(timelineMap) do
 						for i,keyMap in ipairs(timelineMap) do
-							timeline:setFrame(i + 1, getValue(keyMap, "time", 0), keyMap["name"])
+							timeline:setFrame(i - 1, getValue(keyMap, "time", 0), keyMap["name"])
 						end
 						end
 						table_insert(timelines, timeline)
 						table_insert(timelines, timeline)
 					elseif timelineName == "rgba" then
 					elseif timelineName == "rgba" then
@@ -765,7 +765,7 @@ function SkeletonJson.new (attachmentLoader)
 						table_insert(timelines, readTimeline1(timelineMap, timeline, 0, scale))
 						table_insert(timelines, readTimeline1(timelineMap, timeline, 0, scale))
 					elseif timelineName == "scale" then
 					elseif timelineName == "scale" then
 						local timeline = Animation.ScaleTimeline.new(#timelineMap, #timelineMap * 2, boneIndex)
 						local timeline = Animation.ScaleTimeline.new(#timelineMap, #timelineMap * 2, boneIndex)
-						table_insert(timelines, readTimeline2(timelineMap, "x", "y", 1, 1))
+						table_insert(timelines, readTimeline2(timelineMap, timeline, "x", "y", 1, 1))
 					elseif timelineName == "scalex" then
 					elseif timelineName == "scalex" then
 						local timeline = Animation.ScaleXTimeline.new(#timelineMap, #timelineMap, boneIndex)
 						local timeline = Animation.ScaleXTimeline.new(#timelineMap, #timelineMap, boneIndex)
 						table_insert(timelines, readTimeline1(timelineMap, timeline, 1, 1))
 						table_insert(timelines, readTimeline1(timelineMap, timeline, 1, 1))
@@ -774,7 +774,7 @@ function SkeletonJson.new (attachmentLoader)
 						table_insert(timelines, readTimeline1(timelineMap, timeline, 1, 1))
 						table_insert(timelines, readTimeline1(timelineMap, timeline, 1, 1))
 					elseif timelineName == "shear" then
 					elseif timelineName == "shear" then
 						local timeline = Animation.ShearTimeline.new(#timelineMap, #timelineMap * 2, boneIndex)
 						local timeline = Animation.ShearTimeline.new(#timelineMap, #timelineMap * 2, boneIndex)
-						table_insert(timelines, readTimeline2(timelineMap, "x", "y", 0, 1))
+						table_insert(timelines, readTimeline2(timelineMap, timeline, "x", "y", 0, 1))
 					elseif timelineName == "shearx" then
 					elseif timelineName == "shearx" then
 						local timeline = Animation.ShearXTimeline.new(#timelineMap, #timelineMap, boneIndex)
 						local timeline = Animation.ShearXTimeline.new(#timelineMap, #timelineMap, boneIndex)
 						table_insert(timelines, readTimeline1(timelineMap, timeline, 0, 1))
 						table_insert(timelines, readTimeline1(timelineMap, timeline, 0, 1))
@@ -901,7 +901,7 @@ function SkeletonJson.new (attachmentLoader)
 		if map.path then
 		if map.path then
 			for constraintName,constraintMap in pairs(map.path) do
 			for constraintName,constraintMap in pairs(map.path) do
 				local constraint, constraintIndex = -1
 				local constraint, constraintIndex = -1
-				for i,other in pairs(skeletonData.transformConstraints) do
+				for i,other in pairs(skeletonData.pathConstraints) do
 					if other.name == constraintName then
 					if other.name == constraintName then
 						constraintIndex = i
 						constraintIndex = i
 						constraint = other
 						constraint = other
@@ -914,12 +914,12 @@ function SkeletonJson.new (attachmentLoader)
 						if timelineName == "position" then
 						if timelineName == "position" then
 							local timeline = Animation.PathConstraintPositionTimeline.new(#timelineMap, #timelineMap, constraintIndex)
 							local timeline = Animation.PathConstraintPositionTimeline.new(#timelineMap, #timelineMap, constraintIndex)
 							local timelineScale = 1
 							local timelineScale = 1
-							if constraint.positionMode == PositionMode.fixed then timelineScale = scale end
+							if constraint.positionMode == PathConstraintData.PositionMode.fixed then timelineScale = scale end
 							table_insert(timelines, readTimeline1(timelineMap, timeline, 0, timelineScale))
 							table_insert(timelines, readTimeline1(timelineMap, timeline, 0, timelineScale))
 						elseif timelineName == "spacing" then
 						elseif timelineName == "spacing" then
 							local timeline = Animation.PathConstraintSpacingTimeline.new(#timelineMap, #timelineMap, constraintIndex)
 							local timeline = Animation.PathConstraintSpacingTimeline.new(#timelineMap, #timelineMap, constraintIndex)
 							local timelineScale = 1
 							local timelineScale = 1
-							if data.spacingMode == SpacingMode.Length or data.spacingMode == SpacingMode.Fixed then timelineScale = scale end
+							if data.spacingMode == PathConstraintData.SpacingMode.Length or data.spacingMode == PathConstraintData.SpacingMode.Fixed then timelineScale = scale end
 							table_insert(timelines, readTimeline1(timelineMap, timeline, 0, timelineScale))
 							table_insert(timelines, readTimeline1(timelineMap, timeline, 0, timelineScale))
 						elseif timelineName == "mix" then
 						elseif timelineName == "mix" then
 							local timeline = Animation.PathConstraintMixTimeline.new(#timelineMap, #timelineMap * 3, constraintIndex)
 							local timeline = Animation.PathConstraintMixTimeline.new(#timelineMap, #timelineMap * 3, constraintIndex)
@@ -978,6 +978,7 @@ function SkeletonJson.new (attachmentLoader)
 							if weighted then deformLength = math_floor(deformLength / 3) * 2 end
 							if weighted then deformLength = math_floor(deformLength / 3) * 2 end
 
 
 							local timeline = Animation.DeformTimeline.new(#timelineMap, #timelineMap, slotIndex, attachment)
 							local timeline = Animation.DeformTimeline.new(#timelineMap, #timelineMap, slotIndex, attachment)
+							local time = getValue(keyMap, "time", 0)
 							local bezier = 0
 							local bezier = 0
 							for i,keyMap in ipairs(timelineMap) do
 							for i,keyMap in ipairs(timelineMap) do
 								local deform = nil
 								local deform = nil
@@ -1007,7 +1008,7 @@ function SkeletonJson.new (attachmentLoader)
 									end
 									end
 								end
 								end
 								local frame = i - 1
 								local frame = i - 1
-								timeline:setFrame(frame, time, mixRotate, mixX, mixY)
+								timeline:setFrame(frame, time, deform)
 								local nextMap = timelineMap[frame + 1]
 								local nextMap = timelineMap[frame + 1]
 								if not nextMap then
 								if not nextMap then
 									timeline:shrink(bezier)
 									timeline:shrink(bezier)
@@ -1172,15 +1173,15 @@ function SkeletonJson.new (attachmentLoader)
 
 
 	readCurve = function (curve, timeline, bezier, frame, value, time1, time2, value1, value2, scale)
 	readCurve = function (curve, timeline, bezier, frame, value, time1, time2, value1, value2, scale)
 		if curve == "stepped" then
 		if curve == "stepped" then
-			if value ~= 0 then timeline.setStepped(frame) end
+			if value ~= 0 then timeline:setStepped(frame) end
 			return bezier
 			return bezier
 		end
 		end
-		local i = value * 4
+		local i = value * 4 + 1
 		local cx1 = curve[i]
 		local cx1 = curve[i]
 		local cy1 = curve[i + 1] * scale
 		local cy1 = curve[i + 1] * scale
 		local cx2 = curve[i + 2]
 		local cx2 = curve[i + 2]
 		local cy2 = curve[i + 3] * scale
 		local cy2 = curve[i + 3] * scale
-		timeline.setBezier(bezier, frame, value, time1, value1, cx1, cy1, cx2, cy2, time2, value2)
+		timeline:setBezier(bezier, frame, value, time1, value1, cx1, cy1, cx2, cy2, time2, value2)
 		return bezier + 1
 		return bezier + 1
 	end
 	end
 
 

+ 8 - 1
spine-lua/spine-lua/Slot.lua

@@ -60,9 +60,16 @@ end
 
 
 function Slot:setAttachment (attachment)
 function Slot:setAttachment (attachment)
 	if self.attachment == attachment then return end
 	if self.attachment == attachment then return end
+	if not attachment
+		or not attachment.isVertexAttachment
+		or not self.attachment
+		or not self.attachment.isVertexAttachment
+		or attachment.deformAttachment ~= self.attachment.deformAttachment
+	then
+		self.deform = {}
+	end
 	self.attachment = attachment
 	self.attachment = attachment
 	self.attachmentTime = self.bone.skeleton.time
 	self.attachmentTime = self.bone.skeleton.time
-	self.deform = {}
 end
 end
 
 
 function Slot:setAttachmentTime (time)
 function Slot:setAttachmentTime (time)

+ 147 - 135
spine-lua/spine-lua/TextureAtlas.lua

@@ -7,7 +7,7 @@
 -- Integration of the Spine Runtimes into software or otherwise creating
 -- Integration of the Spine Runtimes into software or otherwise creating
 -- derivative works of the Spine Runtimes is permitted under the terms and
 -- derivative works of the Spine Runtimes is permitted under the terms and
 -- conditions of Section 2 of the Spine Editor License Agreement:
 -- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
+-- http:--esotericsoftware.com/spine-editor-license
 --
 --
 -- Otherwise, it is permitted to integrate the Spine Runtimes into software
 -- Otherwise, it is permitted to integrate the Spine Runtimes into software
 -- or otherwise create derivative works of the Spine Runtimes (collectively,
 -- or otherwise create derivative works of the Spine Runtimes (collectively,
@@ -28,6 +28,7 @@
 -------------------------------------------------------------------------------
 -------------------------------------------------------------------------------
 
 
 local setmetatable = setmetatable
 local setmetatable = setmetatable
+local tonumber = tonumber
 local table_insert = table.insert
 local table_insert = table.insert
 local math_abs = math.abs
 local math_abs = math.abs
 
 
@@ -47,7 +48,8 @@ function TextureAtlasPage.new ()
 		vWrap = nil,
 		vWrap = nil,
 		texture = nil,
 		texture = nil,
 		width = 0,
 		width = 0,
-		height = 0
+		height = 0,
+		pma = false
 	}
 	}
 	setmetatable(self, TextureAtlasPage)
 	setmetatable(self, TextureAtlasPage)
 	return self
 	return self
@@ -73,168 +75,178 @@ function TextureAtlas:parse (atlasContent, imageLoader)
 	if not atlasContent then error("atlasContent cannot be nil.", 2) end
 	if not atlasContent then error("atlasContent cannot be nil.", 2) end
 	if not imageLoader then error("imageLoader cannot be nil.", 2) end
 	if not imageLoader then error("imageLoader cannot be nil.", 2) end
 
 
-	function lineIterator(s)
-		if s:sub(-1)~="\n" then s=s.."\n" end
-		return s:gmatch("(.-)\n")
-	end
-
-	local lines = {}
-	local index = 0
-	local numLines = 0
-	for line in lineIterator(atlasContent) do
-		lines[numLines] = line
-		numLines = numLines + 1
-	end
+	local readLine = atlasContent:gmatch("[ \t]*(.-)[ \t]*\r?\n")
 
 
-	local readLine = function ()
-		if index >= numLines then return nil end
-		local line = lines[index]
-		index = index + 1
-		return line
+	local trim = function (value)
+		return value:match("^%s*(.-)%s*$")
 	end
 	end
 
 
-	local readValue = function ()
-		local line = readLine()
-		local idx = line:find(":")
-		if not idx then error("Invalid line: " .. line, 2) end
-		return line:sub(idx + 1):match'^%s*(.*%S)' or ''
-	end
+	local entry = {}
+	local readEntry = function (entry, line)
+		if not line then return 0 end
+		if line:len() == 0 then return 0 end
 
 
-	local readTuple = function ()
-		local line = readLine()
-		local idx = line:find(":")
-		if not idx then
-			error("Invalid line: " .. line, 2)
-		end
+		local colon = line:find(":")
+		if not colon then return 0 end
+		entry[0] = trim(line:sub(1, colon))
+		local lastMatch = colon + 1
 		local i = 1
 		local i = 1
-		local lastMatch = idx + 1
-		local tuple = {}
-		while i <= 3 do
+		while true do
 			local comma = line:find(",", lastMatch)
 			local comma = line:find(",", lastMatch)
-			if not comma then break end
-			tuple[i] = line:sub(lastMatch, comma - 1):match'^%s*(.*%S)' or ''
+			if not comma then
+				entry[i] = trim(line:sub(lastMatch))
+				return i
+			end
+			entry[i] = trim(line:sub(lastMatch, comma - lastMatch))
 			lastMatch = comma + 1
 			lastMatch = comma + 1
+			if i == 4 then return 4 end
 			i = i + 1
 			i = i + 1
 		end
 		end
-		tuple[i] = line:sub(lastMatch):match'^%s*(.*%S)' or ''
-		return tuple
 	end
 	end
 
 
-	local parseInt = function (str)
-		return tonumber(str)
+	local page
+	local region
+
+	local pageFields = {}
+	pageFields["size"] = function ()
+		page.width = tonumber(entry[1])
+		page.height = tonumber(entry[2])
+	end
+	pageFields["format"] = function ()
+		-- page.format = Format[tuple[0]] we don't need format in Lua
+	end
+	pageFields["filter"] = function ()
+		page.minFilter = TextureFilter[entry[1]]
+		page.magFilter = TextureFilter[entry[2]]
+	end
+	pageFields["repeat"] = function ()
+		if entry[1]:find("x") then page.uWrap = TextureWrap.Repeat end
+		if entry[1]:find("y") then page.vWrap = TextureWrap.Repeat end
+	end
+	pageFields["pma"] = function ()
+		page.pma = entry[1] == "true"
 	end
 	end
 
 
-	local filterFromString = function (str)
-		str = str:lower()
-		if str == "nearest" then return TextureFilter.Nearest
-		elseif str == "linear" then return TextureFilter.Linear
-		elseif str == "mipmap" then return TextureFilter.MipMap
-		elseif str == "mipmapnearestnearest" then return TextureFilter.MipMapNearestNearest
-		elseif str == "mipmaplinearnearest" then return TextureFilter.MipMapLinearNearest
-		elseif str == "mipmapnearestlinear" then return TextureFilter.MipMapNearestLinear
-		elseif str == "mipmaplinearlinear" then return TextureFilter.MipMapLinearLinear
-		else error("Unknown texture wrap: " .. str, 2)
+	local regionFields = {}
+	regionFields["xy"] = function () -- Deprecated, use bounds.
+		region.x = tonumber(entry[1])
+		region.y = tonumber(entry[2])
+	end
+	regionFields["size"] = function () -- Deprecated, use bounds.
+		region.width = tonumber(entry[1])
+		region.height = tonumber(entry[2])
+	end
+	regionFields["bounds"] = function ()
+		region.x = tonumber(entry[1])
+		region.y = tonumber(entry[2])
+		region.width = tonumber(entry[3])
+		region.height = tonumber(entry[4])
+	end
+	regionFields["offset"] = function () -- Deprecated, use offsets.
+		region.offsetX = tonumber(entry[1])
+		region.offsetY = tonumber(entry[2])
+	end
+	regionFields["orig"] = function () -- Deprecated, use offsets.
+		region.originalWidth = tonumber(entry[1])
+		region.originalHeight = tonumber(entry[2])
+	end
+	regionFields["offsets"] = function ()
+		region.offsetX = tonumber(entry[1])
+		region.offsetY = tonumber(entry[2])
+		region.originalWidth = tonumber(entry[3])
+		region.originalHeight = tonumber(entry[4])
+	end
+	regionFields["rotate"] = function ()
+		local value = entry[1]
+		if value == "true" then
+			region.degrees = 90
+		elseif value ~= "false" then
+			region.degrees = tonumber(value)
 		end
 		end
 	end
 	end
+	regionFields["index"] = function ()
+		region.index = tonumber(entry[1])
+	end
 
 
-	local page = nil
+	local line = readLine()
+	-- Ignore empty lines before first entry.
+	while line and line:len() == 0 do
+		line = readLine()
+	end
+	-- Header entries.
+	while true do
+		if not line or line:len() == 0 then break end
+		if readEntry(entry, line) == 0 then break end -- Silently ignore all header fields.
+		line = readLine()
+	end
+
+	-- Page and region entries.
+	local names
+	local values
 	while true do
 	while true do
-		local line = readLine()
 		if not line then break end
 		if not line then break end
-		line = line:match'^%s*(.*%S)' or ''
 		if line:len() == 0 then
 		if line:len() == 0 then
 			page = nil
 			page = nil
+			line = readLine()
 		elseif not page then
 		elseif not page then
 			page = TextureAtlasPage.new()
 			page = TextureAtlasPage.new()
-			page.name = line
-
-			local tuple = readTuple()
-			if #tuple == 2 then
-				page.width = parseInt(tuple[1])
-				page.height = parseInt(tuple[2])
-				tuple = readTuple()
-			else
-				-- We only support atlases that have the page width/height
-				-- encoded in them. That way we don't rely on any special
-				-- wrapper objects for images to get the page size from
-				error("Atlas must specify page width/height. Please export to the latest atlas format", 2)
-			end
-
-			tuple = readTuple()
-			page.minFilter = filterFromString(tuple[1])
-			page.magFilter = filterFromString(tuple[2])
-
-			local direction = readValue()
-			page.uWrap = TextureWrap.ClampToEdge
-			page.vWrap = TextureWrap.ClampToEdge
-			if direction == "x" then
-				page.uWrap = TextureWrap.Repeat
-			elseif direction == "y" then
-				page.vWrap = TextureWrap.Repeat
-			elseif direction == "xy" then
-				page.uWrap = TextureWrap.Repeat
-				page.vWrap = TextureWrap.Repeat
+			page.name = trim(line)
+			while true do
+				line = readLine()
+				if readEntry(entry, line) == 0 then break end
+				local field = pageFields[entry[0]]
+				if field then field() end
 			end
 			end
-
-			page.texture = imageLoader(line)
-			-- FIXME page.texture:setFilters(page.minFilter, page.magFilter)
-			-- FIXME page.texture:setWraps(page.uWrap, page.vWrap)
+			page.texture = imageLoader(page.name)
+			-- FIXME - Apply the filter and wrap settings to the texture.
+			-- page.texture:setFilters(page.minFilter, page.magFilter)
+			-- page.texture:setWraps(page.uWrap, page.vWrap)
 			table_insert(self.pages, page)
 			table_insert(self.pages, page)
 		else
 		else
-			local region = TextureAtlasRegion.new()
-			region.name = line
+			region = TextureAtlasRegion.new()
 			region.page = page
 			region.page = page
-
-			local rotateValue = readValue()
-			if rotateValue == "true" then
-				region.degrees = 90
-			elseif rotateValue == "false" then
-				region.degrees = 0
-			else
-				region.degrees = tonumber(rotateValue)
+			region.name = line
+			while true do
+				line = readLine()
+				local count = readEntry(entry, line)
+				if count == 0 then break end
+				local field = regionFields[entry[0]]
+				if field then
+					field()
+				else
+					if not names then
+						names = {}
+						values = {}
+					end
+					table_insert(names, entry[0])
+					local entryValues = {}
+					local i = 0
+					while i < count do
+						table_insert(entryValues, tonumber(entry[i + 1]))
+						 i = i + 1
+					end
+					table_insert(values, entryValues)
+				end
 			end
 			end
-			if region.degrees == 90 then region.rotate = true end
-
-			local tuple = readTuple()
-			local x = parseInt(tuple[1])
-			local y = parseInt(tuple[2])
-
-			tuple = readTuple()
-			local width = parseInt(tuple[1])
-			local height = parseInt(tuple[2])
-
-			region.u = x / page.width
-			region.v = y / page.height
-			if region.rotate then
-				region.u2 = (x + height) / page.width
-				region.v2 = (y + width) / page.height
-			else
-				region.u2 = (x + width) / page.width
-				region.v2 = (y + height) / page.height
+			if region.originalWidth == 0 and region.originalHeight == 0 then
+				region.originalWidth = region.width
+				region.originalHeight = region.height
 			end
 			end
-
-			region.x = x
-			region.y = y
-			region.width = math_abs(width)
-			region.height = math_abs(height)
-
-			-- Read and skip optional splits
-			tuple = readTuple()
-			if #tuple == 4 then
-				tuple = readTuple()
-				if #tuple == 4 then
-					readTuple()
-				end
+			if names and #names > 0 then
+				region.names = names
+				region.values = values
+				names = nil
+				values = nil
+			end
+			region.u = region.x / page.width
+			region.v = region.y / page.height
+			if region.degrees == 90 then
+				region.u2 = (region.x + region.height) / page.width
+				region.v2 = (region.y + region.width) / page.height
+			else
+				region.u2 = (region.x + region.width) / page.width
+				region.v2 = (region.y + region.height) / page.height
 			end
 			end
-
-			region.originalWidth = parseInt(tuple[1])
-			region.originalHeight = parseInt(tuple[2])
-
-			tuple = readTuple()
-			region.offsetX = parseInt(tuple[1])
-			region.offsetY = parseInt(tuple[2])
-
-			region.index = parseInt(readValue())
 			region.texture = page.texture
 			region.texture = page.texture
 			table_insert(self.regions, region)
 			table_insert(self.regions, region)
 		end
 		end

+ 2 - 1
spine-lua/spine-lua/TextureAtlasRegion.lua

@@ -42,9 +42,10 @@ function TextureAtlasRegion.new ()
 	self.x = 0
 	self.x = 0
 	self.y = 0
 	self.y = 0
 	self.index = 0
 	self.index = 0
-	self.rotate = false
 	self.degrees = 0
 	self.degrees = 0
 	self.texture = nil
 	self.texture = nil
+	self.names = nil
+	self.values = nil
 	setmetatable(self, TextureAtlasRegion)
 	setmetatable(self, TextureAtlasRegion)
 
 
 	return self
 	return self

+ 80 - 104
spine-lua/spine-lua/TransformConstraint.lua

@@ -53,7 +53,7 @@ function TransformConstraint.new (data, skeleton)
 		data = data,
 		data = data,
 		bones = {},
 		bones = {},
 		target = nil,
 		target = nil,
-		rotateMix = data.rotateMix, translateMix = data.translateMix, scaleMix = data.scaleMix, shearMix = data.shearMix,
+		mixRotate = data.mixRotate, mixX = data.mixX, mixY = data.mixY, mixScaleX = data.mixScaleX, mixScaleY = data.mixScaleY, mixShearY = data.mixShearY,
 		temp = { 0, 0 },
 		temp = { 0, 0 },
 		active = false
 		active = false
 	}
 	}
@@ -67,11 +67,9 @@ function TransformConstraint.new (data, skeleton)
 	return self
 	return self
 end
 end
 
 
-function TransformConstraint:apply ()
-	self:update()
-end
-
 function TransformConstraint:update ()
 function TransformConstraint:update ()
+	if self.mixRotate == 0 and self.mixX == 0 and self.mixY == 0 and self.mixScaleX == 0 and self.mixScaleX == 0 and self.mixShearY == 0 then return end
+
 	if self.data.local_ then
 	if self.data.local_ then
 		if self.data.relative then
 		if self.data.relative then
 			self:applyRelativeLocal()
 			self:applyRelativeLocal()
@@ -88,10 +86,14 @@ function TransformConstraint:update ()
 end
 end
 
 
 function TransformConstraint:applyAbsoluteWorld ()
 function TransformConstraint:applyAbsoluteWorld ()
-	local rotateMix = self.rotateMix
-	local translateMix = self.translateMix
-	local scaleMix = self.scaleMix
-	local shearMix = self.shearMix
+	local mixRotate = self.mixRotate
+	local mixX = self.mixX
+	local mixY = self.mixY
+	local mixScaleX = self.mixScaleX
+	local mixScaleY = self.mixScaleY
+	local mixShearY = self.mixShearY
+	local translate = mixX ~= 0 or mixY ~= 0
+
 	local target = self.target
 	local target = self.target
 	local ta = target.a
 	local ta = target.a
 	local tb = target.b
 	local tb = target.b
@@ -101,10 +103,10 @@ function TransformConstraint:applyAbsoluteWorld ()
 	if ta * td - tb * tc > 0 then degRadReflect = utils.degRad else degRadReflect = -utils.degRad end
 	if ta * td - tb * tc > 0 then degRadReflect = utils.degRad else degRadReflect = -utils.degRad end
 	local offsetRotation = self.data.offsetRotation * degRadReflect
 	local offsetRotation = self.data.offsetRotation * degRadReflect
 	local offsetShearY = self.data.offsetShearY * degRadReflect
 	local offsetShearY = self.data.offsetShearY * degRadReflect
+
 	local bones = self.bones
 	local bones = self.bones
 	for _, bone in ipairs(bones) do
 	for _, bone in ipairs(bones) do
-		local modified = false
-		if rotateMix ~= 0 then
+		if mixRotate ~= 0 then
 			local a = bone.a
 			local a = bone.a
 			local b = bone.b
 			local b = bone.b
 			local c = bone.c
 			local c = bone.c
@@ -115,45 +117,38 @@ function TransformConstraint:applyAbsoluteWorld ()
 			elseif r < -math_pi then
 			elseif r < -math_pi then
 				r = r + math_pi2
 				r = r + math_pi2
 			end
 			end
-			r = r * rotateMix
+			r = r * mixRotate
 			local cos = math_cos(r)
 			local cos = math_cos(r)
 			local sin = math_sin(r)
 			local sin = math_sin(r)
 			bone.a = cos * a - sin * c
 			bone.a = cos * a - sin * c
 			bone.b = cos * b - sin * d
 			bone.b = cos * b - sin * d
 			bone.c = sin * a + cos * c
 			bone.c = sin * a + cos * c
 			bone.d = sin * b + cos * d
 			bone.d = sin * b + cos * d
-			modified = true
 		end
 		end
 
 
-		if translateMix ~= 0 then
+		if translate then
 			local temp = self.temp
 			local temp = self.temp
 			temp[1] = self.data.offsetX
 			temp[1] = self.data.offsetX
 			temp[2] = self.data.offsetY
 			temp[2] = self.data.offsetY
 			target:localToWorld(temp)
 			target:localToWorld(temp)
-			bone.worldX = bone.worldX + (temp[1] - bone.worldX) * translateMix
-			bone.worldY = bone.worldY + (temp[2] - bone.worldY) * translateMix
-			modified = true
+			bone.worldX = bone.worldX + (temp[1] - bone.worldX) * mixX
+			bone.worldY = bone.worldY + (temp[2] - bone.worldY) * mixY
 		end
 		end
 
 
-		if scaleMix > 0 then
+		if mixScaleX ~= 0 then
 			local s = math_sqrt(bone.a * bone.a + bone.c * bone.c)
 			local s = math_sqrt(bone.a * bone.a + bone.c * bone.c)
-			local ts = math_sqrt(ta * ta + tc * tc)
-			if s > 0.00001 then
-				s = (s + (ts - s + self.data.offsetScaleX) * scaleMix) / s
-			end
+			if s ~= 0 then s = (s + (math_sqrt(ta * ta + tc * tc) - s + self.data.offsetScaleX) * mixScaleX) / s end
 			bone.a = bone.a * s
 			bone.a = bone.a * s
 			bone.c = bone.c * s
 			bone.c = bone.c * s
-			s = math_sqrt(bone.b * bone.b + bone.d * bone.d)
-			ts = math_sqrt(tb * tb + td * td)
-			if s > 0.00001 then
-				s = (s + (ts - s + self.data.offsetScaleY) * scaleMix) / s
-			end
+		end
+		if mixScaleY ~= 0 then
+			local s = math_sqrt(bone.b * bone.b + bone.d * bone.d)
+			if s ~= 0 then s = (s + (math_sqrt(tb * tb + td * td) - s + self.data.offsetScaleY) * mixScaleY) / s end
 			bone.b = bone.b * s
 			bone.b = bone.b * s
 			bone.d = bone.d * s
 			bone.d = bone.d * s
-			modified = true
 		end
 		end
 
 
-		if shearMix > 0 then
+		if mixShearY > 0 then
 			local b = bone.b
 			local b = bone.b
 			local d = bone.d
 			local d = bone.d
 			local by = math_atan2(d, b)
 			local by = math_atan2(d, b)
@@ -163,22 +158,25 @@ function TransformConstraint:applyAbsoluteWorld ()
 			elseif r < -math_pi then
 			elseif r < -math_pi then
 				r = r + math_pi2
 				r = r + math_pi2
 			end
 			end
-			r = by + (r + offsetShearY) * shearMix
+			r = by + (r + offsetShearY) * mixShearY
 			local s = math_sqrt(b * b + d * d)
 			local s = math_sqrt(b * b + d * d)
 			bone.b = math_cos(r) * s
 			bone.b = math_cos(r) * s
 			bone.d = math_sin(r) * s
 			bone.d = math_sin(r) * s
-			modified = true
 		end
 		end
 
 
-		if modified then bone.appliedValid = false end
+		bone:updateAppliedTransform()
 	end
 	end
 end
 end
 
 
 function TransformConstraint:applyRelativeWorld ()
 function TransformConstraint:applyRelativeWorld ()
-	local rotateMix = self.rotateMix
-	local translateMix = self.translateMix
-	local scaleMix = self.scaleMix
-	local shearMix = self.shearMix
+	local mixRotate = self.mixRotate
+	local mixX = self.mixX
+	local mixY = self.mixY
+	local mixScaleX = self.mixScaleX
+	local mixScaleY = self.mixScaleY
+	local mixShearY = self.mixShearY
+	local translate = mixX ~= 0 or mixY ~= 0
+
 	local target = self.target
 	local target = self.target
 	local ta = target.a
 	local ta = target.a
 	local tb = target.b
 	local tb = target.b
@@ -188,11 +186,12 @@ function TransformConstraint:applyRelativeWorld ()
 	if ta * td - tb * tc > 0 then degRadReflect = utils.degRad else degRadReflect = -utils.degRad end
 	if ta * td - tb * tc > 0 then degRadReflect = utils.degRad else degRadReflect = -utils.degRad end
 	local offsetRotation = self.data.offsetRotation * degRadReflect
 	local offsetRotation = self.data.offsetRotation * degRadReflect
 	local offsetShearY = self.data.offsetShearY * degRadReflect
 	local offsetShearY = self.data.offsetShearY * degRadReflect
+
 	local bones = self.bones
 	local bones = self.bones
 	for _, bone in ipairs(bones) do
 	for _, bone in ipairs(bones) do
 		local modified = false
 		local modified = false
 
 
-		if rotateMix ~= 0 then
+		if mixRotate ~= 0 then
 			local a = bone.a
 			local a = bone.a
 			local b = bone.b
 			local b = bone.b
 			local c = bone.c
 			local c = bone.c
@@ -203,37 +202,36 @@ function TransformConstraint:applyRelativeWorld ()
 			elseif r < -math_pi then
 			elseif r < -math_pi then
 				r = r + math_pi2
 				r = r + math_pi2
 			end
 			end
-			r = r * rotateMix
+			r = r * mixRotate
 			local cos = math_cos(r)
 			local cos = math_cos(r)
 			local sin = math_sin(r)
 			local sin = math_sin(r)
 			bone.a = cos * a - sin * c
 			bone.a = cos * a - sin * c
 			bone.b = cos * b - sin * d
 			bone.b = cos * b - sin * d
 			bone.c = sin * a + cos * c
 			bone.c = sin * a + cos * c
 			bone.d = sin * b + cos * d
 			bone.d = sin * b + cos * d
-			modified = true
 		end
 		end
 
 
-		if translateMix ~= 0 then
+		if translate then
 			local temp = self.temp
 			local temp = self.temp
 			temp[1] = self.data.offsetX
 			temp[1] = self.data.offsetX
 			temp[2] = self.data.offsetY
 			temp[2] = self.data.offsetY
 			target:localToWorld(temp)
 			target:localToWorld(temp)
-			bone.worldX = bone.worldX + temp[1] * translateMix
-			bone.worldY = bone.worldY + temp[2] * translateMix
-			modified = true
+			bone.worldX = bone.worldX + temp[1] * mixX
+			bone.worldY = bone.worldY + temp[2] * mixY
 		end
 		end
 
 
-		if scaleMix > 0 then
-			local s = (math_sqrt(ta * ta + tc * tc) - 1 + self.data.offsetScaleX) * scaleMix + 1
+		if mixScaleX ~= 0 then
+			local s = (math_sqrt(ta * ta + tc * tc) - 1 + self.data.offsetScaleX) * mixScaleX + 1
 			bone.a = bone.a * s
 			bone.a = bone.a * s
 			bone.c = bone.c * s
 			bone.c = bone.c * s
-			s = (math_sqrt(tb * tb + td * td) - 1 + self.data.offsetScaleY) * scaleMix + 1
+		end
+		if mixScaleY ~= 0 then
+			local s = (math_sqrt(tb * tb + td * td) - 1 + self.data.offsetScaleY) * mixScaleY + 1
 			bone.b = bone.b * s
 			bone.b = bone.b * s
 			bone.d = bone.d * s
 			bone.d = bone.d * s
-			modified = true
 		end
 		end
 
 
-		if shearMix > 0 then
+		if mixShearY > 0 then
 			local r = math_atan2(td, tb) - math_atan2(tc, ta)
 			local r = math_atan2(td, tb) - math_atan2(tc, ta)
 			if r > math_pi then
 			if r > math_pi then
 				r = r - math_pi2
 				r = r - math_pi2
@@ -242,59 +240,54 @@ function TransformConstraint:applyRelativeWorld ()
 			end
 			end
 			local b = bone.b
 			local b = bone.b
 			local d = bone.d
 			local d = bone.d
-			r = math_atan2(d, b) + (r - math_pi / 2 + offsetShearY) * shearMix
+			r = math_atan2(d, b) + (r - math_pi / 2 + offsetShearY) * mixShearY
 			local s = math_sqrt(b * b + d * d)
 			local s = math_sqrt(b * b + d * d)
 			bone.b = math_cos(r) * s
 			bone.b = math_cos(r) * s
 			bone.d = math_sin(r) * s
 			bone.d = math_sin(r) * s
-			modified = true
 		end
 		end
 
 
-		if modified then bone.appliedValid = false end
+		bone:updateAppliedTransform()
 	end
 	end
 end
 end
 
 
 function TransformConstraint:applyAbsoluteLocal ()
 function TransformConstraint:applyAbsoluteLocal ()
-	local rotateMix = self.rotateMix
-	local translateMix = self.translateMix
-	local scaleMix = self.scaleMix
-	local shearMix = self.shearMix
+	local mixRotate = self.mixRotate
+	local mixX = self.mixX
+	local mixY = self.mixY
+	local mixScaleX = self.mixScaleX
+	local mixScaleY = self.mixScaleY
+	local mixShearY = self.mixShearY
+
 	local target = self.target
 	local target = self.target
 	if not target.appliedValid then target:updatedAppliedTransform() end
 	if not target.appliedValid then target:updatedAppliedTransform() end
 	local bones = self.bones
 	local bones = self.bones
 	for _, bone in ipairs(bones) do
 	for _, bone in ipairs(bones) do
-		local modified = false
-		if not bone.appliedValid then bone:updateAppliedTransform() end
-
 		local rotation = bone.arotation
 		local rotation = bone.arotation
-		if rotateMix ~= 0 then
+		if mixRotate ~= 0 then
 			local r = target.arotation - rotation + self.data.offsetRotation
 			local r = target.arotation - rotation + self.data.offsetRotation
 			r = r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360
 			r = r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360
-			rotation = rotation + r * rotateMix
+			rotation = rotation + r * mixRotate
 		end
 		end
 
 
 		local x = bone.ax
 		local x = bone.ax
 		local y = bone.ay
 		local y = bone.ay
-		if translateMix ~= 0 then
-			x = x + (target.ax - x + self.data.offsetX) * translateMix
-			y = x + (target.ay - y + self.data.offsetY) * translateMix
-		end
+		x = x + (target.ax - x + self.data.offsetX) * mixX
+		y = x + (target.ay - y + self.data.offsetY) * mixX
 
 
 		local scaleX = bone.ascaleX
 		local scaleX = bone.ascaleX
 		local scaleY = bone.ascaleY
 		local scaleY = bone.ascaleY
-		if scaleMix ~= 0 then
-			if scaleX > 0.00001 then
-				scaleX = (scaleX + (target.ascaleX - scaleX + self.data.offsetScaleX) * scaleMix) / scaleX
-			end
-			if scaleY > 0.00001 then
-				scaleY = (scaleY + (target.ascaleY - scaleY + self.data.offsetScaleY) * scaleMix) / scaleY
-			end
+		if mixScaleX ~= 0 and scaleX ~= 0 then
+			scaleX = (scaleX + (target.ascaleX - scaleX + self.data.offsetScaleX) * mixScaleX) / scaleX
+		end
+		if mixScaleY ~= 0 and scaleY ~= 0 then
+			scaleY = (scaleY + (target.ascaleY - scaleY + self.data.offsetScaleY) * mixScaleY) / scaleY
 		end
 		end
 
 
 		local shearY = bone.ashearY
 		local shearY = bone.ashearY
-		if shearMix ~= 0 then
+		if mixShearY ~= 0 then
 			local r = target.ashearY - shearY + self.data.offsetShearY
 			local r = target.ashearY - shearY + self.data.offsetShearY
 			r = r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360
 			r = r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360
-			bone.shearY = bone.shearY + r * shearMix
+			bone.shearY = bone.shearY + r * mixShearY
 		end
 		end
 
 
 		bone:updateWorldTransformWith(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY)
 		bone:updateWorldTransformWith(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY)
@@ -302,40 +295,23 @@ function TransformConstraint:applyAbsoluteLocal ()
 end
 end
 
 
 function TransformConstraint:applyRelativeLocal ()
 function TransformConstraint:applyRelativeLocal ()
-	local rotateMix = self.rotateMix
-	local translateMix = self.translateMix
-	local scaleMix = self.scaleMix
-	local shearMix = self.shearMix
+	local mixRotate = self.mixRotate
+	local mixX = self.mixX
+	local mixY = self.mixY
+	local mixScaleX = self.mixScaleX
+	local mixScaleY = self.mixScaleY
+	local mixShearY = self.mixShearY
+
 	local target = self.target
 	local target = self.target
-	if not target.appliedValid then target:updateAppliedTransform() end
+
 	local bones = self.bones
 	local bones = self.bones
 	for _, bone in ipairs(bones) do
 	for _, bone in ipairs(bones) do
-		if not bone.appliedValid then bone:updateAppliedTransform() end
-
-		local rotation = bone.arotation
-		if rotateMix ~= 0 then rotation = rotation + (target.arotation + self.data.offsetRotation) * rotateMix end
-
-		local x = bone.ax
-		local y = bone.ay
-		if translateMix ~= 0 then
-			x = x + (target.ax + self.data.offsetX) * translateMix
-			y = y + (target.ay + self.data.offsetY) * translateMix
-		end
-
-		local scaleX = bone.ascaleX
-		local scaleY = bone.ascaleY
-		if scaleMix ~= 0 then
-			if scaleX > 0.00001 then
-				scaleX = scaleX * (((target.ascaleX - 1 + self.data.offsetScaleX) * scaleMix) + 1)
-			end
-			if scaleY > 0.00001 then
-				scaleY = scaleY * (((target.ascaleY - 1 + self.data.offsetScaleY) * scaleMix) + 1)
-			end
-		end
-
-		local shearY = bone.ashearY
-		if shearMix ~= 0 then shearY = shearY + (target.ashearY + self.data.offsetShearY) * shearMix end
-
+		local rotation = bone.arotation + (target.arotation + this.data.offsetRotation) * mixRotate
+		local x = bone.ax + (target.ax + this.data.offsetX) * mixX
+		local y = bone.ay + (target.ay + this.data.offsetY) * mixY
+		local scaleX = (bone.ascaleX * ((target.ascaleX - 1 + this.data.offsetScaleX) * mixScaleX) + 1)
+		local scaleY = (bone.ascaleY * ((target.ascaleY - 1 + this.data.offsetScaleY) * mixScaleY) + 1)
+		local shearY = bone.ashearY + (target.ashearY + this.data.offsetShearY) * mixShearY
 		bone:updateWorldTransformWith(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY)
 		bone:updateWorldTransformWith(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY)
 	end
 	end
 end
 end

+ 1 - 1
spine-lua/spine-lua/TransformConstraintData.lua

@@ -37,7 +37,7 @@ function TransformConstraintData.new (name)
 		skinRequired = false,
 		skinRequired = false,
 		bones = {},
 		bones = {},
 		target = nil,
 		target = nil,
-		rotateMix = 0, translateMix = 0, scaleMix = 0, shearMix = 0,
+		mixRotate = 0, mixX = 0, mixY = 0, mixScaleX = 0, mixScaleY = 0, mixShearY = 0,
 		offsetRotation = 0, offsetX = 0, offsetY = 0, offsetScaleX = 0, offsetScaleY = 0, offsetShearY = 0,
 		offsetRotation = 0, offsetX = 0, offsetY = 0, offsetScaleX = 0, offsetScaleY = 0, offsetShearY = 0,
 		relative = false,
 		relative = false,
 		local_ = false
 		local_ = false

+ 6 - 7
spine-lua/spine-lua/attachments/VertexAttachment.lua

@@ -37,7 +37,6 @@ local AttachmentType = require "spine-lua.attachments.AttachmentType"
 local Attachment = require "spine-lua.attachments.Attachment"
 local Attachment = require "spine-lua.attachments.Attachment"
 
 
 local nextID = 0
 local nextID = 0
-local SHL_11 = 2048
 
 
 local VertexAttachment = {}
 local VertexAttachment = {}
 VertexAttachment.__index = VertexAttachment
 VertexAttachment.__index = VertexAttachment
@@ -45,16 +44,16 @@ setmetatable(VertexAttachment, { __index = Attachment })
 
 
 function VertexAttachment.new (name, attachmentType)
 function VertexAttachment.new (name, attachmentType)
 	local self = Attachment.new(name, attachmentType)
 	local self = Attachment.new(name, attachmentType)
-	self.vertexAttachment = true
+
+	self.id = nextID
+	nextID = nextID + 1
+
+	self.isVertexAttachment = true
 	self.bones = nil
 	self.bones = nil
 	self.vertices = nil
 	self.vertices = nil
 	self.worldVerticesLength = 0
 	self.worldVerticesLength = 0
-	while nextID > 65535 do
-		nextID = nextID - 65535
-	end
-	self.id = nextID * SHL_11
 	self.deformAttachment = self
 	self.deformAttachment = self
-	nextID = nextID + 1
+
 	setmetatable(self, VertexAttachment)
 	setmetatable(self, VertexAttachment)
 	return self
 	return self
 end
 end