Explorar el Código

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

Nathan Sweet hace 4 años
padre
commit
137c3f69ed

+ 1 - 1
spine-love/main.lua

@@ -113,8 +113,8 @@ end
 function love.load(arg)
 	if arg[#arg] == "-debug" then require("mobdebug").start() end
 	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("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("coin-pro", "coin", "animation", nil, 0.5, 400, 300))
 	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 setmetatable = setmetatable
-local math_floor = math_floor
+local math_floor = math.floor
 local math_abs = math.abs
 local math_signum = utils.signum
 
@@ -64,14 +64,14 @@ function Animation.new (name, timelines, duration)
 		self.timelineIds = {}
 		for i,timeline in ipairs(self.timelines) do
 			for _,id in ipairs(timeline.propertyIds) do
-				timelineIds[id] = true
+				self.timelineIds[id] = true
 			end
 		end
 	end
 
 	function self:hasTimeline (ids)
 		for _,id in ipairs(ids) do
-			if timelineIds[id] then return true end
+			if self.timelineIds[id] then return true end
 		end
 		return false
 	end
@@ -134,11 +134,28 @@ 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 = {}
-function Animation.Timeline.new (frameCount, propertyIds)
+function Animation.Timeline.new (timelineType, frameEntries, frameCount, propertyIds)
 	local self = {
+		timelineType = timelineType,
 		propertyIds = propertyIds,
-		frames = utils.newNumberArrayZero((frameCount - 1) * self:getFrameEntries())
+		frames = utils.newNumberArrayZero((frameCount - 1) * frameEntries)
 	}
 
 	function self:getFrameEntries ()
@@ -158,7 +175,8 @@ end
 
 local function search1 (frames, time)
 	local n = zlen(frames)
-	while i <= n do
+	local i = 1
+	while i < n do
 		if frames[i] > time then return i - 1 end
 		i = i + 1
 	end
@@ -169,7 +187,7 @@ Animation.Timeline.search1 = search1
 local function search (frames, time, step)
 	local n = zlen(frames)
 	local i = step
-	while i <= n do
+	while i < n do
 		if frames[i] > time then return i - step end
 		i = i + step
 	end
@@ -182,14 +200,15 @@ local BEZIER = 2
 local BEZIER_SIZE = 18
 
 Animation.CurveTimeline = {}
-function Animation.CurveTimeline.new (frameCount, bezierCount, propertyIds)
+function Animation.CurveTimeline.new (timelineType, frameEntries, frameCount, bezierCount, propertyIds)
 	local LINEAR = 0
 	local STEPPED = 1
 	local BEZIER = 2
 	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[frameCount - 1] = STEPPED
 
 	function self:getFrameCount ()
 		return math_floor(zlen(self.curves) / BEZIER_SIZE) + 1
@@ -261,11 +280,11 @@ function Animation.CurveTimeline.new (frameCount, bezierCount, propertyIds)
 end
 
 Animation.CurveTimeline1 = {}
-function Animation.CurveTimeline1.new (frameCount, bezierCount, propertyId)
+function Animation.CurveTimeline1.new (timelineType, frameCount, bezierCount, propertyId)
 	local ENTRIES = 2
 	local VALUE = 1
 
-	local self = Animation.CurveTimeline.new(frameCount, bezierCount, { propertyId })
+	local self = Animation.CurveTimeline.new(timelineType, ENTRIES, frameCount, bezierCount, { propertyId })
 
 	function self:getFrameEntries ()
 		return ENTRIES
@@ -303,12 +322,12 @@ function Animation.CurveTimeline1.new (frameCount, bezierCount, propertyId)
 end
 
 Animation.CurveTimeline2 = {}
-function Animation.CurveTimeline2.new (frameCount, bezierCount, propertyId1, propertyId2)
+function Animation.CurveTimeline2.new (timelineType, frameCount, bezierCount, propertyId1, propertyId2)
 	local ENTRIES = 3
 	local VALUE1 = 1
 	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 ()
 		return ENTRIES
@@ -326,7 +345,7 @@ end
 
 Animation.RotateTimeline = {}
 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
 
 	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
@@ -358,7 +377,11 @@ end
 
 Animation.TranslateTimeline = {}
 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.y.."|"..boneIndex
 	)
@@ -382,7 +405,7 @@ function Animation.TranslateTimeline.new (frameCount, bezierCount, boneIndex)
 
 		local x = 0
 		local y = 0
-		local frame = search2(frames, time, ENTRIES)
+		local i = search(frames, time, ENTRIES)
 		local curveType = self.curves[math_floor(i / ENTRIES)]
 		if curveType == LINEAR then
 			local before = frames[i]
@@ -416,7 +439,7 @@ end
 
 Animation.TranslateXTimeline = {}
 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
 
 	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
@@ -448,7 +471,7 @@ end
 
 Animation.TranslateYTimeline = {}
 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
 
 	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
@@ -480,7 +503,11 @@ end
 
 Animation.ScaleTimeline = {}
 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.scaleY.."|"..boneIndex
 	)
@@ -504,7 +531,7 @@ function Animation.ScaleTimeline.new (frameCount, bezierCount, boneIndex)
 
 		local x = 0
 		local y = 0
-		local i = search2(frames, time, ENTRIES)
+		local i = search(frames, time, ENTRIES)
 		local curveType = self.curves[math_floor(i / ENTRIES)]
 		if curveType == LINEAR then
 			local before = frames[i]
@@ -577,7 +604,7 @@ end
 
 Animation.ScaleXTimeline = {}
 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
 
 	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
@@ -634,7 +661,7 @@ end
 
 Animation.ScaleYTimeline = {}
 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
 
 	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
@@ -690,8 +717,12 @@ function Animation.ScaleYTimeline.new (frameCount, bezierCount, boneIndex)
 end
 
 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.shearY.."|"..boneIndex
 	)
@@ -715,7 +746,7 @@ function Animation.ShearTimeline.new (frameCount)
 
 		local x = 0
 		local y = 0
-		local i = search2(frames, time, ENTRIES)
+		local i = search(frames, time, ENTRIES)
 		local curveType = self.curves[math_floor(i / ENTRIES)]
 		if curveType == LINEAR then
 			local before = frames[i]
@@ -748,8 +779,8 @@ function Animation.ShearTimeline.new (frameCount)
 end
 
 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
 
 	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
@@ -780,8 +811,8 @@ function Animation.ShearXTimeline.new (frameCount)
 end
 
 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
 
 	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 A = 4
 
-	local self = Animation.CurveTimeline.new(frameCount, bezierCount, {
+	local self = Animation.CurveTimeline.new(TimelineType.rgba, ENTRIES, frameCount, bezierCount, {
 		Property.rgb.."|"..slotIndex,
 		Property.alpha.."|"..slotIndex
 	})
@@ -856,7 +887,7 @@ function Animation.RGBATimeline.new (frameCount, bezierCount, slotIndex)
 		end
 
 		local r, g, b, a
-		local i = search2(frames, time, ENTRIES)
+		local i = search(frames, time, ENTRIES)
 		local curveType = self.curves[i / ENTRIES]
 		if curveType == LINEAR then
 			local before = frames[i]
@@ -899,7 +930,7 @@ function Animation.RGBTimeline.new (frameCount, bezierCount, slotIndex)
 	local G = 2
 	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
 	
 	function self:getFrameEntries ()
@@ -935,7 +966,7 @@ function Animation.RGBTimeline.new (frameCount, bezierCount, slotIndex)
 		end
 
 		local r, g, b
-		local i = search2(frames, time, ENTRIES)
+		local i = search(frames, time, ENTRIES)
 		local curveType = self.curves[i / ENTRIES]
 		if curveType == LINEAR then
 			local before = frames[i]
@@ -978,7 +1009,7 @@ end
 
 Animation.AlphaTimeline = {}
 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
 
 	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 B2 = 7
 
-	local self = Animation.CurveTimeline.new(frameCount, bezierCount, {
+	local self = Animation.CurveTimeline.new(TimelineType.rgba2, ENTRIES, frameCount, bezierCount, {
 		Property.rgb.."|"..slotIndex,
 		Property.alpha.."|"..slotIndex,
 		Property.rgb2.."|"..slotIndex
@@ -1069,7 +1100,7 @@ function Animation.RGBA2Timeline.new (frameCount, bezierCount, slotIndex)
 		end
 
 		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)]
 		if curveType == LINEAR then
 			local before = frames[i]
@@ -1139,7 +1170,7 @@ function Animation.RGB2Timeline.new (frameCount, bezierCount, slotIndex)
 	local G2 = 5
 	local B2 = 6
 
-	local self = Animation.CurveTimeline.new(frameCount, bezierCount, {
+	local self = Animation.CurveTimeline.new(TimelineType.rgb2, ENTRIES, frameCount, bezierCount, {
 		Property.rgb.."|"..slotIndex,
 		Property.rgb2.."|"..slotIndex
 	})
@@ -1189,7 +1220,7 @@ function Animation.RGB2Timeline.new (frameCount, bezierCount, slotIndex)
 		end
 
 		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)]
 		if curveType == LINEAR then
 			local before = frames[i]
@@ -1254,7 +1285,7 @@ end
 
 Animation.AttachmentTimeline = {}
 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.attachmentNames = {}
 
@@ -1311,7 +1342,7 @@ end
 
 Animation.DeformTimeline = {}
 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.attachment = attachment
 	self.vertices = {}
@@ -1353,7 +1384,7 @@ function Animation.DeformTimeline.new (frameCount, bezierCount, slotIndex, attac
 		end
 	end
 
-	function getCurvePercent (time, frame)
+	function self:getCurvePercent (time, frame)
 		local curves = self.curves
 		local i = curves[frame]
 		if i == LINEAR then
@@ -1387,7 +1418,7 @@ function Animation.DeformTimeline.new (frameCount, bezierCount, slotIndex, attac
 		if not slot.bone.active then return end
 
 		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 deform = slot.deform
@@ -1604,7 +1635,7 @@ end
 Animation.EventTimeline = {}
 local eventPropertyIds = { Property.event }
 function Animation.EventTimeline.new (frameCount)
-	local self = Animation.Timeline.new(frameCount, eventPropertyIds)
+	local self = Animation.Timeline.new(TimelineType.event, 1, frameCount, eventPropertyIds)
 	self.events = {}
 
 	function self:getFrameCount ()
@@ -1635,7 +1666,7 @@ function Animation.EventTimeline.new (frameCount)
 		if lastTime < frames[0] then
 			i = 0
 		else
-			i = binarySearch1(frames, lastTime)
+			i = search1(frames, lastTime) + 1
 			local i = frames[i]
 			while i > 0 do -- Fire multiple events with the same frame.
 				if frames[i - 1] ~= i then break end
@@ -1654,7 +1685,7 @@ end
 Animation.DrawOrderTimeline = {}
 local drawOrderPropertyIds = { Property.drawOrder }
 function Animation.DrawOrderTimeline.new (frameCount)
-	local self = Animation.Timeline.new(frameCount, drawOrderPropertyIds)
+	local self = Animation.Timeline.new(TimelineType.drawOrder, 1, frameCount, drawOrderPropertyIds)
 	self.drawOrders = {}
 
 	function self:getFrameCount ()
@@ -1667,6 +1698,9 @@ function Animation.DrawOrderTimeline.new (frameCount)
 	end
 
 	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
+		local drawOrder = skeleton.drawOrder
+		local slots = skeleton.slots
+
 		if direction == MixDirection.mixOut then
 			if blend == MixBlend.setup then
 				for i,slot in ipairs(slots) do
@@ -1691,8 +1725,6 @@ function Animation.DrawOrderTimeline.new (frameCount)
 				drawOrder[i] = slots[i]
 			end
 		else
-			local drawOrder = skeleton.drawOrder
-			local slots = skeleton.slots
 			for i,setupIndex in ipairs(drawOrderToSetupIndex) do
 				drawOrder[i] = skeleton.slots[setupIndex]
 			end
@@ -1711,7 +1743,7 @@ function Animation.IkConstraintTimeline.new (frameCount, bezierCount, ikConstrai
 	local COMPRESS = 4
 	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
 
 	function self:getFrameEntries ()
@@ -1804,7 +1836,7 @@ function Animation.IkConstraintTimeline.new (frameCount, bezierCount, ikConstrai
 end
 
 Animation.TransformConstraintTimeline = {}
-function Animation.TransformConstraintTimeline.new (frameCount, transformConstraintIndex)
+function Animation.TransformConstraintTimeline.new (frameCount, bezierCount, transformConstraintIndex)
 	local ENTRIES = 7
 	local ROTATE = 1
 	local X = 2
@@ -1813,7 +1845,7 @@ function Animation.TransformConstraintTimeline.new (frameCount, transformConstra
 	local SCALEY = 5
 	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
 
 	function self:getFrameEntries ()
@@ -1918,7 +1950,7 @@ end
 
 Animation.PathConstraintPositionTimeline = {}
 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
 
 	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
@@ -1948,7 +1980,7 @@ end
 
 Animation.PathConstraintSpacingTimeline = {}
 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
 
 	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 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
 
 	function self:getFrameEntries ()
@@ -2006,13 +2038,13 @@ function Animation.PathConstraintMixTimeline.new (frameCount, bezierCount, pathC
 		local frames = self.frames
 		if time < frames[0] 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
-				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
 			return
 		end
@@ -2020,36 +2052,36 @@ function Animation.PathConstraintMixTimeline.new (frameCount, bezierCount, pathC
 		local rotate
 		local x
 		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
-			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
-			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
-			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
 
 		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
-			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
 

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

@@ -198,12 +198,22 @@ function TrackEntry:getAnimationTime ()
 	return math_min(self.trackTime + self.animationStart, self.animationEnd)
 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 ()
 	self.timelinesRotation = {}
 end
 
 local AnimationState = {}
 AnimationState.__index = AnimationState
+AnimationState.TrackEntry = TrackEntry
 
 function AnimationState.new (data)
 	if not data then error("data cannot be nil", 2) end
@@ -225,8 +235,6 @@ function AnimationState.new (data)
 	return self
 end
 
-AnimationState.TrackEntry = TrackEntry
-
 function AnimationState:update (delta)
 	delta = delta * self.timeScale
 	local tracks = self.tracks
@@ -359,31 +367,35 @@ function AnimationState:apply (skeleton)
 				-- Apply current entry.
 				local animationLast = current.animationLast
 				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
 				if (i == 0 and mix == 1) or blend == MixBlend.add then
 					for i,timeline in ipairs(timelines) do
 						if timeline.type == Animation.TimelineType.attachment then
-							self:applyAttachmentTimeline(timeline, skeleton, animationTime, blend, true)
+							self:applyAttachmentTimeline(timeline, skeleton, applyTime, blend, true)
 						else
-							timeline:apply(skeleton, animationLast, animationTime, self.events, mix, blend, MixDirection.mixIn)
+							timeline:apply(skeleton, animationLast, applyTime, applyEvents, mix, blend, MixDirection.mixIn)
 						end
 					end
 				else
 					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
 						local timelineBlend = MixBlend.setup
 						if timelineMode[ii] == SUBSEQUENT then timelineBlend = blend end
 
 						if timeline.type == Animation.TimelineType.rotate then
-							self:applyRotateTimeline(timeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, ii * 2,
-									firstFrame)
+							self:applyRotateTimeline(timeline, skeleton, applyTime, mix, timelineBlend, current.timelinesRotation, ii * 2, firstFrame)
 						elseif timeline.type == Animation.TimelineType.attachment then
-							self:applyAttachmentTimeline(timeline, skeleton, animationTime, timelineBlend, true)
+							self:applyAttachmentTimeline(timeline, skeleton, applyTime, timelineBlend, true)
 						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
@@ -401,7 +413,7 @@ function AnimationState:apply (skeleton)
 	-- the time is before the first key).
 	local setupState = self.unkeyedState + SETUP
 	local slots = skeleton.slots
-	for _, slot in ipairs(slots) do
+	for _,slot in ipairs(slots) do
 		if slot.attachmentState == setupState then
 			local attachmentName = slot.data.attachmentName
 			if attachmentName == nil then
@@ -432,25 +444,31 @@ function AnimationState:applyMixingFrom (to, skeleton, blend)
 		if blend ~= MixBlend.first then blend = from.mixBlend end
 	end
 
-	local events = nil
-	if mix < from.eventThreshold then events = self.events end
+	
 	local attachments = mix < from.attachmentThreshold
 	local drawOrder = mix < from.drawOrderThreshold
-	local animationLast = from.animationLast
-	local animationTime = from:getAnimationTime()
 	local timelines = from.animation.timelines
 	local alphaHold = from.alpha * to.interruptAlpha
 	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
 		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
 	else
 		local timelineMode = from.timelineMode
 		local timelineHoldMix = from.timelineHoldMix
 
-		local firstFrame = #from.timelinesRotation == 0
+		local firstFrame = #from.timelinesRotation ~= #timelines
 		local timelinesRotation = from.timelinesRotation
 
 		from.totalAlpha = 0
@@ -473,7 +491,7 @@ function AnimationState:applyMixingFrom (to, skeleton, blend)
 			elseif timelineMode[i] == HOLD_FIRST then
 				timelineBlend = MixBlend.setup
 				alpha = alphaHold
-			else
+			else -- HOLD_MIX
 				timelineBlend = MixBlend.setup
 				local holdMix = timelineHoldMix[i]
 				alpha = alphaHold * math_max(0, 1 - holdMix.mixtime / holdMix.mixDuration)
@@ -482,14 +500,14 @@ function AnimationState:applyMixingFrom (to, skeleton, blend)
 			if not skipSubsequent then
 				from.totalAlpha = from.totalAlpha + alpha
 				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
-					self:applyAttachmentTimeline(timeline, skeleton, animationTime, timelineBlend, attachments)
+					self:applyAttachmentTimeline(timeline, skeleton, applyTime, timelineBlend, attachments)
 				else
 					if drawOrder and timeline.type == Animation.TimelineType.drawOrder and timelineBlend == MixBlend.setup then
 						direction = MixDirection.mixIn
 					end
-					timeline:apply(skeleton, animationLast, animationTime, self.events, alpha, timelineBlend, direction)
+					timeline:apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction)
 				end
 			end
 		end
@@ -509,19 +527,12 @@ function AnimationState:applyAttachmentTimeline(timeline, skeleton, time, blend,
 	local slot = skeleton.slots[timeline.slotIndex]
 	if slot.bone.active == false then return end
 
-	local frames = timeline.frames
-	if time < frames[0] then -- Time is before first frame.
+	if time < timeline.frames[0] then -- Time is before first frame.
 		if blend == MixBlend.setup or blend == MixBlend.first then
 			self:setAttachment(skeleton, slot, slot.data.attachmentName, attachments)
 		end
 	else
-		local frameIndex = 0
-		if time >= frames[zlen(frames) - 1] then -- Time is after last frame.
-			frameIndex = zlen(frames) - 1
-		else
-			frameIndex = Animation.binarySearch(frames, time, 1) - 1
-		end
-		self:setAttachment(skeleton, slot, timeline.attachmentNames[frameIndex], attachments)
+		self:setAttachment(skeleton, slot, timeline.attachmentNames[Timeline.search1(timeline.frames, time)], attachments)
 	end
 
 	-- 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
 	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
+	local frames = timeline.frames
 	local r1 = 0
 	local r2 = 0
 	if time < frames[0] then
@@ -570,21 +580,7 @@ function AnimationState:applyRotateTimeline (timeline, skeleton, time, alpha, bl
 		else
 			r1 = bone.rotation
 		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
 
 	-- 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
 	end
 	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
 
 function AnimationState:queueEvents (entry, animationTime)
@@ -654,7 +649,7 @@ function AnimationState:queueEvents (entry, animationTime)
 	-- Queue events after complete.
 	while i <= n do
 		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)
 		end
 		i = i + 1
@@ -701,14 +696,17 @@ function AnimationState:clearTrack (trackIndex)
 	queue:drain()
 end
 
+function AnimationState:clearNext (entry)
+	self:disposeNext(entry.next)
+end
+
 function AnimationState:setCurrent (index, current, interrupt)
 	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 interrupt then queue:interrupt(from) end
+		if interrupt then self.queue:interrupt(from) end
 		current.mixingFrom = from
 		from.mixingTo = current
 		current.mixTime = 0
@@ -720,7 +718,7 @@ function AnimationState:setCurrent (index, current, interrupt)
 		from.timelinesRotation = {}
 	end
 
-	queue:start(current)
+	self.queue:start(current)
 end
 
 function AnimationState:setAnimationByName (trackIndex, animationName, loop)
@@ -779,19 +777,8 @@ function AnimationState:addAnimation (trackIndex, animation, loop, delay)
 		queue:drain()
 	else
 		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
 
 	entry.delay = delay
@@ -806,10 +793,14 @@ function AnimationState:setEmptyAnimation (trackIndex, mixDuration)
 end
 
 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.trackEnd = mixDuration
+	if delay <= 0 and entry.previous then
+		entry.delay = entry.previous:getTrackComplete() - entry.mixDuration + delay
+	end
 	return entry
 end
 
@@ -895,28 +886,19 @@ function AnimationState:_animationsChanged ()
 	self.animationsChanged = false
 
 	self.propertyIDs = {}
-
-	local highestIndex = -1
 	local tracks = self.tracks
-	local numTracks = getNumTracks(tracks)
 	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 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
+			repeat
+				if not entry.mixingTo or entry.mixBlend ~= MixBlend.add then self:computeHold(entry) end
+				entry = entry.mixingTo
+			until not entry
 		end
 		i = i + 1
 	end
@@ -925,57 +907,57 @@ end
 function AnimationState:computeHold(entry)
 	local to = entry.mixingTo
 	local timelines = entry.animation.timelines
-	local timelinesCount = #entry.animation.timelines
 	local timelineMode = entry.timelineMode
 	local timelineHoldMix = entry.timelineHoldMix
 	local propertyIDs = self.propertyIDs
 
 	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
+			timelineMode[i] = mode
 		end
 		return
 	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
+		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
-			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
-					next = next.mixingTo
+					break
 				end
-				if not skip then 	timelineMode[i] = HOLD_FIRST end
+				next = next.mixingTo
 			end
+			if not set then timelineMode[i] = HOLD_FIRST end
 		end
-		i = i + 1
 	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.
 -------------------------------------------------------------------------------
 
+local TransformMode = require "spine-lua.TransformMode"
+
 local setmetatable = setmetatable
 local math_rad = math.rad
 local math_deg = math.deg
@@ -37,19 +39,6 @@ local math_sqrt = math.sqrt
 local math_abs = math.abs
 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 = {}
 Bone.__index = Bone
 
@@ -64,7 +53,6 @@ function Bone.new (data, skeleton, parent)
 		children = { },
 		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,
-		appliedValid = false,
 
 		a = 0, b = 0, worldX = 0, -- a b x
 		c = 0, d = 0, worldY = 0, -- c d y
@@ -78,7 +66,7 @@ function Bone.new (data, skeleton, parent)
 end
 
 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
 
 function Bone:updateWorldTransform ()
@@ -93,13 +81,12 @@ function Bone:updateWorldTransformWith (x, y, rotation, scaleX, scaleY, shearX,
 	self.ascaleY = scaleY
 	self.ashearX = shearX
 	self.ashearY = shearY
-	self.appliedValid = true
 
 	local sx = self.skeleton.scaleX
 	local sy = self.skeleton.scaleY
 
 	local parent = self.parent
-	if parent == nil then
+	if not parent then
 		local rotationY = rotation + 90 + shearY
 		local rotationRad = math_rad(rotation + shearX)
 		local rotationYRad = math_rad(rotationY)
@@ -225,7 +212,7 @@ end
 
 function Bone:updateAppliedTransform ()
 	local parent = self.parent
-	if parent == nil then
+	if not parent then
 		self.ax = self.worldX
 		self.ay = self.worldY
 		self.arotation = math_deg(math_atan2(self.c, self.a))
@@ -268,15 +255,11 @@ function Bone:updateAppliedTransform ()
 end
 
 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 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
 end
 
@@ -313,7 +296,6 @@ function Bone:rotateWorld (degrees)
 	self.b = cos * b - sin * d
 	self.c = sin * a + cos * c
 	self.d = sin * b + cos * d
-	self.appliedValid = false
 end
 
 return Bone

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

@@ -76,6 +76,7 @@ function IkConstraint:apply ()
 end
 
 function IkConstraint:update ()
+	if self.mix == 0 then return end
 	local target = self.target
 	local bones = self.bones
 	local boneCount = #bones
@@ -87,9 +88,7 @@ function IkConstraint:update ()
 end
 
 function IkConstraint:apply1 (bone, targetX, targetY, compress, stretch, uniform, alpha)
-	if not bone.appliedValid then bone:updateAppliedTransform() end
 	local p = bone.parent
-
 	local pa = p.a
 	local pb = p.b
 	local pc = p.c
@@ -148,12 +147,6 @@ function IkConstraint:apply1 (bone, targetX, targetY, compress, stretch, uniform
 	end
 
 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 py = parent.ay
 	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 utils = require "spine-lua.utils"
 local math_pi = math.pi
-local math_pi2 = math.pi * 2
+local math_pi2 = math_pi * 2
 local math_atan2 = math.atan2
 local math_sqrt = math.sqrt
 local math_acos = math.acos
@@ -51,10 +51,10 @@ local math_max = math.max
 local 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)
 	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),
 		position = data.position,
 		spacing = data.spacing,
-		rotateMix = data.rotateMix,
-		translateMix = data.translateMix,
+		mixRotate = data.mixRotate,
+		mixX = data.mixX,
+		mixY = data.mixY,
 		spaces = {},
 		positions = {},
 		world = {},
@@ -85,81 +86,113 @@ function PathConstraint.new (data, skeleton)
 	return self
 end
 
-function PathConstraint:apply ()
-	self:update()
-end
-
 function PathConstraint:update ()
 	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 percentSpacing = data.spacingMode == PathConstraintData.SpacingMode.percent
-	local rotateMode = data.rotateMode
 	local tangents = rotateMode == PathConstraintData.RotateMode.tangent
 	local scale = rotateMode == PathConstraintData.RotateMode.chainscale
+
 	local bones = self.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 lengths = nil
+	if scale then lengths = Utils.setArraySize(this.lengths, boneCount) end
 	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
+		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
 		while i < n do
-			local bone = bones[i + 1]
+			local bone = bones[i]
 			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
-				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
-	 			local x = setupLength * bone.a
+				local x = setupLength * bone.a
 				local y = setupLength * bone.c
 				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
+				local s
 				if lengthSpacing then
-					spaces[i + 1] = (setupLength + spacing) * length / setupLength
+					s = setupLength + spacing
 				else
-					spaces[i + 1] = spacing * length / setupLength
+					s = spacing
 				end
+				spaces[i] = s * length / setupLength
 			end
 		end
-	else
-		local i = 1
-		while i < spacesCount do
-			spaces[i + 1] = spacing
-			i = i + 1
-		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 boneY = positions[2]
 	local offsetRotation = data.offsetRotation
 	local tip = false
 	if offsetRotation == 0 then
-			tip = rotateMode == PathConstraintData.RotateMode.chain
+		tip = data.rotateMode == PathConstraintData.RotateMode.chain
 	else
 		tip = false
 		local p = self.target.bone
@@ -174,8 +207,8 @@ function PathConstraint:update ()
 	local p = 3
 	while i < boneCount do
 		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 y = positions[p + 2]
 		local dx = x - boneX
@@ -183,14 +216,14 @@ function PathConstraint:update ()
 		if scale then
 			local length = lengths[i + 1]
 			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.c = bone.c * s
 			end
 		end
 		boneX = x
 		boneY = y
-		if rotate then
+		if mixRotate then
 			local a = bone.a
 			local b = bone.b
 			local c = bone.c
@@ -210,8 +243,8 @@ function PathConstraint:update ()
 				cos = math_cos(r)
 				sin = math_sin(r)
 				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
 				r = r + offsetRotation
 			end
@@ -220,21 +253,21 @@ function PathConstraint:update ()
 			elseif r < -math_pi then
 				r = r + math_pi2
 			end
-			r = r * rotateMix
+			r = r * mixRotate
 			cos = math_cos(r)
-			sin = math.sin(r)
+			sin = math_sin(r)
 			bone.a = cos * a - sin * c
 			bone.b = cos * b - sin * d
 			bone.c = sin * a + cos * c
 			bone.d = sin * b + cos * d
 		end
-		bone.appliedValid = false
+		bone:updateAppliedTransform()
 		i = i + 1
 		p = p + 3
 	end
 end
 
-function PathConstraint:computeWorldPositions (path, spacesCount, tangents, percentPosition, percentSpacing)
+function PathConstraint:computeWorldPositions (path, spacesCount, tangents)
 	local target = self.target
 	local position = self.position
 	local spaces = self.spaces
@@ -250,20 +283,20 @@ function PathConstraint:computeWorldPositions (path, spacesCount, tangents, perc
 		local lengths = path.lengths
 		if closed then curveCount = curveCount - 1 else curveCount = curveCount - 2 end
 		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
 		world = utils.setArraySize(self.world, 8)
 		i = 0
 		local o = 0
 		local curve = 0
 		while i < spacesCount do
-			local space = spaces[i + 1]
+			local space = spaces[i + 1] * multiplier
 			position = position + space
 			local p = position
 
@@ -389,17 +422,14 @@ function PathConstraint:computeWorldPositions (path, spacesCount, tangents, perc
 		i = i + 1
 		w = w + 6
 	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
 
 	local segments = self.segments
@@ -409,7 +439,7 @@ function PathConstraint:computeWorldPositions (path, spacesCount, tangents, perc
 	local curve = 0
 	local segment = 0
 	while i < spacesCount do
-		local space = spaces[i + 1]
+		local space = spaces[i + 1] * multiplier
 		position = position + space
 		local p = position
 

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

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

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

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

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

@@ -558,9 +558,9 @@ function SkeletonJson.new (attachmentLoader)
 				for timelineName,timelineMap in pairs(slotMap) do
 					if not timelineMap 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
-							timeline:setFrame(i + 1, getValue(keyMap, "time", 0), keyMap["name"])
+							timeline:setFrame(i - 1, getValue(keyMap, "time", 0), keyMap["name"])
 						end
 						table_insert(timelines, timeline)
 					elseif timelineName == "rgba" then
@@ -765,7 +765,7 @@ function SkeletonJson.new (attachmentLoader)
 						table_insert(timelines, readTimeline1(timelineMap, timeline, 0, scale))
 					elseif timelineName == "scale" then
 						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
 						local timeline = Animation.ScaleXTimeline.new(#timelineMap, #timelineMap, boneIndex)
 						table_insert(timelines, readTimeline1(timelineMap, timeline, 1, 1))
@@ -774,7 +774,7 @@ function SkeletonJson.new (attachmentLoader)
 						table_insert(timelines, readTimeline1(timelineMap, timeline, 1, 1))
 					elseif timelineName == "shear" then
 						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
 						local timeline = Animation.ShearXTimeline.new(#timelineMap, #timelineMap, boneIndex)
 						table_insert(timelines, readTimeline1(timelineMap, timeline, 0, 1))
@@ -901,7 +901,7 @@ function SkeletonJson.new (attachmentLoader)
 		if map.path then
 			for constraintName,constraintMap in pairs(map.path) do
 				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
 						constraintIndex = i
 						constraint = other
@@ -914,12 +914,12 @@ function SkeletonJson.new (attachmentLoader)
 						if timelineName == "position" then
 							local timeline = Animation.PathConstraintPositionTimeline.new(#timelineMap, #timelineMap, constraintIndex)
 							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))
 						elseif timelineName == "spacing" then
 							local timeline = Animation.PathConstraintSpacingTimeline.new(#timelineMap, #timelineMap, constraintIndex)
 							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))
 						elseif timelineName == "mix" then
 							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
 
 							local timeline = Animation.DeformTimeline.new(#timelineMap, #timelineMap, slotIndex, attachment)
+							local time = getValue(keyMap, "time", 0)
 							local bezier = 0
 							for i,keyMap in ipairs(timelineMap) do
 								local deform = nil
@@ -1007,7 +1008,7 @@ function SkeletonJson.new (attachmentLoader)
 									end
 								end
 								local frame = i - 1
-								timeline:setFrame(frame, time, mixRotate, mixX, mixY)
+								timeline:setFrame(frame, time, deform)
 								local nextMap = timelineMap[frame + 1]
 								if not nextMap then
 									timeline:shrink(bezier)
@@ -1172,15 +1173,15 @@ function SkeletonJson.new (attachmentLoader)
 
 	readCurve = function (curve, timeline, bezier, frame, value, time1, time2, value1, value2, scale)
 		if curve == "stepped" then
-			if value ~= 0 then timeline.setStepped(frame) end
+			if value ~= 0 then timeline:setStepped(frame) end
 			return bezier
 		end
-		local i = value * 4
+		local i = value * 4 + 1
 		local cx1 = curve[i]
 		local cy1 = curve[i + 1] * scale
 		local cx2 = curve[i + 2]
 		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
 	end
 

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

@@ -60,9 +60,16 @@ end
 
 function Slot:setAttachment (attachment)
 	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.attachmentTime = self.bone.skeleton.time
-	self.deform = {}
 end
 
 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
 -- 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
+-- 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,
@@ -28,6 +28,7 @@
 -------------------------------------------------------------------------------
 
 local setmetatable = setmetatable
+local tonumber = tonumber
 local table_insert = table.insert
 local math_abs = math.abs
 
@@ -47,7 +48,8 @@ function TextureAtlasPage.new ()
 		vWrap = nil,
 		texture = nil,
 		width = 0,
-		height = 0
+		height = 0,
+		pma = false
 	}
 	setmetatable(self, TextureAtlasPage)
 	return self
@@ -73,168 +75,178 @@ function TextureAtlas:parse (atlasContent, imageLoader)
 	if not atlasContent then error("atlasContent 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
 
-	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 lastMatch = idx + 1
-		local tuple = {}
-		while i <= 3 do
+		while true do
 			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
+			if i == 4 then return 4 end
 			i = i + 1
 		end
-		tuple[i] = line:sub(lastMatch):match'^%s*(.*%S)' or ''
-		return tuple
 	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
 
-	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
+	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
-		local line = readLine()
 		if not line then break end
-		line = line:match'^%s*(.*%S)' or ''
 		if line:len() == 0 then
 			page = nil
+			line = readLine()
 		elseif not page then
 			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
-
-			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)
 		else
-			local region = TextureAtlasRegion.new()
-			region.name = line
+			region = TextureAtlasRegion.new()
 			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
-			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
-
-			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
-
-			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
 			table_insert(self.regions, region)
 		end

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

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

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

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

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

@@ -37,7 +37,7 @@ function TransformConstraintData.new (name)
 		skinRequired = false,
 		bones = {},
 		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,
 		relative = 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 nextID = 0
-local SHL_11 = 2048
 
 local VertexAttachment = {}
 VertexAttachment.__index = VertexAttachment
@@ -45,16 +44,16 @@ setmetatable(VertexAttachment, { __index = Attachment })
 
 function VertexAttachment.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.vertices = nil
 	self.worldVerticesLength = 0
-	while nextID > 65535 do
-		nextID = nextID - 65535
-	end
-	self.id = nextID * SHL_11
 	self.deformAttachment = self
-	nextID = nextID + 1
+
 	setmetatable(self, VertexAttachment)
 	return self
 end