소스 검색

[lua] SkeletonJson 4.0 port, clean up.

Nathan Sweet 4 년 전
부모
커밋
3c0a43687b

+ 1611 - 1612
spine-lua/spine-lua/Animation.lua

@@ -1,1612 +1,1611 @@
--------------------------------------------------------------------------------
--- 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.
--------------------------------------------------------------------------------
-
--- FIXME
--- All the indexing in this file is zero based. We use zlen()
--- instead of the # operator. Initialization of number arrays
--- is performed via utils.newNumberArrayZero. This needs
--- to be rewritten using one-based indexing for better performance
-
-local utils = require "spine-lua.utils"
-local AttachmentType = require "spine-lua.attachments.AttachmentType"
-local math_floor = math.floor
-local math_abs = math.abs
-local math_signum = utils.signum
-
-local function zlen(array)
-	return #array + 1
-end
-
-local Animation = {}
-function Animation.new (name, timelines, duration)
-	if not timelines then error("timelines cannot be nil", 2) end
-
-	local self = {
-		name = name,
-		timelines = timelines,
-		timelineIds = {},
-		duration = duration
-	}
-	
-	for i,timeline in ipairs(self.timelines) do
-		self.timelineIds[timeline:getPropertyId()] = true
-	end
-
-	function self:hasTimeline(id)
-		return self.timelineIds[id] == true
-	end
-
-	function self:apply (skeleton, lastTime, time, loop, events, alpha, blend, direction)
-		if not skeleton then error("skeleton cannot be nil.", 2) end
-
-		if loop and duration > 0 then
-			time = time % self.duration
-			if lastTime > 0 then lastTime = lastTime % self.duration end
-		end
-
-		for i,timeline in ipairs(self.timelines) do
-			timeline:apply(skeleton, lastTime, time, events, alpha, blend, direction)
-		end
-	end
-
-	return self
-end
-
-local function binarySearch (values, target, step)
-	local low = 0
-	local high = math.floor(zlen(values) / step - 2)
-	if high == 0 then return step end
-	local current = math.floor(high / 2)
-	while true do
-		if values[(current + 1) * step] <= target then
-			low = current + 1
-		else
-			high = current
-		end
-		if low == high then return (low + 1) * step end
-		current = math.floor((low + high) / 2)
-	end
-end
-Animation.binarySearch = binarySearch
-
-local function binarySearch1 (values, target)
-	local low = 0
-	local high = math.floor(zlen(values)	- 2)
-	if high == 0 then return 1 end
-	local current = math.floor(high / 2)
-	while true do
-		if values[current + 1] <= target then
-			low = current + 1
-		else
-			high = current
-		end
-		if low == high then return low + 1 end
-		current = math.floor((low + high) / 2)
-	end
-end
-
-local function linearSearch (values, target, step)
-	local i = 0
-	local last = zlen(values) - step
-	while i <= last do
-		if (values[i] > target) then return i end
-		i = i + step
-	end
-	return -1
-end
-
-Animation.MixBlend = {
-	setup = 0,
-	first = 1,
-	replace = 2,
-	add = 3
-}
-local MixBlend = Animation.MixBlend
-
-Animation.MixDirection = {
-	_in = 0, out = 1
-}
-local MixDirection = Animation.MixDirection
-
-Animation.TimelineType = {
-	rotate = 0, translate = 1, scale = 2, shear = 3,
-	attachment = 4, color = 5, deform = 6,
-	event = 7, drawOrder = 8,
-	ikConstraint = 9, transformConstraint = 10,
-	pathConstraintPosition = 11, pathConstraintSpacing = 12, pathConstraintMix = 13,
-	twoColor = 14
-}
-local TimelineType = Animation.TimelineType
-local SHL_24 = 16777216
-local SHL_27 = 134217728
-
-Animation.CurveTimeline = {}
-function Animation.CurveTimeline.new (frameCount)
-	local LINEAR = 0
-	local STEPPED = 1
-	local BEZIER = 2
-	local BEZIER_SIZE = 10 * 2 - 1
-
-	local self = {
-		curves = utils.newNumberArrayZero((frameCount - 1) * BEZIER_SIZE) -- type, x, y, ...
-	}
-
-	function self:getFrameCount ()
-		return math.floor(zlen(self.curves) / BEZIER_SIZE) + 1
-	end
-
-	function self:setStepped (frameIndex)
-		self.curves[frameIndex * BEZIER_SIZE] = STEPPED
-	end
-
-	function self:getCurveType (frameIndex)
-		local index = frameIndex * BEZIER_SIZE
-		if index == zlen(self.curves) then return LINEAR end
-		local type = self.curves[index]
-		if type == LINEAR then return LINEAR end
-		if type == STEPPED then return STEPPED end
-		return BEZIER
-	end
-
-	function self:setCurve (frameIndex, cx1, cy1, cx2, cy2)
-			local tmpx = (-cx1 * 2 + cx2) * 0.03
-			local tmpy = (-cy1 * 2 + cy2) * 0.03
-			local dddfx = ((cx1 - cx2) * 3 + 1) * 0.006
-			local dddfy = ((cy1 - cy2) * 3 + 1) * 0.006
-			local ddfx = tmpx * 2 + dddfx
-			local ddfy = tmpy * 2 + dddfy
-			local dfx = cx1 * 0.3 + tmpx + dddfx * 0.16666667
-			local dfy = cy1 * 0.3 + tmpy + dddfy * 0.16666667
-
-			local i = frameIndex * BEZIER_SIZE
-			local curves = self.curves
-			curves[i] = BEZIER
-			i = i + 1
-
-			local x = dfx
-			local y = dfy
-			local n = i + BEZIER_SIZE - 1
-			while i < n do
-				curves[i] = x
-				curves[i + 1] = y
-				dfx = dfx + ddfx
-				dfy = dfy + ddfy
-				ddfx = ddfx + dddfx
-				ddfy = ddfy + dddfy
-				x = x + dfx
-				y = y + dfy
-				i = i + 2
-			end
-	end
-
-	function self:getCurvePercent (frameIndex, percent)
-		percent = utils.clamp(percent, 0, 1)
-		local curves = self.curves
-		local i = frameIndex * BEZIER_SIZE
-		local type = curves[i]
-		if type == LINEAR then return percent end
-		if type == STEPPED then return 0 end
-		i = i + 1
-		local x
-		local n = i + BEZIER_SIZE - 1
-		local start = i
-		while i < n do
-			x = curves[i]
-			if x >= percent then
-				local prevX, prevY
-				if i == start then
-					prevX = 0
-					prevY = 0
-				else
-					prevX = curves[i - 2]
-					prevY = curves[i - 1]
-				end
-				return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX)
-			end
-			i = i + 2
-		end
-		local y = curves[i - 1]
-		return y + (1 - y) * (percent - x) / (1 - x) -- Last point is 1,1.
-	end
-
-	return self
-end
-
-Animation.RotateTimeline = {}
-Animation.RotateTimeline.ENTRIES = 2
-Animation.RotateTimeline.PREV_TIME = -2
-Animation.RotateTimeline.PREV_ROTATION = -1
-Animation.RotateTimeline.ROTATION = 1
-function Animation.RotateTimeline.new (frameCount)
-	local ENTRIES = Animation.RotateTimeline.ENTRIES
-	local PREV_TIME = Animation.RotateTimeline.PREV_TIME
-	local PREV_ROTATION = Animation.RotateTimeline.PREV_ROTATION
-	local ROTATION = Animation.RotateTimeline.ROTATION
-
-	local self = Animation.CurveTimeline.new(frameCount)
-	self.boneIndex = -1
-	self.frames = utils.newNumberArrayZero(frameCount * 2)
-	self.type = TimelineType.rotate
-
-	function self:getPropertyId ()
-		return TimelineType.rotate * SHL_24 + self.boneIndex
-	end
-
-	function self:setFrame (frameIndex, time, degrees)
-		frameIndex = frameIndex * 2
-		self.frames[frameIndex] = time
-		self.frames[frameIndex + ROTATION] = degrees
-	end
-
-	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
-		local frames = self.frames
-
-		local bone = skeleton.bones[self.boneIndex]
-		if not bone.active then return end
-		if time < frames[0] then
-			if blend == MixBlend.setup then
-				bone.rotation = bone.data.rotation
-			elseif blend == MixBlend.first then
-				local r = bone.data.rotation - bone.rotation
-				bone.rotation = bone.rotation + (r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360) * alpha
-			end
-			return
-		end
-
-		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
-			local r = frames[zlen(frames) + PREV_ROTATION]
-			if blend == MixBlend.setup then
-				bone.rotation = bone.data.rotation + r * alpha
-			elseif blend == MixBlend.first or blend == MixBlend.replace then
-				r = r + bone.data.rotation - bone.rotation
-				r = r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360 -- Wrap within -180 and 180.
-				bone.rotation = bone.rotation + r * alpha;
-			elseif blend == MixBlend.add then
-				bone.rotation = bone.rotation + r * alpha;
-			end
-			return;
-		end
-
-		-- Interpolate between the last frame and the current frame.
-		local frame = binarySearch(frames, time, ENTRIES)
-		local prevRotation = frames[frame + PREV_ROTATION]
-		local frameTime = frames[frame]
-		local percent = self:getCurvePercent((math.floor(frame / 2)) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
-
-		local r = frames[frame + ROTATION] - prevRotation
-		r = prevRotation + (r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360) * percent
-		if blend == MixBlend.setup then
-			bone.rotation = bone.data.rotation + (r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360) * alpha
-		elseif blend == MixBlend.first or blend == MixBlend.replace then
-			r = r + bone.data.rotation - bone.rotation;
-			bone.rotation = bone.rotation + (r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360) * alpha
-		elseif blend == MixBlend.add then
-			bone.rotation = bone.rotation + (r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360) * alpha
-		end
-	end
-
-	return self
-end
-
-Animation.TranslateTimeline = {}
-Animation.TranslateTimeline.ENTRIES = 3
-function Animation.TranslateTimeline.new (frameCount)
-	local ENTRIES = Animation.TranslateTimeline.ENTRIES
-	local PREV_TIME = -3
-	local PREV_X = -2
-	local PREV_Y = -1
-	local X = 1
-	local Y = 2
-
-	local self = Animation.CurveTimeline.new(frameCount)
-	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
-	self.boneIndex = -1
-	self.type = TimelineType.translate
-
-	function self:getPropertyId ()
-		return TimelineType.translate * SHL_24 + self.boneIndex
-	end
-
-	function self:setFrame (frameIndex, time, x, y)
-		frameIndex = frameIndex * ENTRIES
-		self.frames[frameIndex] = time
-		self.frames[frameIndex + X] = x
-		self.frames[frameIndex + Y] = y
-	end
-
-	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
-		local frames = self.frames
-
-		local bone = skeleton.bones[self.boneIndex]
-		if not bone.active then return end
-
-		if time < frames[0] then
-			if blend == MixBlend.setup then
-				bone.x = bone.data.x
-				bone.y = bone.data.y
-			elseif blend == MixBlend.first then
-				bone.x = bone.x + (bone.data.x - bone.x) * alpha
-				bone.y = bone.y + (bone.data.y - bone.y) * alpha
-			end
-			return
-		end
-
-		local x = 0
-		local y = 0
-		if time >= frames[zlen(frames) - ENTRIES] then -- // Time is after last frame.
-			x = frames[zlen(frames) + PREV_X];
-			y = frames[zlen(frames) + PREV_Y];
-		else
-			-- Interpolate between the previous frame and the current frame.
-			local frame = binarySearch(frames, time, ENTRIES)
-			x = frames[frame + PREV_X]
-			y = frames[frame + PREV_Y]
-			local frameTime = frames[frame]
-			local percent = self:getCurvePercent(math_floor(frame / ENTRIES) - 1,
-				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
-			x = x + (frames[frame + X] - x) * percent
-			y = y + (frames[frame + Y] - y) * percent
-		end
-		if blend == MixBlend.setup then
-			bone.x = bone.data.x + x * alpha
-			bone.y = bone.data.y + y * alpha
-		elseif blend == MixBlend.first or blend == MixBlend.replace then
-			bone.x = bone.x + (bone.data.x + x - bone.x) * alpha
-			bone.y = bone.y + (bone.data.y + y - bone.y) * alpha
-		elseif blend == MixBlend.add then
-			bone.x = bone.x + x * alpha
-			bone.y = bone.y + y * alpha
-		end
-	end
-
-	return self
-end
-
-Animation.ScaleTimeline = {}
-Animation.ScaleTimeline.ENTRIES = Animation.TranslateTimeline.ENTRIES
-function Animation.ScaleTimeline.new (frameCount)
-	local ENTRIES = Animation.ScaleTimeline.ENTRIES
-	local PREV_TIME = -3
-	local PREV_X = -2
-	local PREV_Y = -1
-	local X = 1
-	local Y = 2
-
-	local self = Animation.TranslateTimeline.new(frameCount)
-	self.type = TimelineType.scale
-
-	function self:getPropertyId ()
-		return TimelineType.scale * SHL_24 + self.boneIndex
-	end
-
-	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
-		local frames = self.frames
-
-		local bone = skeleton.bones[self.boneIndex]
-		if not bone.active then return end
-
-		if time < frames[0] then
-			if blend == MixBlend.setup then
-				bone.scaleX = bone.data.scaleX
-				bone.scaleY = bone.data.scaleY
-			elseif blend == MixBlend.first then
-				bone.scaleX = bone.scaleX + (bone.data.scaleX - bone.scaleX) * alpha
-				bone.scaleY = bone.scaleY + (bone.data.scaleY - bone.scaleY) * alpha
-			end
-			return
-		end
-
-		local x = 0
-		local y = 0
-		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
-			x = frames[zlen(frames) + PREV_X] * bone.data.scaleX
-			y = frames[zlen(frames) + PREV_Y] * bone.data.scaleY
-		else
-			-- Interpolate between the previous frame and the current frame.
-			local frame = binarySearch(frames, time, ENTRIES)
-			x = frames[frame + PREV_X]
-			y = frames[frame + PREV_Y]
-			local frameTime = frames[frame]
-			local percent = self:getCurvePercent(math_floor(frame / ENTRIES) - 1,
-				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
-
-			x = (x + (frames[frame + X] - x) * percent) * bone.data.scaleX
-			y = (y + (frames[frame + Y] - y) * percent) * bone.data.scaleY
-		end
-		if alpha == 1 then
-			if blend == MixBlend.add then
-				bone.scaleX = bone.scaleX + x - bone.data.scaleX
-				bone.scaleY = bone.scaleY + y - bone.data.scaleY
-			else
-				bone.scaleX = x
-				bone.scaleY = y
-			end
-		else
-			local bx = 0
-			local by = 0
-			if direction == MixDirection.out then
-				if blend == MixBlend.setup then
-					bx = bone.data.scaleX
-					by = bone.data.scaleY
-					bone.scaleX = bx + (math_abs(x) * math_signum(bx) - bx) * alpha
-					bone.scaleY = by + (math_abs(y) * math_signum(by) - by) * alpha
-				elseif blend == MixBlend.first or blend == MixBlend.replace then
-					bx = bone.scaleX
-					by = bone.scaleY
-					bone.scaleX = bx + (math_abs(x) * math_signum(bx) - bx) * alpha
-					bone.scaleY = by + (math_abs(y) * math_signum(by) - by) * alpha
-				elseif blend == MixBlend.add then
-					bx = bone.scaleX
-					by = bone.scaleY
-					bone.scaleX = bx + (math_abs(x) * math_signum(bx) - bone.data.scaleX) * alpha
-					bone.scaleY = by + (math_abs(y) * math_signum(by) - bone.data.scaleY) * alpha
-				end
-			else
-				if blend == MixBlend.setup then
-					bx = math_abs(bone.data.scaleX) * math_signum(x)
-					by = math_abs(bone.data.scaleY) * math_signum(y)
-					bone.scaleX = bx + (x - bx) * alpha
-					bone.scaleY = by + (y - by) * alpha
-				elseif blend == MixBlend.first or blend == MixBlend.replace then
-					bx = math_abs(bone.scaleX) * math_signum(x)
-					by = math_abs(bone.scaleY) * math_signum(y)
-					bone.scaleX = bx + (x - bx) * alpha
-					bone.scaleY = by + (y - by) * alpha
-				elseif blend == MixBlend.add then
-					bx = math_signum(x)
-					by = math_signum(y)
-					bone.scaleX = math_abs(bone.scaleX) * bx + (x - math_abs(bone.data.scaleX) * bx) * alpha
-					bone.scaleY = math_abs(bone.scaleY) * by + (y - math_abs(bone.data.scaleY) * by) * alpha
-				end
-			end
-		end
-	end
-
-	return self
-end
-
-Animation.ShearTimeline = {}
-Animation.ShearTimeline.ENTRIES = Animation.TranslateTimeline.ENTRIES
-function Animation.ShearTimeline.new (frameCount)
-	local ENTRIES = Animation.ShearTimeline.ENTRIES
-	local PREV_TIME = -3
-	local PREV_X = -2
-	local PREV_Y = -1
-	local X = 1
-	local Y = 2
-
-	local self = Animation.TranslateTimeline.new(frameCount)
-	self.type = TimelineType.shear
-
-	function self:getPropertyId ()
-		return TimelineType.shear * SHL_24 + self.boneIndex
-	end
-
-	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
-		local frames = self.frames
-
-		local bone = skeleton.bones[self.boneIndex]
-		if not bone.active then return end
-
-		if time < frames[0] then
-			if blend == MixBlend.setup then
-				bone.shearX = bone.data.shearX
-				bone.shearY = bone.data.shearY
-			elseif blend == MixBlend.first then
-				bone.shearX = bone.shearX + (bone.data.shearX - bone.shearX) * alpha
-				bone.shearY = bone.shearX + (bone.data.shearY - bone.shearY) * alpha
-			end
-			return
-		end
-
-		local x = 0
-		local y = 0
-		if time >= frames[zlen(frames) - ENTRIES] then -- // Time is after last frame.
-			x = frames[zlen(frames) + PREV_X]
-			y = frames[zlen(frames) + PREV_Y]
-		else
-			-- Interpolate between the previous frame and the current frame.
-			local frame = binarySearch(frames, time, ENTRIES)
-			x = frames[frame + PREV_X]
-			y = frames[frame + PREV_Y]
-			local frameTime = frames[frame]
-			local percent = self:getCurvePercent(math_floor(frame / ENTRIES) - 1,
-				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
-
-			x = x + (frames[frame + X] - x) * percent
-			y = y + (frames[frame + Y] - y) * percent
-		end
-		if blend == MixBlend.setup then
-			bone.shearX = bone.data.shearX + x * alpha
-			bone.shearY = bone.data.shearY + y * alpha
-		elseif blend == MixBlend.first or blend == MixBlend.replace then
-			bone.shearX = bone.shearX + (bone.data.shearX + x - bone.shearX) * alpha
-			bone.shearY = bone.shearY + (bone.data.shearY + y - bone.shearY) * alpha
-		elseif blend == MixBlend.add then
-			bone.shearX = bone.shearX + x * alpha
-			bone.shearY = bone.shearY + y * alpha
-		end
-	end
-
-	return self
-end
-
-Animation.ColorTimeline = {}
-Animation.ColorTimeline.ENTRIES = 5
-function Animation.ColorTimeline.new (frameCount)
-	local ENTRIES = Animation.ColorTimeline.ENTRIES
-	local PREV_TIME = -5
-	local PREV_R = -4
-	local PREV_G = -3
-	local PREV_B = -2
-	local PREV_A = -1
-	local R = 1
-	local G = 2
-	local B = 3
-	local A = 4
-
-	local self = Animation.CurveTimeline.new(frameCount)
-	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
-	self.slotIndex = -1
-	self.type = TimelineType.color
-
-	function self:getPropertyId ()
-		return TimelineType.color * SHL_24 + self.slotIndex
-	end
-
-	function self:setFrame (frameIndex, time, r, g, b, a)
-		frameIndex = frameIndex * ENTRIES
-		self.frames[frameIndex] = time
-		self.frames[frameIndex + R] = r
-		self.frames[frameIndex + G] = g
-		self.frames[frameIndex + B] = b
-		self.frames[frameIndex + A] = a
-	end
-
-	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
-		local frames = self.frames
-		local slot = skeleton.slots[self.slotIndex]
-		if not slot.bone.active then return end
-		if time < frames[0] then
-			if blend == MixBlend.setup then
-				slot.color:setFrom(slot.data.color)
-			elseif blend == MixBlend.first then
-				local color = slot.color
-				local setup = slot.data.color
-				color:add((setup.r - color.r) * alpha, (setup.g - color.g) * alpha, (setup.b - color.b) * alpha,
-						(setup.a - color.a) * alpha)
-			end
-			return
-		end
-
-		local r, g, b, a
-		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
-			local i = zlen(frames)
-			r = frames[i + PREV_R]
-			g = frames[i + PREV_G]
-			b = frames[i + PREV_B]
-			a = frames[i + PREV_A]
-		else
-			-- Interpolate between the last frame and the current frame.
-			local frame = binarySearch(frames, time, ENTRIES)
-			r = frames[frame + PREV_R]
-			g = frames[frame + PREV_G]
-			b = frames[frame + PREV_B]
-			a = frames[frame + PREV_A]
-			local frameTime = frames[frame]
-			local percent = self:getCurvePercent(math.floor(frame / ENTRIES) - 1,
-					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
-
-			r = r + (frames[frame + R] - r) * percent
-			g = g + (frames[frame + G] - g) * percent
-			b = b + (frames[frame + B] - b) * percent
-			a = a + (frames[frame + A] - a) * percent
-		end
-		if alpha == 1 then
-			slot.color:set(r, g, b, a)
-		else
-			local color = slot.color
-			if blend == MixBlend.setup then color:setFrom(slot.data.color) end
-			color:add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha)
-		end
-	end
-
-	return self
-end
-
-Animation.TwoColorTimeline = {}
-Animation.TwoColorTimeline.ENTRIES = 8
-function Animation.TwoColorTimeline.new (frameCount)
-	local ENTRIES = Animation.TwoColorTimeline.ENTRIES
-	local PREV_TIME = -8
-	local PREV_R = -7
-	local PREV_G = -6
-	local PREV_B = -5
-	local PREV_A = -4
-	local PREV_R2 = -3
-	local PREV_G2 = -2
-	local PREV_B2 = -1
-	local R = 1
-	local G = 2
-	local B = 3
-	local A = 4
-	local R2 = 5
-	local G2 = 6
-	local B2 = 7
-
-	local self = Animation.CurveTimeline.new(frameCount)
-	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
-	self.slotIndex = -1
-	self.type = TimelineType.twoColor
-
-	function self:getPropertyId ()
-		return TimelineType.twoColor * SHL_24 + self.slotIndex
-	end
-
-	function self:setFrame (frameIndex, time, r, g, b, a, r2, g2, b2)
-		frameIndex = frameIndex * ENTRIES
-		self.frames[frameIndex] = time
-		self.frames[frameIndex + R] = r
-		self.frames[frameIndex + G] = g
-		self.frames[frameIndex + B] = b
-		self.frames[frameIndex + A] = a
-		self.frames[frameIndex + R2] = r2
-		self.frames[frameIndex + G2] = g2
-		self.frames[frameIndex + B2] = b2
-	end
-
-	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
-		local frames = self.frames
-		local slot = skeleton.slots[self.slotIndex]
-		if not slot.bone.active then return end
-
-		if time < frames[0] then
-			if blend == MixBlend.setup then
-				slot.color:setFrom(slot.data.color)
-				slot.darkColor:setFrom(slot.data.darkColor)
-			elseif blend == MixBlend.first then
-				local light = slot.color
-				local dark = slot.darkColor
-				local setupLight = slot.data.color
-				local setupDark = slot.data.darkColor
-				light:add((setupLight.r - light.r) * alpha, (setupLight.g - light.g) * alpha, (setupLight.b - light.b) * alpha,
-					(setupLight.a - light.a) * alpha)
-				dark:add((setupDark.r - dark.r) * alpha, (setupDark.g - dark.g) * alpha, (setupDark.b - dark.b) * alpha, 0)
-			end
-			return
-		end
-
-		local r, g, b, a, r2, g2, b2
-		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
-			local i = zlen(frames)
-			r = frames[i + PREV_R]
-			g = frames[i + PREV_G]
-			b = frames[i + PREV_B]
-			a = frames[i + PREV_A]
-			r2 = frames[i + PREV_R2]
-			g2 = frames[i + PREV_G2]
-			b2 = frames[i + PREV_B2]
-		else
-			-- Interpolate between the last frame and the current frame.
-			local frame = binarySearch(frames, time, ENTRIES)
-			r = frames[frame + PREV_R]
-			g = frames[frame + PREV_G]
-			b = frames[frame + PREV_B]
-			a = frames[frame + PREV_A]
-			r2 = frames[frame + PREV_R2]
-			g2 = frames[frame + PREV_G2]
-			b2 = frames[frame + PREV_B2]
-			local frameTime = frames[frame]
-			local percent = self:getCurvePercent(math.floor(frame / ENTRIES) - 1,
-					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
-
-			r = r + (frames[frame + R] - r) * percent
-			g = g + (frames[frame + G] - g) * percent
-			b = b + (frames[frame + B] - b) * percent
-			a = a + (frames[frame + A] - a) * percent
-			r2 = r2 + (frames[frame + R2] - r2) * percent
-			g2 = g2 + (frames[frame + G2] - g2) * percent
-			b2 = b2 + (frames[frame + B2] - b2) * percent
-		end
-		if alpha == 1 then
-			slot.color:set(r, g, b, a)
-			slot.darkColor:set(r2, g2, b2, 1)
-		else
-			local light = slot.color
-			local dark = slot.darkColor
-			if blend == MixBlend.setup then
-				light:setFrom(slot.data.color)
-				dark:setFrom(slot.data.darkColor)
-			end
-			light:add((r - light.r) * alpha, (g - light.g) * alpha, (b - light.b) * alpha, (a - light.a) * alpha)
-			dark:add((r2 - dark.r) * alpha, (g2 - dark.g) * alpha, (b2 - dark.b) * alpha, 0)
-		end
-	end
-
-	return self
-end
-
-Animation.AttachmentTimeline = {}
-function Animation.AttachmentTimeline.new (frameCount)
-	local self = {
-		frames = utils.newNumberArrayZero(frameCount), -- time, ...
-		attachmentNames = {},
-		slotIndex = -1,
-		type = TimelineType.attachment
-	}
-
-	function self:getFrameCount ()
-		return zlen(self.frames)
-	end
-
-	function self:setFrame (frameIndex, time, attachmentName)
-		self.frames[frameIndex] = time
-		self.attachmentNames[frameIndex] = attachmentName
-	end
-
-	function self:getPropertyId ()
-		return TimelineType.attachment * SHL_24 + self.slotIndex
-	end
-
-	function self:setAttachment(skeleton, slot, attachmentName)
-		attachmentName = slot.data.attachmentName
-		if not attachmentName then
-			slot:setAttachment(nil)
-		else
-			slot:setAttachment(skeleton:getAttachmentByIndex(self.slotIndex, attachmentName))
-		end
-	end
-
-	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
-		local slot = skeleton.slots[self.slotIndex]
-		if not slot.bone.active then return end
-		local attachmentName
-		if direction == MixDirection.out then
-			if blend == MixBlend.setup then
-				self:setAttachment(skeleton, slot, slot.data.attachmentName)
-			end
-			return;
-		end
-
-		local frames = self.frames
-		if time < frames[0] then
-			if blend == MixBlend.setup or blend == MixBlend.first then
-				self:setAttachment(skeleton, slot, slot.data.attachmentName)
-			end
-			return
-		end
-
-		local frameIndex = 0
-		if time >= frames[zlen(frames) - 1] then
-			frameIndex = zlen(frames) - 1
-		else
-			frameIndex = binarySearch(frames, time, 1) - 1
-		end
-
-		attachmentName = self.attachmentNames[frameIndex]
-		if not attachmentName then
-			slot:setAttachment(nil)
-		else
-			slot:setAttachment(skeleton:getAttachmentByIndex(self.slotIndex, attachmentName))
-		end
-	end
-
-	return self
-end
-
-Animation.DeformTimeline = {}
-function Animation.DeformTimeline.new (frameCount)
-	local self = Animation.CurveTimeline.new(frameCount)
-	self.frames = utils.newNumberArrayZero(frameCount)
-	self.frameVertices = utils.newNumberArrayZero(frameCount)
-	self.slotIndex = -1
-	self.attachment = nil
-	self.type = TimelineType.deform
-
-	function self:getPropertyId ()
-		return TimelineType.deform * SHL_27 + self.attachment.id + self.slotIndex
-	end
-
-	function self:setFrame (frameIndex, time, vertices)
-		self.frames[frameIndex] = time
-		self.frameVertices[frameIndex] = vertices
-	end
-
-	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
-		local slot = skeleton.slots[self.slotIndex]
-		if not slot.bone.active then return end
-
-		local slotAttachment = slot.attachment
-		if not slotAttachment then return end
-		if not (slotAttachment.type == AttachmentType.mesh or slotAttachment.type == AttachmentType.linkedmesh or slotAttachment.type == AttachmentType.path or slotAttachment.type == AttachmentType.boundingbox) then return end
-		if slotAttachment.deformAttachment ~= self.attachment then return end
-
-		local frames = self.frames
-		local deformArray = slot.deform
-		if #(deformArray) == 0 then blend = MixBlend.setup end
-
-		local frameVertices = self.frameVertices
-		local vertexCount = #(frameVertices[0])
-
-		if time < frames[0] then
-			local vertexAttachment = slotAttachment;
-			if blend == MixBlend.setup then
-				slot.deform = {}
-				return;
-			elseif blend == MixBlend.first then
-				if (alpha == 1) then
-					slot.deform = {}
-					return;
-				end
-
-				local deform = utils.setArraySize(deformArray, vertexCount)
-				if (vertexAttachment.bones == nil) then
-					local setupVertices = vertexAttachment.vertices
-					local i = 1
-					while i <= vertexCount do
-						deform[i] = deform[i] + (setupVertices[i] - deform[i]) * alpha
-						i = i + 1
-					end
-				else
-					alpha = 1 - alpha
-					local i = 1
-					while i <= vertexCount do
-						deform[i] = deform[i] * alpha
-						i = i + 1
-					end
-				end
-			end
-			return
-		end
-
-		local deform = utils.setArraySize(deformArray, vertexCount)
-		if time >= frames[zlen(frames) - 1] then -- Time is after last frame.
-			local lastVertices = frameVertices[zlen(frames) - 1]
-			if alpha == 1 then
-				if blend == MixBlend.add then
-					local vertexAttachment = slotAttachment
-					if vertexAttachment.bones == nil then
-						-- Unweighted vertex positions, with alpha.
-						local setupVertices = vertexAttachment.vertices
-						local i = 1
-						while i <= vertexCount do
-							deform[i] = deform[i] + lastVertices[i] - setupVertices[i]
-							i = i + 1
-						end
-					else
-						-- Weighted deform offsets, with alpha.
-						local i = 1
-						while i <= vertexCount do
-							deform[i] = deform[i] + lastVertices[i]
-							i = i + 1
-						end
-					end
-				else
-					local i = 1
-					while i <= vertexCount do
-						deform[i] = lastVertices[i]
-						i = i + 1
-					end
-				end
-			else
-				if blend == MixBlend.setup then
-					local vertexAttachment = slotAttachment
-					if vertexAttachment.bones == nil then
-						-- Unweighted vertex positions, with alpha.
-						local setupVertices = vertexAttachment.vertices
-						local i = 1
-						while i <= vertexCount do
-							local setup = setupVertices[i]
-							deform[i] = setup + (lastVertices[i] - setup) * alpha
-							i = i + 1
-						end
-					else
-						-- Weighted deform offsets, with alpha.
-						local i = 1
-						while i <= vertexCount do
-							deform[i] = lastVertices[i] * alpha
-							i = i + 1
-						end
-					end
-				elseif blend == MixBlend.first or blend == MixBlend.replace then
-					local i = 1
-					while i <= vertexCount do
-						deform[i] = deform[i] + (lastVertices[i] - deform[i]) * alpha
-						i = i + 1
-					end
-					local vertexAttachment = slotAttachment
-					if vertexAttachment.bones == nil then
-						local setupVertices = vertexAttachment.vertices
-						local i = 1
-						while i <= vertexCount do
-							deform[i] = deform[i] + (lastVertices[i] - setupVertices[i]) * alpha
-							i = i + 1
-						end
-					else
-						-- Weighted deform offsets, with alpha.
-						local i = 1
-						while i <= vertexCount do
-							deform[i] = deform[i] + lastVertices[i] * alpha
-							i = i + 1
-						end
-					end
-				elseif blend == MixBlend.add then
-					local vertexAttachment = slotAttachment
-					if vertexAttachment.bones == nil then
-						local setupVertices = vertexAttachment.vertices
-						local i = 1
-						while i <= vertexCount do
-							deform[i] = deform[i] + (lastVertices[i] - setupVertices[i]) * alpha
-							i = i + 1
-						end
-					else
-						-- Weighted deform offsets, with alpha.
-						local i = 1
-						while i <= vertexCount do
-							deform[i] = deform[i] + lastVertices[i] * alpha
-							i = i + 1
-						end
-					end
-				end
-			end
-			return;
-		end
-
-		-- Interpolate between the previous frame and the current frame.
-		local frame = binarySearch(frames, time, 1)
-		local prevVertices = frameVertices[frame - 1]
-		local nextVertices = frameVertices[frame]
-		local frameTime = frames[frame]
-		local percent = self:getCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime))
-
-		if alpha == 1 then
-			if blend == MixBlend.add then
-				local vertexAttachment = slotAttachment
-				if vertexAttachment.bones == nil then
-					-- Unweighted vertex positions, with alpha.
-					local setupVertices = vertexAttachment.vertices
-					local i = 1
-					while i <= vertexCount do
-						local prev = prevVertices[i]
-						deform[i] = deform[i] + prev + (nextVertices[i] - prev) * precent - setupVertices[i]
-						i = i + 1
-					end
-				else
-					-- Weighted deform offsets, with alpha.
-					local i = 1
-					while i <= vertexCount do
-						local prev = prevVertices[i]
-						deform[i] = deform[i] + prev + (nextVertices[i] - prev) * percent
-						i = i + 1
-					end
-				end
-			else
-				local i = 1
-				while i <= vertexCount do
-					local prev = prevVertices[i]
-					deform[i] = prev + (nextVertices[i] - prev) * percent
-					i = i + 1
-				end
-			end
-		else
-			if blend == MixBlend.setup then
-				local vertexAttachment = slotAttachment
-				if vertexAttachment.bones == nil then
-					-- Unweighted vertex positions, with alpha.
-					local setupVertices = vertexAttachment.vertices
-					local i = 1
-					while i <= vertexCount do
-						local prev = prevVertices[i]
-						local setup = setupVertices[i]
-						deform[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha
-						i = i + 1
-					end
-				else
-					-- Weighted deform offsets, with alpha.
-					local i = 1
-					while i <= vertexCount do
-						local prev = prevVertices[i]
-						deform[i] = (prev + (nextVertices[i] - prev) * percent) * alpha
-						i = i + 1
-					end
-				end
-			elseif blend == MixBlend.first or blend == MixBlend.replace then
-				local i = 1
-				while i <= vertexCount do
-					local prev = prevVertices[i]
-					deform[i] = deform[i] + (prev + (nextVertices[i] - prev) * percent - deform[i]) * alpha
-					i = i + 1
-				end
-			elseif blend == MixBlend.add then
-				local vertexAttachment = slotAttachment
-				if vertexAttachment.bones == nil then
-					local setupVertices = vertexAttachment.vertices
-					local i = 1
-					while i <= vertexCount do
-						local prev = prevVertices[i]
-						deform[i] = deform[i] + (prev + (nextVertices[i] - prev) * percent - setupVertices[i]) * alpha
-						i = i + 1
-					end
-				else
-					-- Weighted deform offsets, with alpha.
-					local i = 1
-					while i <= vertexCount do
-						local prev = prevVertices[i]
-						deform[i] = deform[i] + (prev + (nextVertices[i] - prev) * percent) * alpha
-						i = i + 1
-					end
-				end
-			end
-		end
-	end
-
-	return self
-end
-
-Animation.EventTimeline = {}
-function Animation.EventTimeline.new (frameCount)
-	local self = {
-		frames = utils.newNumberArrayZero(frameCount),
-		events = {},
-		type = TimelineType.event
-	}
-
-	function self:getPropertyId ()
-		return TimelineType.event * SHL_24
-	end
-
-	function self:getFrameCount ()
-		return zlen(self.frames)
-	end
-
-	function self:setFrame (frameIndex, event)
-		self.frames[frameIndex] = event.time
-		self.events[frameIndex] = event
-	end
-
-	-- Fires events for frames > lastTime and <= time.
-	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
-		if not firedEvents then return end
-
-		local frames = self.frames
-		local frameCount = zlen(frames)
-
-		if lastTime > time then -- Fire events after last time for looped animations.
-			self:apply(skeleton, lastTime, 999999, firedEvents, alpha, blend, direction)
-			lastTime = -1
-		elseif lastTime >= frames[frameCount - 1] then -- Last time is after last frame.
-			return
-		end
-		if time < frames[0] then return end -- Time is before first frame.
-
-		local frame
-		if lastTime < frames[0] then
-			frame = 0
-		else
-			frame = binarySearch1(frames, lastTime)
-			local frame = frames[frame]
-			while frame > 0 do -- Fire multiple events with the same frame.
-				if frames[frame - 1] ~= frame then break end
-				frame = frame - 1
-			end
-		end
-		local events = self.events
-		while frame < frameCount and time >= frames[frame] do
-			table.insert(firedEvents, events[frame])
-			frame = frame + 1
-		end
-	end
-
-	return self
-end
-
-Animation.DrawOrderTimeline = {}
-function Animation.DrawOrderTimeline.new (frameCount)
-	local self = {
-		frames = utils.newNumberArrayZero(frameCount),
-		drawOrders = {},
-		type = TimelineType.drawOrder
-	}
-
-	function self:getPropertyId ()
-		return TimelineType.drawOrder * SHL_24
-	end
-
-	function self:getFrameCount ()
-		return zlen(self.frames)
-	end
-
-	function self:setFrame (frameIndex, time, drawOrder)
-		self.frames[frameIndex] = time
-		self.drawOrders[frameIndex] = drawOrder
-	end
-
-	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
-		local drawOrder = skeleton.drawOrder
-		local slots = skeleton.slots
-		if direction == MixDirection.out then
-			if blend == MixBlend.setup then
-				for i,slot in ipairs(slots) do
-					drawOrder[i] = slots[i]
-				end
-			end
-			return;
-		end
-
-		local frames = self.frames
-		if time < frames[0] then
-			if blend == MixBlend.setup or blend == MixBlend.first then
-				for i,slot in ipairs(slots) do
-					drawOrder[i] = slots[i]
-				end
-			end
-			return
-		end
-
-		local frame
-		if time >= frames[zlen(frames) - 1] then -- Time is after last frame.
-			frame = zlen(frames) - 1
-		else
-			frame = binarySearch1(frames, time) - 1
-		end
-
-		local drawOrderToSetupIndex = self.drawOrders[frame]
-		if not drawOrderToSetupIndex then
-			for i,slot in ipairs(slots) do
-				drawOrder[i] = slots[i]
-			end
-		else
-			for i,setupIndex in ipairs(drawOrderToSetupIndex) do
-				drawOrder[i] = skeleton.slots[setupIndex]
-			end
-		end
-	end
-
-	return self
-end
-
-Animation.IkConstraintTimeline = {}
-Animation.IkConstraintTimeline.ENTRIES = 6
-function Animation.IkConstraintTimeline.new (frameCount)
-	local ENTRIES = Animation.IkConstraintTimeline.ENTRIES
-	local PREV_TIME = -6
-	local PREV_MIX = -5
-	local PREV_SOFTNESS = -4
-	local PREV_BEND_DIRECTION = -3
-	local PREV_COMPRESS = -2
-	local PREV_STRETCH = -1
-	local MIX = 1
-	local SOFTNESS = 2
-	local BEND_DIRECTION = 3
-	local COMPRESS = 4
-	local STRETCH = 5
-
-	local self = Animation.CurveTimeline.new(frameCount)
-	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES) -- time, mix, softness, bendDirection, compress, stretch, ...
-	self.ikConstraintIndex = -1
-	self.type = TimelineType.ikConstraint
-
-	function self:getPropertyId ()
-		return TimelineType.ikConstraint * SHL_24 + self.ikConstraintIndex
-	end
-
-	function self:setFrame (frameIndex, time, mix, softness, bendDirection, compress, stretch)
-		frameIndex = frameIndex * ENTRIES
-		self.frames[frameIndex] = time
-		self.frames[frameIndex + MIX] = mix
-		self.frames[frameIndex + SOFTNESS] = softness
-		self.frames[frameIndex + BEND_DIRECTION] = bendDirection
-		if (compress) then
-			self.frames[frameIndex + COMPRESS] = 1
-		else
-			self.frames[frameIndex + COMPRESS] = 0
-		end
-		if (stretch) then
-			self.frames[frameIndex + STRETCH] = 1
-		else
-			self.frames[frameIndex + STRETCH] = 0
-		end
-	end
-
-	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
-		local frames = self.frames
-
-		local constraint = skeleton.ikConstraints[self.ikConstraintIndex]
-		if not constraint.active then return end
-		if time < frames[0] then
-			if blend == MixBlend.setup then
-				constraint.mix = constraint.data.mix
-				constraint.softness = constraint.data.softness
-				constraint.bendDirection = constraint.data.bendDirection
-				constraint.compress = constraint.data.compress
-				constraint.stretch = constraint.data.stretch
-			elseif blend == MixBlend.first then
-				constraint.mix = constraint.mix + (constraint.data.mix - constraint.mix) * alpha
-				constraint.softness = constraint.softness + (constraint.data.softness - constraint.softness) * alpha
-				constraint.bendDirection = constraint.data.bendDirection
-				constraint.compress = constraint.data.compress
-				constraint.stretch = constraint.data.stretch
-			end
-			return
-		end
-
-		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
-			if blend == MixBlend.setup then
-				constraint.mix = constraint.data.mix + (frames[zlen(frames) + PREV_MIX] - constraint.data.mix) * alpha
-				constraint.softness = constraint.data.softness + (frames[zlen(frames) + PREV_SOFTNESS] - constraint.data.softness) * alpha
-				if direction == MixDirection.out then
-					constraint.bendDirection = constraint.data.bendDirection
-					constraint.compress = constraint.data.compress
-					constraint.stretch = constraint.data.stretch
-				else
-					constraint.bendDirection = math_floor(frames[zlen(frames) + PREV_BEND_DIRECTION]);
-					if (math_floor(frames[zlen(frames) + PREV_COMPRESS]) == 1) then constraint.compress = true else constraint.compress = false end
-					if (math_floor(frames[zlen(frames) + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end
-				end
-			else
-				constraint.mix = constraint.mix + (frames[zlen(frames) + PREV_MIX] - constraint.mix) * alpha
-				constraint.softness = constraint.softness + (frames[zlen(frames) + PREV_SOFTNESS] - constraint.softness) * alpha
-				if direction == MixDirection._in then
-					constraint.bendDirection = math_floor(frames[zlen(frames) + PREV_BEND_DIRECTION])
-					if (math_floor(frames[zlen(frames) + PREV_COMPRESS]) == 1) then constraint.compress = true else constraint.compress = false end
-					if (math_floor(frames[zlen(frames) + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end
-				end
-			end
-			return
-		end
-
-		-- Interpolate between the previous frame and the current frame.
-		local frame = binarySearch(frames, time, ENTRIES)
-		local mix = frames[frame + PREV_MIX]
-		local softness = frames[frame + PREV_SOFTNESS]
-		local frameTime = frames[frame]
-		local percent = self:getCurvePercent(math.floor(frame / ENTRIES) - 1,
-				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
-
-		if blend == MixBlend.setup then
-			constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha
-			constraint.softness = constraint.data.softness + (softness + (frames[frame + SOFTNESS] - softness) * percent - constraint.data.softness) * alpha
-			if direction == MixDirection.out then
-				constraint.bendDirection = constraint.data.bendDirection
-				constraint.compress = constraint.data.compress
-				constraint.stretch = constraint.data.stretch
-			else
-				constraint.bendDirection = math_floor(frames[frame + PREV_BEND_DIRECTION])
-				if (math_floor(frames[frame + PREV_COMPRESS]) == 1) then constraint.compress = true else constraint.compress = false end
-				if (math_floor(frames[frame + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end
-			end
-		else
-			constraint.mix = constraint.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha
-			constraint.softness = constraint.softness + (softness + (frames[frame + SOFTNESS] - softness) * percent - constraint.softness) * alpha
-			if direction == MixDirection._in then
-				constraint.bendDirection = math_floor(frames[frame + PREV_BEND_DIRECTION])
-				if (math_floor(frames[frame + PREV_COMPRESS]) == 1) then constraint.compress = true else constraint.compress = false end
-				if (math_floor(frames[frame + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end
-			end
-		end
-	end
-
-	return self
-end
-
-Animation.TransformConstraintTimeline = {}
-Animation.TransformConstraintTimeline.ENTRIES = 5
-function Animation.TransformConstraintTimeline.new (frameCount)
-	local ENTRIES = Animation.TransformConstraintTimeline.ENTRIES
-	local PREV_TIME = -5
-	local PREV_ROTATE = -4
-	local PREV_TRANSLATE = -3
-	local PREV_SCALE = -2
-	local PREV_SHEAR = -1
-	local ROTATE = 1
-	local TRANSLATE = 2
-	local SCALE = 3
-	local SHEAR = 4
-
-	local self = Animation.CurveTimeline.new(frameCount)
-	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
-	self.transformConstraintIndex = -1
-	self.type = TimelineType.transformConstraint
-
-	function self:getPropertyId ()
-		return TimelineType.transformConstraint * SHL_24 + self.transformConstraintIndex
-	end
-
-	function self:setFrame (frameIndex, time, rotateMix, translateMix, scaleMix, shearMix)
-		frameIndex = frameIndex * ENTRIES
-		self.frames[frameIndex] = time
-		self.frames[frameIndex + ROTATE] = rotateMix
-		self.frames[frameIndex + TRANSLATE] = translateMix
-		self.frames[frameIndex + SCALE] = scaleMix
-		self.frames[frameIndex + SHEAR] = shearMix
-	end
-
-	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
-		local frames = self.frames
-
-		local constraint = skeleton.transformConstraints[self.transformConstraintIndex]
-		if not constraint.active then return end
-
-		if time < frames[0] then
-			local data = constraint.data
-			if blend == MixBlend.setup then
-				constraint.rotateMix = data.rotateMix
-				constraint.translateMix = data.translateMix
-				constraint.scaleMix = data.scaleMix
-				constraint.shearMix = data.shearMix
-			elseif blend == MixBlend.first then
-				constraint.rotateMix = constraint.rotateMix + (data.rotateMix - constraint.rotateMix) * alpha
-				constraint.translateMix = constraint.translateMix + (data.translateMix - constraint.translateMix) * alpha
-				constraint.scaleMix = constraint.scaleMix + (data.scaleMix - constraint.scaleMix) * alpha
-				constraint.shearMix = constraint.shearMix + (data.shearMix - constraint.shearMix) * alpha
-			end
-			return
-		end
-
-		local rotate = 0
-		local translate = 0
-		local scale = 0
-		local shear = 0
-		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
-			local i = zlen(frames)
-			rotate = frames[i + PREV_ROTATE]
-			translate = frames[i + PREV_TRANSLATE]
-			scale = frames[i + PREV_SCALE]
-			shear = frames[i + PREV_SHEAR]
-		else
-			-- Interpolate between the previous frame and the current frame.
-			local frame = binarySearch(frames, time, ENTRIES)
-			rotate = frames[frame + PREV_ROTATE]
-			translate = frames[frame + PREV_TRANSLATE]
-			scale = frames[frame + PREV_SCALE]
-			shear = frames[frame + PREV_SHEAR]
-			local frameTime = frames[frame]
-			local percent = self:getCurvePercent(math_floor(frame / ENTRIES) - 1,
-				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
-			rotate = rotate + (frames[frame + ROTATE] - rotate) * percent
-			translate = translate + (frames[frame + TRANSLATE] - translate) * percent
-			scale = scale + (frames[frame + SCALE] - scale) * percent
-			shear = shear + (frames[frame + SHEAR] - shear) * percent
-		end
-		if blend == MixBlend.setup then
-			local data = constraint.data
-			constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha
-			constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha
-			constraint.scaleMix = data.scaleMix + (scale - data.scaleMix) * alpha
-			constraint.shearMix = data.shearMix + (shear - data.shearMix) * alpha
-		else
-			constraint.rotateMix = constraint.rotateMix + (rotate - constraint.rotateMix) * alpha
-			constraint.translateMix = constraint.translateMix + (translate - constraint.translateMix) * alpha
-			constraint.scaleMix = constraint.scaleMix + (scale - constraint.scaleMix) * alpha
-			constraint.shearMix = constraint.shearMix + (shear - constraint.shearMix) * alpha
-		end
-	end
-
-	return self
-end
-
-Animation.PathConstraintPositionTimeline = {}
-Animation.PathConstraintPositionTimeline.ENTRIES = 2
-function Animation.PathConstraintPositionTimeline.new (frameCount)
-	local ENTRIES = Animation.PathConstraintPositionTimeline.ENTRIES
-	local PREV_TIME = -2
-	local PREV_VALUE = -1
-	local VALUE = 1
-
-	local self = Animation.CurveTimeline.new(frameCount)
-	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
-	self.pathConstraintIndex = -1
-	self.type = TimelineType.pathConstraintPosition
-
-	function self:getPropertyId ()
-		return TimelineType.pathConstraintPosition * SHL_24 + self.pathConstraintIndex
-	end
-
-	function self:setFrame (frameIndex, time, value)
-		frameIndex = frameIndex * ENTRIES
-		self.frames[frameIndex] = time
-		self.frames[frameIndex + VALUE] = value
-	end
-
-	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
-		local frames = self.frames
-
-		local constraint = skeleton.pathConstraints[self.pathConstraintIndex]
-		if not constraint.active then return end
-
-		if (time < frames[0]) then
-			if blend == MixBlend.setup then
-				constraint.position = constraint.data.position
-			elseif blend == MixBlend.first then
-				constraint.position = constraint.position + (constraint.data.position - constraint.position) * alpha
-			end
-			return
-		end
-
-		local position = 0
-		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
-			position = frames[zlen(frames) + PREV_VALUE]
-		else
-			-- Interpolate between the previous frame and the current frame.
-			local frame = binarySearch(frames, time, ENTRIES)
-			position = frames[frame + PREV_VALUE]
-			local frameTime = frames[frame]
-			local percent = self:getCurvePercent(math_floor(frame / ENTRIES) - 1,
-				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
-
-			position = position + (frames[frame + VALUE] - position) * percent
-		end
-		if blend == MixBlend.setup then
-			constraint.position = constraint.data.position + (position - constraint.data.position) * alpha
-		else
-			constraint.position = constraint.position + (position - constraint.position) * alpha
-		end
-	end
-
-	return self
-end
-
-Animation.PathConstraintSpacingTimeline = {}
-Animation.PathConstraintSpacingTimeline.ENTRIES = 2
-function Animation.PathConstraintSpacingTimeline.new (frameCount)
-	local ENTRIES = Animation.PathConstraintSpacingTimeline.ENTRIES
-	local PREV_TIME = -2
-	local PREV_VALUE = -1
-	local VALUE = 1
-
-	local self = Animation.CurveTimeline.new(frameCount)
-	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
-	self.pathConstraintIndex = -1
-	self.type = TimelineType.pathConstraintSpacing
-
-	function self:getPropertyId ()
-		return TimelineType.pathConstraintSpacing * SHL_24 + self.pathConstraintIndex
-	end
-
-	function self:setFrame (frameIndex, time, value)
-		frameIndex = frameIndex * ENTRIES
-		self.frames[frameIndex] = time
-		self.frames[frameIndex + VALUE] = value
-	end
-
-	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
-		local frames = self.frames
-
-		local constraint = skeleton.pathConstraints[self.pathConstraintIndex]
-		if not constraint.active then return end
-
-		if (time < frames[0]) then
-			if blend == MixBlend.setup then
-				constraint.spacing = constraint.data.spacing
-			elseif blend == MixBlend.first then
-				constraint.spacing = constraint.spacing + (constraint.data.spacing - constraint.spacing) * alpha
-			end
-			return
-		end
-
-		local spacing = 0
-		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
-			spacing = frames[zlen(frames) + PREV_VALUE]
-		else
-			-- Interpolate between the previous frame and the current frame.
-			local frame = binarySearch(frames, time, ENTRIES)
-			spacing = frames[frame + PREV_VALUE]
-			local frameTime = frames[frame]
-			local percent = self:getCurvePercent(math_floor(frame / ENTRIES) - 1,
-				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
-
-			spacing = spacing + (frames[frame + VALUE] - spacing) * percent
-		end
-
-		if blend == MixBlend.setup then
-			constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha
-		else
-			constraint.spacing = constraint.spacing + (spacing - constraint.spacing) * alpha
-		end
-	end
-
-	return self
-end
-
-Animation.PathConstraintMixTimeline = {}
-Animation.PathConstraintMixTimeline.ENTRIES = 3
-function Animation.PathConstraintMixTimeline.new (frameCount)
-	local ENTRIES = Animation.PathConstraintMixTimeline.ENTRIES
-	local PREV_TIME = -3
-	local PREV_ROTATE = -2
-	local PREV_TRANSLATE = -1
-	local ROTATE = 1
-	local TRANSLATE = 2
-
-	local self = Animation.CurveTimeline.new(frameCount)
-	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
-	self.pathConstraintIndex = -1
-	self.type = TimelineType.pathConstraintMix
-
-	function self:getPropertyId ()
-		return TimelineType.pathConstraintMix * SHL_24 + self.pathConstraintIndex
-	end
-
-	function self:setFrame (frameIndex, time, rotateMix, translateMix)
-		frameIndex = frameIndex * ENTRIES
-		self.frames[frameIndex] = time
-		self.frames[frameIndex + ROTATE] = rotateMix
-		self.frames[frameIndex + TRANSLATE] = translateMix
-	end
-
-	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
-		local frames = self.frames
-
-		local constraint = skeleton.pathConstraints[self.pathConstraintIndex]
-		if not constraint.active then return end
-
-		if (time < frames[0]) then
-			if blend == MixBlend.setup then
-				constraint.rotateMix = constraint.data.rotateMix
-				constraint.translateMix = constraint.data.translateMix
-			elseif blend == MixBlend.first then
-				constraint.rotateMix = constraint.rotateMix + (constraint.data.rotateMix - constraint.rotateMix) * alpha
-				constraint.translateMix = constraint.translateMix + (constraint.data.translateMix - constraint.translateMix) * alpha
-			end
-			return
-		end
-
-		local rotate = 0
-		local translate = 0
-		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
-			rotate = frames[zlen(frames) + PREV_ROTATE]
-			translate = frames[zlen(frames) + PREV_TRANSLATE]
-		else
-			-- Interpolate between the previous frame and the current frame.
-			local frame = binarySearch(frames, time, ENTRIES)
-			rotate = frames[frame + PREV_ROTATE]
-			translate = frames[frame + PREV_TRANSLATE]
-			local frameTime = frames[frame]
-			local percent = self:getCurvePercent(math_floor(frame / ENTRIES) - 1,
-				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
-
-			rotate = rotate + (frames[frame + ROTATE] - rotate) * percent
-			translate = translate + (frames[frame + TRANSLATE] - translate) * percent
-		end
-
-		if blend == MixBlend.setup then
-			constraint.rotateMix = constraint.data.rotateMix + (rotate - constraint.data.rotateMix) * alpha
-			constraint.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha
-		else
-			constraint.rotateMix = constraint.rotateMix + (rotate - constraint.rotateMix) * alpha
-			constraint.translateMix = constraint.translateMix + (translate - constraint.translateMix) * alpha
-		end
-	end
-
-	return self
-end
-
-return Animation
+-------------------------------------------------------------------------------
+-- 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.
+-------------------------------------------------------------------------------
+
+-- FIXME
+-- All the indexing in this file is zero based. We use zlen()
+-- instead of the # operator. Initialization of number arrays
+-- is performed via utils.newNumberArrayZero. This needs
+-- to be rewritten using one-based indexing for better performance
+
+local utils = require "spine-lua.utils"
+local AttachmentType = require "spine-lua.attachments.AttachmentType"
+local math_floor = math.floor
+local math_abs = math.abs
+local math_signum = utils.signum
+
+local function zlen(array)
+	return #array + 1
+end
+
+local Animation = {}
+function Animation.new (name, timelines, duration)
+	if not timelines then error("timelines cannot be nil", 2) end
+
+	local self = {
+		name = name,
+		timelines = timelines,
+		timelineIds = {},
+		duration = duration
+	}
+	
+	for i,timeline in ipairs(self.timelines) do
+		self.timelineIds[timeline:getPropertyId()] = true
+	end
+
+	function self:hasTimeline(id)
+		return self.timelineIds[id] == true
+	end
+
+	function self:apply (skeleton, lastTime, time, loop, events, alpha, blend, direction)
+		if not skeleton then error("skeleton cannot be nil.", 2) end
+
+		if loop and duration > 0 then
+			time = time % self.duration
+			if lastTime > 0 then lastTime = lastTime % self.duration end
+		end
+
+		for i,timeline in ipairs(self.timelines) do
+			timeline:apply(skeleton, lastTime, time, events, alpha, blend, direction)
+		end
+	end
+
+	return self
+end
+
+local function binarySearch (values, target, step)
+	local low = 0
+	local high = math.floor(zlen(values) / step - 2)
+	if high == 0 then return step end
+	local current = math.floor(high / 2)
+	while true do
+		if values[(current + 1) * step] <= target then
+			low = current + 1
+		else
+			high = current
+		end
+		if low == high then return (low + 1) * step end
+		current = math.floor((low + high) / 2)
+	end
+end
+Animation.binarySearch = binarySearch
+
+local function binarySearch1 (values, target)
+	local low = 0
+	local high = math.floor(zlen(values)	- 2)
+	if high == 0 then return 1 end
+	local current = math.floor(high / 2)
+	while true do
+		if values[current + 1] <= target then
+			low = current + 1
+		else
+			high = current
+		end
+		if low == high then return low + 1 end
+		current = math.floor((low + high) / 2)
+	end
+end
+
+local function linearSearch (values, target, step)
+	local i = 0
+	local last = zlen(values) - step
+	while i <= last do
+		if (values[i] > target) then return i end
+		i = i + step
+	end
+	return -1
+end
+
+Animation.MixBlend = {
+	setup = 0,
+	first = 1,
+	replace = 2,
+	add = 3
+}
+local MixBlend = Animation.MixBlend
+
+Animation.MixDirection = {
+	_in = 0, out = 1
+}
+local MixDirection = Animation.MixDirection
+
+Animation.TimelineType = {
+	rotate = 0, translate = 1, scale = 2, shear = 3,
+	attachment = 4, color = 5, deform = 6,
+	event = 7, drawOrder = 8,
+	ikConstraint = 9, transformConstraint = 10,
+	pathConstraintPosition = 11, pathConstraintSpacing = 12, pathConstraintMix = 13,
+	twoColor = 14
+}
+local TimelineType = Animation.TimelineType
+local SHL_24 = 16777216
+local SHL_27 = 134217728
+
+Animation.CurveTimeline = {}
+function Animation.CurveTimeline.new (frameCount)
+	local LINEAR = 0
+	local STEPPED = 1
+	local BEZIER = 2
+	local BEZIER_SIZE = 10 * 2 - 1
+
+	local self = {
+		curves = utils.newNumberArrayZero((frameCount - 1) * BEZIER_SIZE) -- type, x, y, ...
+	}
+
+	function self:getFrameCount ()
+		return math.floor(zlen(self.curves) / BEZIER_SIZE) + 1
+	end
+
+	function self:setStepped (frameIndex)
+		self.curves[frameIndex * BEZIER_SIZE] = STEPPED
+	end
+
+	function self:getCurveType (frameIndex)
+		local index = frameIndex * BEZIER_SIZE
+		if index == zlen(self.curves) then return LINEAR end
+		local type = self.curves[index]
+		if type == LINEAR then return LINEAR end
+		if type == STEPPED then return STEPPED end
+		return BEZIER
+	end
+
+	function self:setCurve (frameIndex, cx1, cy1, cx2, cy2)
+			local tmpx = (-cx1 * 2 + cx2) * 0.03
+			local tmpy = (-cy1 * 2 + cy2) * 0.03
+			local dddfx = ((cx1 - cx2) * 3 + 1) * 0.006
+			local dddfy = ((cy1 - cy2) * 3 + 1) * 0.006
+			local ddfx = tmpx * 2 + dddfx
+			local ddfy = tmpy * 2 + dddfy
+			local dfx = cx1 * 0.3 + tmpx + dddfx * 0.16666667
+			local dfy = cy1 * 0.3 + tmpy + dddfy * 0.16666667
+
+			local i = frameIndex * BEZIER_SIZE
+			local curves = self.curves
+			curves[i] = BEZIER
+			i = i + 1
+
+			local x = dfx
+			local y = dfy
+			local n = i + BEZIER_SIZE - 1
+			while i < n do
+				curves[i] = x
+				curves[i + 1] = y
+				dfx = dfx + ddfx
+				dfy = dfy + ddfy
+				ddfx = ddfx + dddfx
+				ddfy = ddfy + dddfy
+				x = x + dfx
+				y = y + dfy
+				i = i + 2
+			end
+	end
+
+	function self:getCurvePercent (frameIndex, percent)
+		percent = utils.clamp(percent, 0, 1)
+		local curves = self.curves
+		local i = frameIndex * BEZIER_SIZE
+		local type = curves[i]
+		if type == LINEAR then return percent end
+		if type == STEPPED then return 0 end
+		i = i + 1
+		local x
+		local n = i + BEZIER_SIZE - 1
+		local start = i
+		while i < n do
+			x = curves[i]
+			if x >= percent then
+				local prevX, prevY
+				if i == start then
+					prevX = 0
+					prevY = 0
+				else
+					prevX = curves[i - 2]
+					prevY = curves[i - 1]
+				end
+				return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX)
+			end
+			i = i + 2
+		end
+		local y = curves[i - 1]
+		return y + (1 - y) * (percent - x) / (1 - x) -- Last point is 1,1.
+	end
+
+	return self
+end
+
+Animation.RotateTimeline = {}
+Animation.RotateTimeline.ENTRIES = 2
+Animation.RotateTimeline.PREV_TIME = -2
+Animation.RotateTimeline.PREV_ROTATION = -1
+Animation.RotateTimeline.ROTATION = 1
+function Animation.RotateTimeline.new (frameCount)
+	local ENTRIES = Animation.RotateTimeline.ENTRIES
+	local PREV_TIME = Animation.RotateTimeline.PREV_TIME
+	local PREV_ROTATION = Animation.RotateTimeline.PREV_ROTATION
+	local ROTATION = Animation.RotateTimeline.ROTATION
+
+	local self = Animation.CurveTimeline.new(frameCount)
+	self.boneIndex = -1
+	self.frames = utils.newNumberArrayZero(frameCount * 2)
+	self.type = TimelineType.rotate
+
+	function self:getPropertyId ()
+		return TimelineType.rotate * SHL_24 + self.boneIndex
+	end
+
+	function self:setFrame (frameIndex, time, degrees)
+		frameIndex = frameIndex * 2
+		self.frames[frameIndex] = time
+		self.frames[frameIndex + ROTATION] = degrees
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
+		local frames = self.frames
+
+		local bone = skeleton.bones[self.boneIndex]
+		if not bone.active then return end
+		if time < frames[0] then
+			if blend == MixBlend.setup then
+				bone.rotation = bone.data.rotation
+			elseif blend == MixBlend.first then
+				local r = bone.data.rotation - bone.rotation
+				bone.rotation = bone.rotation + (r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360) * alpha
+			end
+			return
+		end
+
+		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
+			local r = frames[zlen(frames) + PREV_ROTATION]
+			if blend == MixBlend.setup then
+				bone.rotation = bone.data.rotation + r * alpha
+			elseif blend == MixBlend.first or blend == MixBlend.replace then
+				r = r + bone.data.rotation - bone.rotation
+				r = r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360 -- Wrap within -180 and 180.
+				bone.rotation = bone.rotation + r * alpha
+			elseif blend == MixBlend.add then
+				bone.rotation = bone.rotation + r * alpha
+			end
+			return
		end
+
+		-- Interpolate between the last frame and the current frame.
+		local frame = binarySearch(frames, time, ENTRIES)
+		local prevRotation = frames[frame + PREV_ROTATION]
+		local frameTime = frames[frame]
+		local percent = self:getCurvePercent((math.floor(frame / 2)) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
+
+		local r = frames[frame + ROTATION] - prevRotation
+		r = prevRotation + (r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360) * percent
+		if blend == MixBlend.setup then
+			bone.rotation = bone.data.rotation + (r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360) * alpha
+		elseif blend == MixBlend.first or blend == MixBlend.replace then
+			r = r + bone.data.rotation - bone.rotation
+			bone.rotation = bone.rotation + (r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360) * alpha
+		elseif blend == MixBlend.add then
+			bone.rotation = bone.rotation + (r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360) * alpha
+		end
+	end
+
+	return self
+end
+
+Animation.TranslateTimeline = {}
+Animation.TranslateTimeline.ENTRIES = 3
+function Animation.TranslateTimeline.new (frameCount)
+	local ENTRIES = Animation.TranslateTimeline.ENTRIES
+	local PREV_TIME = -3
+	local PREV_X = -2
+	local PREV_Y = -1
+	local X = 1
+	local Y = 2
+
+	local self = Animation.CurveTimeline.new(frameCount)
+	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
+	self.boneIndex = -1
+	self.type = TimelineType.translate
+
+	function self:getPropertyId ()
+		return TimelineType.translate * SHL_24 + self.boneIndex
+	end
+
+	function self:setFrame (frameIndex, time, x, y)
+		frameIndex = frameIndex * ENTRIES
+		self.frames[frameIndex] = time
+		self.frames[frameIndex + X] = x
+		self.frames[frameIndex + Y] = y
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
+		local frames = self.frames
+
+		local bone = skeleton.bones[self.boneIndex]
+		if not bone.active then return end
+
+		if time < frames[0] then
+			if blend == MixBlend.setup then
+				bone.x = bone.data.x
+				bone.y = bone.data.y
+			elseif blend == MixBlend.first then
+				bone.x = bone.x + (bone.data.x - bone.x) * alpha
+				bone.y = bone.y + (bone.data.y - bone.y) * alpha
+			end
+			return
+		end
+
+		local x = 0
+		local y = 0
+		if time >= frames[zlen(frames) - ENTRIES] then -- // Time is after last frame.
+			x = frames[zlen(frames) + PREV_X]
+			y = frames[zlen(frames) + PREV_Y]
+		else
+			-- Interpolate between the previous frame and the current frame.
+			local frame = binarySearch(frames, time, ENTRIES)
+			x = frames[frame + PREV_X]
+			y = frames[frame + PREV_Y]
+			local frameTime = frames[frame]
+			local percent = self:getCurvePercent(math_floor(frame / ENTRIES) - 1,
+				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
+
+			x = x + (frames[frame + X] - x) * percent
+			y = y + (frames[frame + Y] - y) * percent
+		end
+		if blend == MixBlend.setup then
+			bone.x = bone.data.x + x * alpha
+			bone.y = bone.data.y + y * alpha
+		elseif blend == MixBlend.first or blend == MixBlend.replace then
+			bone.x = bone.x + (bone.data.x + x - bone.x) * alpha
+			bone.y = bone.y + (bone.data.y + y - bone.y) * alpha
+		elseif blend == MixBlend.add then
+			bone.x = bone.x + x * alpha
+			bone.y = bone.y + y * alpha
+		end
+	end
+
+	return self
+end
+
+Animation.ScaleTimeline = {}
+Animation.ScaleTimeline.ENTRIES = Animation.TranslateTimeline.ENTRIES
+function Animation.ScaleTimeline.new (frameCount)
+	local ENTRIES = Animation.ScaleTimeline.ENTRIES
+	local PREV_TIME = -3
+	local PREV_X = -2
+	local PREV_Y = -1
+	local X = 1
+	local Y = 2
+
+	local self = Animation.TranslateTimeline.new(frameCount)
+	self.type = TimelineType.scale
+
+	function self:getPropertyId ()
+		return TimelineType.scale * SHL_24 + self.boneIndex
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
+		local frames = self.frames
+
+		local bone = skeleton.bones[self.boneIndex]
+		if not bone.active then return end
+
+		if time < frames[0] then
+			if blend == MixBlend.setup then
+				bone.scaleX = bone.data.scaleX
+				bone.scaleY = bone.data.scaleY
+			elseif blend == MixBlend.first then
+				bone.scaleX = bone.scaleX + (bone.data.scaleX - bone.scaleX) * alpha
+				bone.scaleY = bone.scaleY + (bone.data.scaleY - bone.scaleY) * alpha
+			end
+			return
+		end
+
+		local x = 0
+		local y = 0
+		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
+			x = frames[zlen(frames) + PREV_X] * bone.data.scaleX
+			y = frames[zlen(frames) + PREV_Y] * bone.data.scaleY
+		else
+			-- Interpolate between the previous frame and the current frame.
+			local frame = binarySearch(frames, time, ENTRIES)
+			x = frames[frame + PREV_X]
+			y = frames[frame + PREV_Y]
+			local frameTime = frames[frame]
+			local percent = self:getCurvePercent(math_floor(frame / ENTRIES) - 1,
+				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
+
+			x = (x + (frames[frame + X] - x) * percent) * bone.data.scaleX
+			y = (y + (frames[frame + Y] - y) * percent) * bone.data.scaleY
+		end
+		if alpha == 1 then
+			if blend == MixBlend.add then
+				bone.scaleX = bone.scaleX + x - bone.data.scaleX
+				bone.scaleY = bone.scaleY + y - bone.data.scaleY
+			else
+				bone.scaleX = x
+				bone.scaleY = y
+			end
+		else
+			local bx = 0
+			local by = 0
+			if direction == MixDirection.out then
+				if blend == MixBlend.setup then
+					bx = bone.data.scaleX
+					by = bone.data.scaleY
+					bone.scaleX = bx + (math_abs(x) * math_signum(bx) - bx) * alpha
+					bone.scaleY = by + (math_abs(y) * math_signum(by) - by) * alpha
+				elseif blend == MixBlend.first or blend == MixBlend.replace then
+					bx = bone.scaleX
+					by = bone.scaleY
+					bone.scaleX = bx + (math_abs(x) * math_signum(bx) - bx) * alpha
+					bone.scaleY = by + (math_abs(y) * math_signum(by) - by) * alpha
+				elseif blend == MixBlend.add then
+					bx = bone.scaleX
+					by = bone.scaleY
+					bone.scaleX = bx + (math_abs(x) * math_signum(bx) - bone.data.scaleX) * alpha
+					bone.scaleY = by + (math_abs(y) * math_signum(by) - bone.data.scaleY) * alpha
+				end
+			else
+				if blend == MixBlend.setup then
+					bx = math_abs(bone.data.scaleX) * math_signum(x)
+					by = math_abs(bone.data.scaleY) * math_signum(y)
+					bone.scaleX = bx + (x - bx) * alpha
+					bone.scaleY = by + (y - by) * alpha
+				elseif blend == MixBlend.first or blend == MixBlend.replace then
+					bx = math_abs(bone.scaleX) * math_signum(x)
+					by = math_abs(bone.scaleY) * math_signum(y)
+					bone.scaleX = bx + (x - bx) * alpha
+					bone.scaleY = by + (y - by) * alpha
+				elseif blend == MixBlend.add then
+					bx = math_signum(x)
+					by = math_signum(y)
+					bone.scaleX = math_abs(bone.scaleX) * bx + (x - math_abs(bone.data.scaleX) * bx) * alpha
+					bone.scaleY = math_abs(bone.scaleY) * by + (y - math_abs(bone.data.scaleY) * by) * alpha
+				end
+			end
+		end
+	end
+
+	return self
+end
+
+Animation.ShearTimeline = {}
+Animation.ShearTimeline.ENTRIES = Animation.TranslateTimeline.ENTRIES
+function Animation.ShearTimeline.new (frameCount)
+	local ENTRIES = Animation.ShearTimeline.ENTRIES
+	local PREV_TIME = -3
+	local PREV_X = -2
+	local PREV_Y = -1
+	local X = 1
+	local Y = 2
+
+	local self = Animation.TranslateTimeline.new(frameCount)
+	self.type = TimelineType.shear
+
+	function self:getPropertyId ()
+		return TimelineType.shear * SHL_24 + self.boneIndex
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
+		local frames = self.frames
+
+		local bone = skeleton.bones[self.boneIndex]
+		if not bone.active then return end
+
+		if time < frames[0] then
+			if blend == MixBlend.setup then
+				bone.shearX = bone.data.shearX
+				bone.shearY = bone.data.shearY
+			elseif blend == MixBlend.first then
+				bone.shearX = bone.shearX + (bone.data.shearX - bone.shearX) * alpha
+				bone.shearY = bone.shearX + (bone.data.shearY - bone.shearY) * alpha
+			end
+			return
+		end
+
+		local x = 0
+		local y = 0
+		if time >= frames[zlen(frames) - ENTRIES] then -- // Time is after last frame.
+			x = frames[zlen(frames) + PREV_X]
+			y = frames[zlen(frames) + PREV_Y]
+		else
+			-- Interpolate between the previous frame and the current frame.
+			local frame = binarySearch(frames, time, ENTRIES)
+			x = frames[frame + PREV_X]
+			y = frames[frame + PREV_Y]
+			local frameTime = frames[frame]
+			local percent = self:getCurvePercent(math_floor(frame / ENTRIES) - 1,
+				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
+
+			x = x + (frames[frame + X] - x) * percent
+			y = y + (frames[frame + Y] - y) * percent
+		end
+		if blend == MixBlend.setup then
+			bone.shearX = bone.data.shearX + x * alpha
+			bone.shearY = bone.data.shearY + y * alpha
+		elseif blend == MixBlend.first or blend == MixBlend.replace then
+			bone.shearX = bone.shearX + (bone.data.shearX + x - bone.shearX) * alpha
+			bone.shearY = bone.shearY + (bone.data.shearY + y - bone.shearY) * alpha
+		elseif blend == MixBlend.add then
+			bone.shearX = bone.shearX + x * alpha
+			bone.shearY = bone.shearY + y * alpha
+		end
+	end
+
+	return self
+end
+
+Animation.ColorTimeline = {}
+Animation.ColorTimeline.ENTRIES = 5
+function Animation.ColorTimeline.new (frameCount)
+	local ENTRIES = Animation.ColorTimeline.ENTRIES
+	local PREV_TIME = -5
+	local PREV_R = -4
+	local PREV_G = -3
+	local PREV_B = -2
+	local PREV_A = -1
+	local R = 1
+	local G = 2
+	local B = 3
+	local A = 4
+
+	local self = Animation.CurveTimeline.new(frameCount)
+	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
+	self.slotIndex = -1
+	self.type = TimelineType.color
+
+	function self:getPropertyId ()
+		return TimelineType.color * SHL_24 + self.slotIndex
+	end
+
+	function self:setFrame (frameIndex, time, r, g, b, a)
+		frameIndex = frameIndex * ENTRIES
+		self.frames[frameIndex] = time
+		self.frames[frameIndex + R] = r
+		self.frames[frameIndex + G] = g
+		self.frames[frameIndex + B] = b
+		self.frames[frameIndex + A] = a
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
+		local frames = self.frames
+		local slot = skeleton.slots[self.slotIndex]
+		if not slot.bone.active then return end
+		if time < frames[0] then
+			if blend == MixBlend.setup then
+				slot.color:setFrom(slot.data.color)
+			elseif blend == MixBlend.first then
+				local color = slot.color
+				local setup = slot.data.color
+				color:add((setup.r - color.r) * alpha, (setup.g - color.g) * alpha, (setup.b - color.b) * alpha,
+						(setup.a - color.a) * alpha)
+			end
+			return
+		end
+
+		local r, g, b, a
+		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
+			local i = zlen(frames)
+			r = frames[i + PREV_R]
+			g = frames[i + PREV_G]
+			b = frames[i + PREV_B]
+			a = frames[i + PREV_A]
+		else
+			-- Interpolate between the last frame and the current frame.
+			local frame = binarySearch(frames, time, ENTRIES)
+			r = frames[frame + PREV_R]
+			g = frames[frame + PREV_G]
+			b = frames[frame + PREV_B]
+			a = frames[frame + PREV_A]
+			local frameTime = frames[frame]
+			local percent = self:getCurvePercent(math.floor(frame / ENTRIES) - 1,
+					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
+
+			r = r + (frames[frame + R] - r) * percent
+			g = g + (frames[frame + G] - g) * percent
+			b = b + (frames[frame + B] - b) * percent
+			a = a + (frames[frame + A] - a) * percent
+		end
+		if alpha == 1 then
+			slot.color:set(r, g, b, a)
+		else
+			local color = slot.color
+			if blend == MixBlend.setup then color:setFrom(slot.data.color) end
+			color:add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha)
+		end
+	end
+
+	return self
+end
+
+Animation.TwoColorTimeline = {}
+Animation.TwoColorTimeline.ENTRIES = 8
+function Animation.TwoColorTimeline.new (frameCount)
+	local ENTRIES = Animation.TwoColorTimeline.ENTRIES
+	local PREV_TIME = -8
+	local PREV_R = -7
+	local PREV_G = -6
+	local PREV_B = -5
+	local PREV_A = -4
+	local PREV_R2 = -3
+	local PREV_G2 = -2
+	local PREV_B2 = -1
+	local R = 1
+	local G = 2
+	local B = 3
+	local A = 4
+	local R2 = 5
+	local G2 = 6
+	local B2 = 7
+
+	local self = Animation.CurveTimeline.new(frameCount)
+	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
+	self.slotIndex = -1
+	self.type = TimelineType.twoColor
+
+	function self:getPropertyId ()
+		return TimelineType.twoColor * SHL_24 + self.slotIndex
+	end
+
+	function self:setFrame (frameIndex, time, r, g, b, a, r2, g2, b2)
+		frameIndex = frameIndex * ENTRIES
+		self.frames[frameIndex] = time
+		self.frames[frameIndex + R] = r
+		self.frames[frameIndex + G] = g
+		self.frames[frameIndex + B] = b
+		self.frames[frameIndex + A] = a
+		self.frames[frameIndex + R2] = r2
+		self.frames[frameIndex + G2] = g2
+		self.frames[frameIndex + B2] = b2
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
+		local frames = self.frames
+		local slot = skeleton.slots[self.slotIndex]
+		if not slot.bone.active then return end
+
+		if time < frames[0] then
+			if blend == MixBlend.setup then
+				slot.color:setFrom(slot.data.color)
+				slot.darkColor:setFrom(slot.data.darkColor)
+			elseif blend == MixBlend.first then
+				local light = slot.color
+				local dark = slot.darkColor
+				local setupLight = slot.data.color
+				local setupDark = slot.data.darkColor
+				light:add((setupLight.r - light.r) * alpha, (setupLight.g - light.g) * alpha, (setupLight.b - light.b) * alpha,
+					(setupLight.a - light.a) * alpha)
+				dark:add((setupDark.r - dark.r) * alpha, (setupDark.g - dark.g) * alpha, (setupDark.b - dark.b) * alpha, 0)
+			end
+			return
+		end
+
+		local r, g, b, a, r2, g2, b2
+		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
+			local i = zlen(frames)
+			r = frames[i + PREV_R]
+			g = frames[i + PREV_G]
+			b = frames[i + PREV_B]
+			a = frames[i + PREV_A]
+			r2 = frames[i + PREV_R2]
+			g2 = frames[i + PREV_G2]
+			b2 = frames[i + PREV_B2]
+		else
+			-- Interpolate between the last frame and the current frame.
+			local frame = binarySearch(frames, time, ENTRIES)
+			r = frames[frame + PREV_R]
+			g = frames[frame + PREV_G]
+			b = frames[frame + PREV_B]
+			a = frames[frame + PREV_A]
+			r2 = frames[frame + PREV_R2]
+			g2 = frames[frame + PREV_G2]
+			b2 = frames[frame + PREV_B2]
+			local frameTime = frames[frame]
+			local percent = self:getCurvePercent(math.floor(frame / ENTRIES) - 1,
+					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
+
+			r = r + (frames[frame + R] - r) * percent
+			g = g + (frames[frame + G] - g) * percent
+			b = b + (frames[frame + B] - b) * percent
+			a = a + (frames[frame + A] - a) * percent
+			r2 = r2 + (frames[frame + R2] - r2) * percent
+			g2 = g2 + (frames[frame + G2] - g2) * percent
+			b2 = b2 + (frames[frame + B2] - b2) * percent
+		end
+		if alpha == 1 then
+			slot.color:set(r, g, b, a)
+			slot.darkColor:set(r2, g2, b2, 1)
+		else
+			local light = slot.color
+			local dark = slot.darkColor
+			if blend == MixBlend.setup then
+				light:setFrom(slot.data.color)
+				dark:setFrom(slot.data.darkColor)
+			end
+			light:add((r - light.r) * alpha, (g - light.g) * alpha, (b - light.b) * alpha, (a - light.a) * alpha)
+			dark:add((r2 - dark.r) * alpha, (g2 - dark.g) * alpha, (b2 - dark.b) * alpha, 0)
+		end
+	end
+
+	return self
+end
+
+Animation.AttachmentTimeline = {}
+function Animation.AttachmentTimeline.new (frameCount)
+	local self = {
+		frames = utils.newNumberArrayZero(frameCount), -- time, ...
+		attachmentNames = {},
+		slotIndex = -1,
+		type = TimelineType.attachment
+	}
+
+	function self:getFrameCount ()
+		return zlen(self.frames)
+	end
+
+	function self:setFrame (frameIndex, time, attachmentName)
+		self.frames[frameIndex] = time
+		self.attachmentNames[frameIndex] = attachmentName
+	end
+
+	function self:getPropertyId ()
+		return TimelineType.attachment * SHL_24 + self.slotIndex
+	end
+
+	function self:setAttachment(skeleton, slot, attachmentName)
+		attachmentName = slot.data.attachmentName
+		if not attachmentName then
+			slot:setAttachment(nil)
+		else
+			slot:setAttachment(skeleton:getAttachmentByIndex(self.slotIndex, attachmentName))
+		end
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
+		local slot = skeleton.slots[self.slotIndex]
+		if not slot.bone.active then return end
+		local attachmentName
+		if direction == MixDirection.out then
+			if blend == MixBlend.setup then
+				self:setAttachment(skeleton, slot, slot.data.attachmentName)
+			end
+			return
+		end
+
+		local frames = self.frames
+		if time < frames[0] then
+			if blend == MixBlend.setup or blend == MixBlend.first then
+				self:setAttachment(skeleton, slot, slot.data.attachmentName)
+			end
+			return
+		end
+
+		local frameIndex = 0
+		if time >= frames[zlen(frames) - 1] then
+			frameIndex = zlen(frames) - 1
+		else
+			frameIndex = binarySearch(frames, time, 1) - 1
+		end
+
+		attachmentName = self.attachmentNames[frameIndex]
+		if not attachmentName then
+			slot:setAttachment(nil)
+		else
+			slot:setAttachment(skeleton:getAttachmentByIndex(self.slotIndex, attachmentName))
+		end
+	end
+
+	return self
+end
+
+Animation.DeformTimeline = {}
+function Animation.DeformTimeline.new (frameCount)
+	local self = Animation.CurveTimeline.new(frameCount)
+	self.frames = utils.newNumberArrayZero(frameCount)
+	self.frameVertices = utils.newNumberArrayZero(frameCount)
+	self.slotIndex = -1
+	self.attachment = nil
+	self.type = TimelineType.deform
+
+	function self:getPropertyId ()
+		return TimelineType.deform * SHL_27 + self.attachment.id + self.slotIndex
+	end
+
+	function self:setFrame (frameIndex, time, vertices)
+		self.frames[frameIndex] = time
+		self.frameVertices[frameIndex] = vertices
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
+		local slot = skeleton.slots[self.slotIndex]
+		if not slot.bone.active then return end
+
+		local slotAttachment = slot.attachment
+		if not slotAttachment then return end
+		if not (slotAttachment.type == AttachmentType.mesh or slotAttachment.type == AttachmentType.linkedmesh or slotAttachment.type == AttachmentType.path or slotAttachment.type == AttachmentType.boundingbox) then return end
+		if slotAttachment.deformAttachment ~= self.attachment then return end
+
+		local frames = self.frames
+		local deformArray = slot.deform
+		if #(deformArray) == 0 then blend = MixBlend.setup end
+
+		local frameVertices = self.frameVertices
+		local vertexCount = #(frameVertices[0])
+
+		if time < frames[0] then
+			local vertexAttachment = slotAttachment
+			if blend == MixBlend.setup then
+				slot.deform = {}
+				return
+			elseif blend == MixBlend.first then
+				if (alpha == 1) then
+					slot.deform = {}
+					return
+				end
+
+				local deform = utils.setArraySize(deformArray, vertexCount)
+				if (vertexAttachment.bones == nil) then
+					local setupVertices = vertexAttachment.vertices
+					local i = 1
+					while i <= vertexCount do
+						deform[i] = deform[i] + (setupVertices[i] - deform[i]) * alpha
+						i = i + 1
+					end
+				else
+					alpha = 1 - alpha
+					local i = 1
+					while i <= vertexCount do
+						deform[i] = deform[i] * alpha
+						i = i + 1
+					end
+				end
+			end
+			return
+		end
+
+		local deform = utils.setArraySize(deformArray, vertexCount)
+		if time >= frames[zlen(frames) - 1] then -- Time is after last frame.
+			local lastVertices = frameVertices[zlen(frames) - 1]
+			if alpha == 1 then
+				if blend == MixBlend.add then
+					local vertexAttachment = slotAttachment
+					if vertexAttachment.bones == nil then
+						-- Unweighted vertex positions, with alpha.
+						local setupVertices = vertexAttachment.vertices
+						local i = 1
+						while i <= vertexCount do
+							deform[i] = deform[i] + lastVertices[i] - setupVertices[i]
+							i = i + 1
+						end
+					else
+						-- Weighted deform offsets, with alpha.
+						local i = 1
+						while i <= vertexCount do
+							deform[i] = deform[i] + lastVertices[i]
+							i = i + 1
+						end
+					end
+				else
+					local i = 1
+					while i <= vertexCount do
+						deform[i] = lastVertices[i]
+						i = i + 1
+					end
+				end
+			else
+				if blend == MixBlend.setup then
+					local vertexAttachment = slotAttachment
+					if vertexAttachment.bones == nil then
+						-- Unweighted vertex positions, with alpha.
+						local setupVertices = vertexAttachment.vertices
+						local i = 1
+						while i <= vertexCount do
+							local setup = setupVertices[i]
+							deform[i] = setup + (lastVertices[i] - setup) * alpha
+							i = i + 1
+						end
+					else
+						-- Weighted deform offsets, with alpha.
+						local i = 1
+						while i <= vertexCount do
+							deform[i] = lastVertices[i] * alpha
+							i = i + 1
+						end
+					end
+				elseif blend == MixBlend.first or blend == MixBlend.replace then
+					local i = 1
+					while i <= vertexCount do
+						deform[i] = deform[i] + (lastVertices[i] - deform[i]) * alpha
+						i = i + 1
+					end
+					local vertexAttachment = slotAttachment
+					if vertexAttachment.bones == nil then
+						local setupVertices = vertexAttachment.vertices
+						local i = 1
+						while i <= vertexCount do
+							deform[i] = deform[i] + (lastVertices[i] - setupVertices[i]) * alpha
+							i = i + 1
+						end
+					else
+						-- Weighted deform offsets, with alpha.
+						local i = 1
+						while i <= vertexCount do
+							deform[i] = deform[i] + lastVertices[i] * alpha
+							i = i + 1
+						end
+					end
+				elseif blend == MixBlend.add then
+					local vertexAttachment = slotAttachment
+					if vertexAttachment.bones == nil then
+						local setupVertices = vertexAttachment.vertices
+						local i = 1
+						while i <= vertexCount do
+							deform[i] = deform[i] + (lastVertices[i] - setupVertices[i]) * alpha
+							i = i + 1
+						end
+					else
+						-- Weighted deform offsets, with alpha.
+						local i = 1
+						while i <= vertexCount do
+							deform[i] = deform[i] + lastVertices[i] * alpha
+							i = i + 1
+						end
+					end
+				end
+			end
+			return
+		end
+
+		-- Interpolate between the previous frame and the current frame.
+		local frame = binarySearch(frames, time, 1)
+		local prevVertices = frameVertices[frame - 1]
+		local nextVertices = frameVertices[frame]
+		local frameTime = frames[frame]
+		local percent = self:getCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime))
+
+		if alpha == 1 then
+			if blend == MixBlend.add then
+				local vertexAttachment = slotAttachment
+				if vertexAttachment.bones == nil then
+					-- Unweighted vertex positions, with alpha.
+					local setupVertices = vertexAttachment.vertices
+					local i = 1
+					while i <= vertexCount do
+						local prev = prevVertices[i]
+						deform[i] = deform[i] + prev + (nextVertices[i] - prev) * precent - setupVertices[i]
+						i = i + 1
+					end
+				else
+					-- Weighted deform offsets, with alpha.
+					local i = 1
+					while i <= vertexCount do
+						local prev = prevVertices[i]
+						deform[i] = deform[i] + prev + (nextVertices[i] - prev) * percent
+						i = i + 1
+					end
+				end
+			else
+				local i = 1
+				while i <= vertexCount do
+					local prev = prevVertices[i]
+					deform[i] = prev + (nextVertices[i] - prev) * percent
+					i = i + 1
+				end
+			end
+		else
+			if blend == MixBlend.setup then
+				local vertexAttachment = slotAttachment
+				if vertexAttachment.bones == nil then
+					-- Unweighted vertex positions, with alpha.
+					local setupVertices = vertexAttachment.vertices
+					local i = 1
+					while i <= vertexCount do
+						local prev = prevVertices[i]
+						local setup = setupVertices[i]
+						deform[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha
+						i = i + 1
+					end
+				else
+					-- Weighted deform offsets, with alpha.
+					local i = 1
+					while i <= vertexCount do
+						local prev = prevVertices[i]
+						deform[i] = (prev + (nextVertices[i] - prev) * percent) * alpha
+						i = i + 1
+					end
+				end
+			elseif blend == MixBlend.first or blend == MixBlend.replace then
+				local i = 1
+				while i <= vertexCount do
+					local prev = prevVertices[i]
+					deform[i] = deform[i] + (prev + (nextVertices[i] - prev) * percent - deform[i]) * alpha
+					i = i + 1
+				end
+			elseif blend == MixBlend.add then
+				local vertexAttachment = slotAttachment
+				if vertexAttachment.bones == nil then
+					local setupVertices = vertexAttachment.vertices
+					local i = 1
+					while i <= vertexCount do
+						local prev = prevVertices[i]
+						deform[i] = deform[i] + (prev + (nextVertices[i] - prev) * percent - setupVertices[i]) * alpha
+						i = i + 1
+					end
+				else
+					-- Weighted deform offsets, with alpha.
+					local i = 1
+					while i <= vertexCount do
+						local prev = prevVertices[i]
+						deform[i] = deform[i] + (prev + (nextVertices[i] - prev) * percent) * alpha
+						i = i + 1
+					end
+				end
+			end
+		end
+	end
+
+	return self
+end
+
+Animation.EventTimeline = {}
+function Animation.EventTimeline.new (frameCount)
+	local self = {
+		frames = utils.newNumberArrayZero(frameCount),
+		events = {},
+		type = TimelineType.event
+	}
+
+	function self:getPropertyId ()
+		return TimelineType.event * SHL_24
+	end
+
+	function self:getFrameCount ()
+		return zlen(self.frames)
+	end
+
+	function self:setFrame (frameIndex, event)
+		self.frames[frameIndex] = event.time
+		self.events[frameIndex] = event
+	end
+
+	-- Fires events for frames > lastTime and <= time.
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
+		if not firedEvents then return end
+
+		local frames = self.frames
+		local frameCount = zlen(frames)
+
+		if lastTime > time then -- Fire events after last time for looped animations.
+			self:apply(skeleton, lastTime, 999999, firedEvents, alpha, blend, direction)
+			lastTime = -1
+		elseif lastTime >= frames[frameCount - 1] then -- Last time is after last frame.
+			return
+		end
+		if time < frames[0] then return end -- Time is before first frame.
+
+		local frame
+		if lastTime < frames[0] then
+			frame = 0
+		else
+			frame = binarySearch1(frames, lastTime)
+			local frame = frames[frame]
+			while frame > 0 do -- Fire multiple events with the same frame.
+				if frames[frame - 1] ~= frame then break end
+				frame = frame - 1
+			end
+		end
+		local events = self.events
+		while frame < frameCount and time >= frames[frame] do
+			table.insert(firedEvents, events[frame])
+			frame = frame + 1
+		end
+	end
+
+	return self
+end
+
+Animation.DrawOrderTimeline = {}
+function Animation.DrawOrderTimeline.new (frameCount)
+	local self = {
+		frames = utils.newNumberArrayZero(frameCount),
+		drawOrders = {},
+		type = TimelineType.drawOrder
+	}
+
+	function self:getPropertyId ()
+		return TimelineType.drawOrder * SHL_24
+	end
+
+	function self:getFrameCount ()
+		return zlen(self.frames)
+	end
+
+	function self:setFrame (frameIndex, time, drawOrder)
+		self.frames[frameIndex] = time
+		self.drawOrders[frameIndex] = drawOrder
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
+		local drawOrder = skeleton.drawOrder
+		local slots = skeleton.slots
+		if direction == MixDirection.out then
+			if blend == MixBlend.setup then
+				for i,slot in ipairs(slots) do
+					drawOrder[i] = slots[i]
+				end
+			end
+			return
+		end
+
+		local frames = self.frames
+		if time < frames[0] then
+			if blend == MixBlend.setup or blend == MixBlend.first then
+				for i,slot in ipairs(slots) do
+					drawOrder[i] = slots[i]
+				end
+			end
+			return
+		end
+
+		local frame
+		if time >= frames[zlen(frames) - 1] then -- Time is after last frame.
+			frame = zlen(frames) - 1
+		else
+			frame = binarySearch1(frames, time) - 1
+		end
+
+		local drawOrderToSetupIndex = self.drawOrders[frame]
+		if not drawOrderToSetupIndex then
+			for i,slot in ipairs(slots) do
+				drawOrder[i] = slots[i]
+			end
+		else
+			for i,setupIndex in ipairs(drawOrderToSetupIndex) do
+				drawOrder[i] = skeleton.slots[setupIndex]
+			end
+		end
+	end
+
+	return self
+end
+
+Animation.IkConstraintTimeline = {}
+Animation.IkConstraintTimeline.ENTRIES = 6
+function Animation.IkConstraintTimeline.new (frameCount)
+	local ENTRIES = Animation.IkConstraintTimeline.ENTRIES
+	local PREV_TIME = -6
+	local PREV_MIX = -5
+	local PREV_SOFTNESS = -4
+	local PREV_BEND_DIRECTION = -3
+	local PREV_COMPRESS = -2
+	local PREV_STRETCH = -1
+	local MIX = 1
+	local SOFTNESS = 2
+	local BEND_DIRECTION = 3
+	local COMPRESS = 4
+	local STRETCH = 5
+
+	local self = Animation.CurveTimeline.new(frameCount)
+	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES) -- time, mix, softness, bendDirection, compress, stretch, ...
+	self.ikConstraintIndex = -1
+	self.type = TimelineType.ikConstraint
+
+	function self:getPropertyId ()
+		return TimelineType.ikConstraint * SHL_24 + self.ikConstraintIndex
+	end
+
+	function self:setFrame (frameIndex, time, mix, softness, bendDirection, compress, stretch)
+		frameIndex = frameIndex * ENTRIES
+		self.frames[frameIndex] = time
+		self.frames[frameIndex + MIX] = mix
+		self.frames[frameIndex + SOFTNESS] = softness
+		self.frames[frameIndex + BEND_DIRECTION] = bendDirection
+		if (compress) then
+			self.frames[frameIndex + COMPRESS] = 1
+		else
+			self.frames[frameIndex + COMPRESS] = 0
+		end
+		if (stretch) then
+			self.frames[frameIndex + STRETCH] = 1
+		else
+			self.frames[frameIndex + STRETCH] = 0
+		end
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
+		local frames = self.frames
+
+		local constraint = skeleton.ikConstraints[self.ikConstraintIndex]
+		if not constraint.active then return end
+		if time < frames[0] then
+			if blend == MixBlend.setup then
+				constraint.mix = constraint.data.mix
+				constraint.softness = constraint.data.softness
+				constraint.bendDirection = constraint.data.bendDirection
+				constraint.compress = constraint.data.compress
+				constraint.stretch = constraint.data.stretch
+			elseif blend == MixBlend.first then
+				constraint.mix = constraint.mix + (constraint.data.mix - constraint.mix) * alpha
+				constraint.softness = constraint.softness + (constraint.data.softness - constraint.softness) * alpha
+				constraint.bendDirection = constraint.data.bendDirection
+				constraint.compress = constraint.data.compress
+				constraint.stretch = constraint.data.stretch
+			end
+			return
+		end
+
+		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
+			if blend == MixBlend.setup then
+				constraint.mix = constraint.data.mix + (frames[zlen(frames) + PREV_MIX] - constraint.data.mix) * alpha
+				constraint.softness = constraint.data.softness + (frames[zlen(frames) + PREV_SOFTNESS] - constraint.data.softness) * alpha
+				if direction == MixDirection.out then
+					constraint.bendDirection = constraint.data.bendDirection
+					constraint.compress = constraint.data.compress
+					constraint.stretch = constraint.data.stretch
+				else
+					constraint.bendDirection = math_floor(frames[zlen(frames) + PREV_BEND_DIRECTION])
+					if (math_floor(frames[zlen(frames) + PREV_COMPRESS]) == 1) then constraint.compress = true else constraint.compress = false end
+					if (math_floor(frames[zlen(frames) + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end
+				end
+			else
+				constraint.mix = constraint.mix + (frames[zlen(frames) + PREV_MIX] - constraint.mix) * alpha
+				constraint.softness = constraint.softness + (frames[zlen(frames) + PREV_SOFTNESS] - constraint.softness) * alpha
+				if direction == MixDirection._in then
+					constraint.bendDirection = math_floor(frames[zlen(frames) + PREV_BEND_DIRECTION])
+					if (math_floor(frames[zlen(frames) + PREV_COMPRESS]) == 1) then constraint.compress = true else constraint.compress = false end
+					if (math_floor(frames[zlen(frames) + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end
+				end
+			end
+			return
+		end
+
+		-- Interpolate between the previous frame and the current frame.
+		local frame = binarySearch(frames, time, ENTRIES)
+		local mix = frames[frame + PREV_MIX]
+		local softness = frames[frame + PREV_SOFTNESS]
+		local frameTime = frames[frame]
+		local percent = self:getCurvePercent(math.floor(frame / ENTRIES) - 1,
+				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
+
+		if blend == MixBlend.setup then
+			constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha
+			constraint.softness = constraint.data.softness + (softness + (frames[frame + SOFTNESS] - softness) * percent - constraint.data.softness) * alpha
+			if direction == MixDirection.out then
+				constraint.bendDirection = constraint.data.bendDirection
+				constraint.compress = constraint.data.compress
+				constraint.stretch = constraint.data.stretch
+			else
+				constraint.bendDirection = math_floor(frames[frame + PREV_BEND_DIRECTION])
+				if (math_floor(frames[frame + PREV_COMPRESS]) == 1) then constraint.compress = true else constraint.compress = false end
+				if (math_floor(frames[frame + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end
+			end
+		else
+			constraint.mix = constraint.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha
+			constraint.softness = constraint.softness + (softness + (frames[frame + SOFTNESS] - softness) * percent - constraint.softness) * alpha
+			if direction == MixDirection._in then
+				constraint.bendDirection = math_floor(frames[frame + PREV_BEND_DIRECTION])
+				if (math_floor(frames[frame + PREV_COMPRESS]) == 1) then constraint.compress = true else constraint.compress = false end
+				if (math_floor(frames[frame + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end
+			end
+		end
+	end
+
+	return self
+end
+
+Animation.TransformConstraintTimeline = {}
+Animation.TransformConstraintTimeline.ENTRIES = 5
+function Animation.TransformConstraintTimeline.new (frameCount)
+	local ENTRIES = Animation.TransformConstraintTimeline.ENTRIES
+	local PREV_TIME = -5
+	local PREV_ROTATE = -4
+	local PREV_TRANSLATE = -3
+	local PREV_SCALE = -2
+	local PREV_SHEAR = -1
+	local ROTATE = 1
+	local TRANSLATE = 2
+	local SCALE = 3
+	local SHEAR = 4
+
+	local self = Animation.CurveTimeline.new(frameCount)
+	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
+	self.transformConstraintIndex = -1
+	self.type = TimelineType.transformConstraint
+
+	function self:getPropertyId ()
+		return TimelineType.transformConstraint * SHL_24 + self.transformConstraintIndex
+	end
+
+	function self:setFrame (frameIndex, time, rotateMix, translateMix, scaleMix, shearMix)
+		frameIndex = frameIndex * ENTRIES
+		self.frames[frameIndex] = time
+		self.frames[frameIndex + ROTATE] = rotateMix
+		self.frames[frameIndex + TRANSLATE] = translateMix
+		self.frames[frameIndex + SCALE] = scaleMix
+		self.frames[frameIndex + SHEAR] = shearMix
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
+		local frames = self.frames
+
+		local constraint = skeleton.transformConstraints[self.transformConstraintIndex]
+		if not constraint.active then return end
+
+		if time < frames[0] then
+			local data = constraint.data
+			if blend == MixBlend.setup then
+				constraint.rotateMix = data.rotateMix
+				constraint.translateMix = data.translateMix
+				constraint.scaleMix = data.scaleMix
+				constraint.shearMix = data.shearMix
+			elseif blend == MixBlend.first then
+				constraint.rotateMix = constraint.rotateMix + (data.rotateMix - constraint.rotateMix) * alpha
+				constraint.translateMix = constraint.translateMix + (data.translateMix - constraint.translateMix) * alpha
+				constraint.scaleMix = constraint.scaleMix + (data.scaleMix - constraint.scaleMix) * alpha
+				constraint.shearMix = constraint.shearMix + (data.shearMix - constraint.shearMix) * alpha
+			end
+			return
+		end
+
+		local rotate = 0
+		local translate = 0
+		local scale = 0
+		local shear = 0
+		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
+			local i = zlen(frames)
+			rotate = frames[i + PREV_ROTATE]
+			translate = frames[i + PREV_TRANSLATE]
+			scale = frames[i + PREV_SCALE]
+			shear = frames[i + PREV_SHEAR]
+		else
+			-- Interpolate between the previous frame and the current frame.
+			local frame = binarySearch(frames, time, ENTRIES)
+			rotate = frames[frame + PREV_ROTATE]
+			translate = frames[frame + PREV_TRANSLATE]
+			scale = frames[frame + PREV_SCALE]
+			shear = frames[frame + PREV_SHEAR]
+			local frameTime = frames[frame]
+			local percent = self:getCurvePercent(math_floor(frame / ENTRIES) - 1,
+				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
+
+			rotate = rotate + (frames[frame + ROTATE] - rotate) * percent
+			translate = translate + (frames[frame + TRANSLATE] - translate) * percent
+			scale = scale + (frames[frame + SCALE] - scale) * percent
+			shear = shear + (frames[frame + SHEAR] - shear) * percent
+		end
+		if blend == MixBlend.setup then
+			local data = constraint.data
+			constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha
+			constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha
+			constraint.scaleMix = data.scaleMix + (scale - data.scaleMix) * alpha
+			constraint.shearMix = data.shearMix + (shear - data.shearMix) * alpha
+		else
+			constraint.rotateMix = constraint.rotateMix + (rotate - constraint.rotateMix) * alpha
+			constraint.translateMix = constraint.translateMix + (translate - constraint.translateMix) * alpha
+			constraint.scaleMix = constraint.scaleMix + (scale - constraint.scaleMix) * alpha
+			constraint.shearMix = constraint.shearMix + (shear - constraint.shearMix) * alpha
+		end
+	end
+
+	return self
+end
+
+Animation.PathConstraintPositionTimeline = {}
+Animation.PathConstraintPositionTimeline.ENTRIES = 2
+function Animation.PathConstraintPositionTimeline.new (frameCount)
+	local ENTRIES = Animation.PathConstraintPositionTimeline.ENTRIES
+	local PREV_TIME = -2
+	local PREV_VALUE = -1
+	local VALUE = 1
+
+	local self = Animation.CurveTimeline.new(frameCount)
+	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
+	self.pathConstraintIndex = -1
+	self.type = TimelineType.pathConstraintPosition
+
+	function self:getPropertyId ()
+		return TimelineType.pathConstraintPosition * SHL_24 + self.pathConstraintIndex
+	end
+
+	function self:setFrame (frameIndex, time, value)
+		frameIndex = frameIndex * ENTRIES
+		self.frames[frameIndex] = time
+		self.frames[frameIndex + VALUE] = value
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
+		local frames = self.frames
+
+		local constraint = skeleton.pathConstraints[self.pathConstraintIndex]
+		if not constraint.active then return end
+
+		if (time < frames[0]) then
+			if blend == MixBlend.setup then
+				constraint.position = constraint.data.position
+			elseif blend == MixBlend.first then
+				constraint.position = constraint.position + (constraint.data.position - constraint.position) * alpha
+			end
+			return
+		end
+
+		local position = 0
+		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
+			position = frames[zlen(frames) + PREV_VALUE]
+		else
+			-- Interpolate between the previous frame and the current frame.
+			local frame = binarySearch(frames, time, ENTRIES)
+			position = frames[frame + PREV_VALUE]
+			local frameTime = frames[frame]
+			local percent = self:getCurvePercent(math_floor(frame / ENTRIES) - 1,
+				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
+
+			position = position + (frames[frame + VALUE] - position) * percent
+		end
+		if blend == MixBlend.setup then
+			constraint.position = constraint.data.position + (position - constraint.data.position) * alpha
+		else
+			constraint.position = constraint.position + (position - constraint.position) * alpha
+		end
+	end
+
+	return self
+end
+
+Animation.PathConstraintSpacingTimeline = {}
+Animation.PathConstraintSpacingTimeline.ENTRIES = 2
+function Animation.PathConstraintSpacingTimeline.new (frameCount)
+	local ENTRIES = Animation.PathConstraintSpacingTimeline.ENTRIES
+	local PREV_TIME = -2
+	local PREV_VALUE = -1
+	local VALUE = 1
+
+	local self = Animation.CurveTimeline.new(frameCount)
+	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
+	self.pathConstraintIndex = -1
+	self.type = TimelineType.pathConstraintSpacing
+
+	function self:getPropertyId ()
+		return TimelineType.pathConstraintSpacing * SHL_24 + self.pathConstraintIndex
+	end
+
+	function self:setFrame (frameIndex, time, value)
+		frameIndex = frameIndex * ENTRIES
+		self.frames[frameIndex] = time
+		self.frames[frameIndex + VALUE] = value
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
+		local frames = self.frames
+
+		local constraint = skeleton.pathConstraints[self.pathConstraintIndex]
+		if not constraint.active then return end
+
+		if (time < frames[0]) then
+			if blend == MixBlend.setup then
+				constraint.spacing = constraint.data.spacing
+			elseif blend == MixBlend.first then
+				constraint.spacing = constraint.spacing + (constraint.data.spacing - constraint.spacing) * alpha
+			end
+			return
+		end
+
+		local spacing = 0
+		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
+			spacing = frames[zlen(frames) + PREV_VALUE]
+		else
+			-- Interpolate between the previous frame and the current frame.
+			local frame = binarySearch(frames, time, ENTRIES)
+			spacing = frames[frame + PREV_VALUE]
+			local frameTime = frames[frame]
+			local percent = self:getCurvePercent(math_floor(frame / ENTRIES) - 1,
+				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
+
+			spacing = spacing + (frames[frame + VALUE] - spacing) * percent
+		end
+
+		if blend == MixBlend.setup then
+			constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha
+		else
+			constraint.spacing = constraint.spacing + (spacing - constraint.spacing) * alpha
+		end
+	end
+
+	return self
+end
+
+Animation.PathConstraintMixTimeline = {}
+Animation.PathConstraintMixTimeline.ENTRIES = 3
+function Animation.PathConstraintMixTimeline.new (frameCount)
+	local ENTRIES = Animation.PathConstraintMixTimeline.ENTRIES
+	local PREV_TIME = -3
+	local PREV_ROTATE = -2
+	local PREV_TRANSLATE = -1
+	local ROTATE = 1
+	local TRANSLATE = 2
+
+	local self = Animation.CurveTimeline.new(frameCount)
+	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
+	self.pathConstraintIndex = -1
+	self.type = TimelineType.pathConstraintMix
+
+	function self:getPropertyId ()
+		return TimelineType.pathConstraintMix * SHL_24 + self.pathConstraintIndex
+	end
+
+	function self:setFrame (frameIndex, time, rotateMix, translateMix)
+		frameIndex = frameIndex * ENTRIES
+		self.frames[frameIndex] = time
+		self.frames[frameIndex + ROTATE] = rotateMix
+		self.frames[frameIndex + TRANSLATE] = translateMix
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
+		local frames = self.frames
+
+		local constraint = skeleton.pathConstraints[self.pathConstraintIndex]
+		if not constraint.active then return end
+
+		if (time < frames[0]) then
+			if blend == MixBlend.setup then
+				constraint.rotateMix = constraint.data.rotateMix
+				constraint.translateMix = constraint.data.translateMix
+			elseif blend == MixBlend.first then
+				constraint.rotateMix = constraint.rotateMix + (constraint.data.rotateMix - constraint.rotateMix) * alpha
+				constraint.translateMix = constraint.translateMix + (constraint.data.translateMix - constraint.translateMix) * alpha
+			end
+			return
+		end
+
+		local rotate = 0
+		local translate = 0
+		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
+			rotate = frames[zlen(frames) + PREV_ROTATE]
+			translate = frames[zlen(frames) + PREV_TRANSLATE]
+		else
+			-- Interpolate between the previous frame and the current frame.
+			local frame = binarySearch(frames, time, ENTRIES)
+			rotate = frames[frame + PREV_ROTATE]
+			translate = frames[frame + PREV_TRANSLATE]
+			local frameTime = frames[frame]
+			local percent = self:getCurvePercent(math_floor(frame / ENTRIES) - 1,
+				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
+
+			rotate = rotate + (frames[frame + ROTATE] - rotate) * percent
+			translate = translate + (frames[frame + TRANSLATE] - translate) * percent
+		end
+
+		if blend == MixBlend.setup then
+			constraint.rotateMix = constraint.data.rotateMix + (rotate - constraint.data.rotateMix) * alpha
+			constraint.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha
+		else
+			constraint.rotateMix = constraint.rotateMix + (rotate - constraint.rotateMix) * alpha
+			constraint.translateMix = constraint.translateMix + (translate - constraint.translateMix) * alpha
+		end
+	end
+
+	return self
+end
+
+return Animation

+ 19 - 19
spine-lua/spine-lua/AnimationState.lua

@@ -159,7 +159,7 @@ function EventQueue:drain ()
 	end
 	end
 	self:clear()
 	self:clear()
 
 
-	self.drainDisabled = false;
+	self.drainDisabled = false
 end
 end
 
 
 function EventQueue:clear ()
 function EventQueue:clear ()
@@ -326,7 +326,7 @@ function AnimationState:updateMixingFrom (to, delta)
 
 
 	from.trackTime = from.trackTime + delta * from.timeScale
 	from.trackTime = from.trackTime + delta * from.timeScale
 	to.mixTime = to.mixTime + delta
 	to.mixTime = to.mixTime + delta
-	return false;
+	return false
 end
 end
 
 
 function AnimationState:apply (skeleton)
 function AnimationState:apply (skeleton)
@@ -388,7 +388,7 @@ function AnimationState:apply (skeleton)
 					end
 					end
 				end
 				end
 				self:queueEvents(current, animationTime)
 				self:queueEvents(current, animationTime)
-				self.events = {};
+				self.events = {}
 				current.nextAnimationLast = animationTime
 				current.nextAnimationLast = animationTime
 				current.nextTrackLast = current.trackTime
 				current.nextTrackLast = current.trackTime
 			end
 			end
@@ -400,7 +400,7 @@ function AnimationState:apply (skeleton)
 	-- subsequent timelines see any deform, but the subsequent timelines don't set an attachment (eg they are also mixing out or
 	-- subsequent timelines see any deform, but the subsequent timelines don't set an attachment (eg they are also mixing out or
 	-- the time is before the first key).
 	-- the time is before the first key).
 	local setupState = self.unkeyedState + SETUP
 	local setupState = self.unkeyedState + SETUP
-	local slots = skeleton.slots;
+	local slots = skeleton.slots
 	for _, slot in ipairs(slots) do
 	for _, slot in ipairs(slots) do
 		if slot.attachmentState == setupState then
 		if slot.attachmentState == setupState then
 			local attachmentName = slot.data.attachmentName
 			local attachmentName = slot.data.attachmentName
@@ -411,7 +411,7 @@ function AnimationState:apply (skeleton)
 			end
 			end
 		end
 		end
 	end
 	end
-	self.unkeyedState = self.unkeyedState + 2; -- Increasing after each use avoids the need to reset attachmentState for every slot.
+	self.unkeyedState = self.unkeyedState + 2 -- Increasing after each use avoids the need to reset attachmentState for every slot.
 
 
 
 
 	queue:drain()
 	queue:drain()
@@ -453,11 +453,11 @@ function AnimationState:applyMixingFrom (to, skeleton, blend)
 		local firstFrame = #from.timelinesRotation == 0
 		local firstFrame = #from.timelinesRotation == 0
 		local timelinesRotation = from.timelinesRotation
 		local timelinesRotation = from.timelinesRotation
 
 
-		from.totalAlpha = 0;
+		from.totalAlpha = 0
 
 
 		for i,timeline in ipairs(timelines) do
 		for i,timeline in ipairs(timelines) do
-			local skipSubsequent = false;
-			local direction = MixDirection.out;
+			local skipSubsequent = false
+			local direction = MixDirection.out
 			local timelineBlend = MixBlend.setup
 			local timelineBlend = MixBlend.setup
 			local alpha = 0
 			local alpha = 0
 			if timelineMode[i] == SUBSEQUENT then
 			if timelineMode[i] == SUBSEQUENT then
@@ -498,7 +498,7 @@ function AnimationState:applyMixingFrom (to, skeleton, blend)
 	if (to.mixDuration > 0) then
 	if (to.mixDuration > 0) then
 		self:queueEvents(from, animationTime)
 		self:queueEvents(from, animationTime)
 	end
 	end
-	self.events = {};
+	self.events = {}
 	from.nextAnimationLast = animationTime
 	from.nextAnimationLast = animationTime
 	from.nextTrackLast = from.trackTime
 	from.nextTrackLast = from.trackTime
 
 
@@ -506,20 +506,20 @@ function AnimationState:applyMixingFrom (to, skeleton, blend)
 end
 end
 
 
 function AnimationState:applyAttachmentTimeline(timeline, skeleton, time, blend, attachments)
 function AnimationState:applyAttachmentTimeline(timeline, skeleton, time, blend, attachments)
-	local slot = skeleton.slots[timeline.slotIndex];
+	local slot = skeleton.slots[timeline.slotIndex]
 	if slot.bone.active == false then return end
 	if slot.bone.active == false then return end
 
 
 	local frames = timeline.frames
 	local frames = timeline.frames
 	if time < frames[0] then -- Time is before first frame.
 	if time < frames[0] then -- Time is before first frame.
 		if blend == MixBlend.setup or blend == MixBlend.first then
 		if blend == MixBlend.setup or blend == MixBlend.first then
-			self:setAttachment(skeleton, slot, slot.data.attachmentName, attachments);
+			self:setAttachment(skeleton, slot, slot.data.attachmentName, attachments)
 		end
 		end
 	else
 	else
 		local frameIndex = 0
 		local frameIndex = 0
 		if (time >= frames[zlen(frames) - 1]) then -- Time is after last frame.
 		if (time >= frames[zlen(frames) - 1]) then -- Time is after last frame.
-			frameIndex = zlen(frames) - 1;
+			frameIndex = zlen(frames) - 1
 		else
 		else
-			frameIndex = Animation.binarySearch(frames, time, 1) - 1;
+			frameIndex = Animation.binarySearch(frames, time, 1) - 1
 		end
 		end
 		self:setAttachment(skeleton, slot, timeline.attachmentNames[frameIndex], attachments)
 		self:setAttachment(skeleton, slot, timeline.attachmentNames[frameIndex], attachments)
 	end
 	end
@@ -665,7 +665,7 @@ function AnimationState:clearTracks ()
 	local queue = self.queue
 	local queue = self.queue
 	local tracks = self.tracks
 	local tracks = self.tracks
 	local oldDrainDisabled = queue.drainDisabled
 	local oldDrainDisabled = queue.drainDisabled
-	queue.drainDisabled = true;
+	queue.drainDisabled = true
 	local numTracks = getNumTracks(tracks)
 	local numTracks = getNumTracks(tracks)
 	local i = 0
 	local i = 0
 	while i <= numTracks do
 	while i <= numTracks do
@@ -673,7 +673,7 @@ function AnimationState:clearTracks ()
 	end
 	end
 	tracks = {}
 	tracks = {}
 	queue.drainDisabled = oldDrainDisabled
 	queue.drainDisabled = oldDrainDisabled
-	queue:drain();
+	queue:drain()
 end
 end
 
 
 function AnimationState:clearTrack (trackIndex)
 function AnimationState:clearTrack (trackIndex)
@@ -686,7 +686,7 @@ function AnimationState:clearTrack (trackIndex)
 
 
 	self:disposeNext(current)
 	self:disposeNext(current)
 
 
-	local entry = current;
+	local entry = current
 	while (true) do
 	while (true) do
 		local from = entry.mixingFrom
 		local from = entry.mixingFrom
 		if from == nil then break end
 		if from == nil then break end
@@ -717,7 +717,7 @@ function AnimationState:setCurrent (index, current, interrupt)
 			current.interruptAlpha = current.interruptAlpha * math_min(1, from.mixTime / from.mixDuration)
 			current.interruptAlpha = current.interruptAlpha * math_min(1, from.mixTime / from.mixDuration)
 		end
 		end
 
 
-		from.timelinesRotation = {};
+		from.timelinesRotation = {}
 	end
 	end
 
 
 	queue:start(current)
 	queue:start(current)
@@ -731,7 +731,7 @@ end
 
 
 function AnimationState:setAnimation (trackIndex, animation, loop)
 function AnimationState:setAnimation (trackIndex, animation, loop)
 	if not animation then error("animation cannot be null.") end
 	if not animation then error("animation cannot be null.") end
-	local interrupt = true;
+	local interrupt = true
 	local current = self:expandToIndex(trackIndex)
 	local current = self:expandToIndex(trackIndex)
 	local queue = self.queue
 	local queue = self.queue
 	local tracks = self.tracks
 	local tracks = self.tracks
@@ -743,7 +743,7 @@ function AnimationState:setAnimation (trackIndex, animation, loop)
 			queue:_end(current)
 			queue:_end(current)
 			self:disposeNext(current)
 			self:disposeNext(current)
 			current = current.mixingFrom
 			current = current.mixingFrom
-			interrupt = false;
+			interrupt = false
 		else
 		else
 			self:disposeNext(current)
 			self:disposeNext(current)
 		end
 		end

+ 14 - 15
spine-lua/spine-lua/Bone.lua

@@ -40,13 +40,12 @@ local math_pi = math.pi
 local TransformMode = require "spine-lua.TransformMode"
 local TransformMode = require "spine-lua.TransformMode"
 
 
 function math.sign(x)
 function math.sign(x)
-	if x<0 then
+	if x < 0 then
 		return -1
 		return -1
-	elseif x>0 then
+	elseif x > 0 then
 		return 1
 		return 1
-	else
-		return 0
 	end
 	end
+	return 0
 end
 end
 
 
 local math_sign = math.sign
 local math_sign = math.sign
@@ -96,8 +95,8 @@ function Bone:updateWorldTransformWith (x, y, rotation, scaleX, scaleY, shearX,
 	self.ashearY = shearY
 	self.ashearY = shearY
 	self.appliedValid = true
 	self.appliedValid = true
 
 
-	local sx = self.skeleton.scaleX;
-	local sy = self.skeleton.scaleY;
+	local sx = self.skeleton.scaleX
+	local sy = self.skeleton.scaleY
 
 
 	local parent = self.parent
 	local parent = self.parent
 	if parent == nil then
 	if parent == nil then
@@ -132,7 +131,7 @@ function Bone:updateWorldTransformWith (x, y, rotation, scaleX, scaleY, shearX,
 		self.b = pa * lb + pb * ld
 		self.b = pa * lb + pb * ld
 		self.c = pc * la + pd * lc
 		self.c = pc * la + pd * lc
 		self.d = pc * lb + pd * ld
 		self.d = pc * lb + pd * ld
-		return;
+		return
 	elseif transformMode == TransformMode.onlyTranslation then
 	elseif transformMode == TransformMode.onlyTranslation then
 		local rotationY = rotation + 90 + shearY
 		local rotationY = rotation + 90 + shearY
 		self.a = math_cos(math_rad(rotation + shearX)) * scaleX
 		self.a = math_cos(math_rad(rotation + shearX)) * scaleX
@@ -148,11 +147,11 @@ function Bone:updateWorldTransformWith (x, y, rotation, scaleX, scaleY, shearX,
 			pc = pc / self.skeleton.scaleY
 			pc = pc / self.skeleton.scaleY
 			pb = pc * s
 			pb = pc * s
 			pd = pa * s
 			pd = pa * s
-			prx = math_deg(math_atan2(pc, pa));
+			prx = math_deg(math_atan2(pc, pa))
 		else
 		else
-			pa = 0;
-			pc = 0;
-			prx = 90 - math_deg(math_atan2(pd, pb));
+			pa = 0
+			pc = 0
+			prx = 90 - math_deg(math_atan2(pd, pb))
 		end
 		end
 		local rx = rotation + shearX - prx
 		local rx = rotation + shearX - prx
 		local ry = rotation + shearY - prx + 90
 		local ry = rotation + shearY - prx + 90
@@ -181,10 +180,10 @@ function Bone:updateWorldTransformWith (x, y, rotation, scaleX, scaleY, shearX,
 		local r = math_pi / 2 + math_atan2(zc, za)
 		local r = math_pi / 2 + math_atan2(zc, za)
 		local zb = math_cos(r) * s
 		local zb = math_cos(r) * s
 		local zd = math_sin(r) * s
 		local zd = math_sin(r) * s
-		local la = math_cos(math_rad(shearX)) * scaleX;
-		local lb = math_cos(math_rad(90 + shearY)) * scaleY;
-		local lc = math_sin(math_rad(shearX)) * scaleX;
-		local ld = math_sin(math_rad(90 + shearY)) * scaleY;
+		local la = math_cos(math_rad(shearX)) * scaleX
+		local lb = math_cos(math_rad(90 + shearY)) * scaleY
+		local lc = math_sin(math_rad(shearX)) * scaleX
+		local ld = math_sin(math_rad(90 + shearY)) * scaleY
 		self.a = za * la + zb * lc
 		self.a = za * la + zb * lc
 		self.b = za * lb + zb * ld
 		self.b = za * lb + zb * ld
 		self.c = zc * la + zd * lc
 		self.c = zc * la + zd * lc

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

@@ -47,7 +47,8 @@ function BoneData.new (index, name, parent)
 		shearX = 0, shearY = 0,
 		shearX = 0, shearY = 0,
 		inheritRotation = true,
 		inheritRotation = true,
 		inheritScale = true,
 		inheritScale = true,
-		skinRequired = false
+		skinRequired = false,
+		color = nil
 	}
 	}
 
 
 	return self
 	return self

+ 0 - 61
spine-lua/spine-lua/BoundingBoxAttachment.lua

@@ -1,61 +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 AttachmentType = require "spine-lua.AttachmentType"
-
-local BoundingBoxAttachment = {}
-function BoundingBoxAttachment.new (name)
-	if not name then error("name cannot be nil", 2) end
-
-	local self = {
-		name = name,
-		type = AttachmentType.boundingbox,
-		vertices = {}
-	}
-
-	function self:computeWorldVertices (x, y, bone, worldVertices)
-		x = x + bone.worldX
-		y = y + bone.worldY
-		local m00 = bone.m00
-		local m01 = bone.m01
-		local m10 = bone.m10
-		local m11 = bone.m11
-		local vertices = self.vertices
-		local count = #vertices
-		for i = 1, count, 2 do
-			local px = vertices[i]
-			local py = vertices[i + 1]
-			worldVertices[i] = px * m00 + py * m01 + x
-			worldVertices[i + 1] = px * m10 + py * m11 + y
-		end
-	end
-
-	return self
-end
-return BoundingBoxAttachment

+ 10 - 10
spine-lua/spine-lua/IkConstraint.lua

@@ -102,12 +102,12 @@ function IkConstraint:apply1 (bone, targetX, targetY, compress, stretch, uniform
 		tx = targetX - bone.worldX
 		tx = targetX - bone.worldX
 		ty = targetY - bone.worldY
 		ty = targetY - bone.worldY
 	elseif bone.data.transformMode == TransformMode.noRotationOrReflection then
 	elseif bone.data.transformMode == TransformMode.noRotationOrReflection then
-		local s = math_abs(pa * pd - pb * pc) / (pa * pa + pc * pc);
-		local sa = pa / bone.skeleton.scaleX;
-		local sc = pc / bone.skeleton.scaleY;
-		pb = -sc * s * bone.skeleton.scaleX;
-		pd = sa * s * bone.skeleton.scaleY;
-		rotationIK = rotationIK + math_deg(math_atan2(sc, sa));
+		local s = math_abs(pa * pd - pb * pc) / (pa * pa + pc * pc)
+		local sa = pa / bone.skeleton.scaleX
+		local sc = pc / bone.skeleton.scaleY
+		pb = -sc * s * bone.skeleton.scaleX
+		pd = sa * s * bone.skeleton.scaleY
+		rotationIK = rotationIK + math_deg(math_atan2(sc, sa))
 
 
 
 
 		local x = targetX - p.worldX
 		local x = targetX - p.worldX
@@ -255,13 +255,13 @@ function IkConstraint:apply2 (parent, child, targetX, targetY, bendDir, stretch,
 		b = psy * l2
 		b = psy * l2
 		local aa = a * a
 		local aa = a * a
 		local bb = b * b
 		local bb = b * b
-		local ta = math_atan2(ty, tx);
+		local ta = math_atan2(ty, tx)
 		c = bb * l1 * l1 + aa * dd - aa * bb
 		c = bb * l1 * l1 + aa * dd - aa * bb
 		local c1 = -2 * bb * l1
 		local c1 = -2 * bb * l1
 		local c2 = bb - aa
 		local c2 = bb - aa
 		d = c1 * c1 - 4 * c2 * c
 		d = c1 * c1 - 4 * c2 * c
 		if d >= 0 then
 		if d >= 0 then
-			local q = math_sqrt(d);
+			local q = math_sqrt(d)
 			if (c1 < 0) then q = -q end
 			if (c1 < 0) then q = -q end
 			q = -(c1 + q) / 2
 			q = -(c1 + q) / 2
 			local r0 = q / c2
 			local r0 = q / c2
@@ -279,7 +279,7 @@ function IkConstraint:apply2 (parent, child, targetX, targetY, bendDir, stretch,
 			local minAngle = math_pi
 			local minAngle = math_pi
 			local minX = l1 - a
 			local minX = l1 - a
 			local minDist = minX * minX
 			local minDist = minX * minX
-			local minY = 0;
+			local minY = 0
 			local maxAngle = 0
 			local maxAngle = 0
 			local maxX = l1 + a
 			local maxX = l1 + a
 			local maxDist = maxX * maxX
 			local maxDist = maxX * maxX
@@ -328,7 +328,7 @@ function IkConstraint:apply2 (parent, child, targetX, targetY, bendDir, stretch,
 	elseif a2 < -180 then
 	elseif a2 < -180 then
 		a2 = a2 + 360
 		a2 = a2 + 360
 	end
 	end
-	child:updateWorldTransformWith(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY);
+	child:updateWorldTransformWith(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY)
 end
 end
 
 
 return IkConstraint
 return IkConstraint

+ 0 - 93
spine-lua/spine-lua/MeshAttachment.lua

@@ -1,93 +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 AttachmentType = require "spine-lua.AttachmentType"
-
-local MeshAttachment = {}
-function MeshAttachment.new (name)
-	if not name then error("name cannot be nil", 2) end
-
-	local self = {
-		name = name,
-		type = AttachmentType.mesh,
-		vertices = nil,
-		uvs = nil,
-		regionUVs = nil,
-		triangles = nil,
-		hullLength = 0,
-		r = 1, g = 1, b = 1, a = 1,
-		path = nil,
-		rendererObject = nil,
-		regionU = 0, regionV = 0, regionU2 = 1, regionV2 = 1, regionRotate = false,
-		regionOffsetX = 0, regionOffsetY = 0,
-		regionWidth = 0, regionHeight = 0,
-		regionOriginalWidth = 0, regionOriginalHeight = 0,
-		edges = nil,
-		width = 0, height = 0
-	}
-
-	function self:updateUVs ()
-		local width, height = self.regionU2 - self.regionU, self.regionV2 - self.regionV
-		local n = #self.regionUVs
-		if not self.uvs or #self.uvs ~= n then
-			self.uvs = {}
-		end
-		if self.regionRotate then
-			for i = 1, n, 2 do
-				self.uvs[i] = self.regionU + self.regionUVs[i + 1] * width
-				self.uvs[i + 1] = self.regionV + height - self.regionUVs[i] * height
-			end
-		else
-			for i = 1, n, 2 do
-				self.uvs[i] = self.regionU + self.regionUVs[i] * width
-				self.uvs[i + 1] = self.regionV + self.regionUVs[i + 1] * height
-			end
-		end
-	end
-
-	function self:computeWorldVertices (x, y, slot, worldVertices)
-		local bone = slot.bone
-x,y=slot.bone.skeleton.x,slot.bone.skeleton.y
-		x = x + bone.worldX
-		y = y + bone.worldY
-		local m00, m01, m10, m11 = bone.m00, bone.m01, bone.m10, bone.m11
-		local vertices = self.vertices
-		local verticesCount = #vertices
-		if slot.deform and #slot.deform == verticesCount then vertices = slot.deform end
-		for i = 1, verticesCount, 2 do
-			local vx = vertices[i]
-			local vy = vertices[i + 1]
-			worldVertices[i] = vx * m00 + vy * m01 + x
-			worldVertices[i + 1] = vx * m10 + vy * m11 + y
-		end
-	end
-
-	return self
-end
-return MeshAttachment

+ 15 - 15
spine-lua/spine-lua/PathConstraint.lua

@@ -99,7 +99,7 @@ function PathConstraint:update ()
 	local rotate = rotateMix > 0
 	local rotate = rotateMix > 0
 	if not translate and not rotate then return end
 	if not translate and not rotate then return end
 
 
-	local data = self.data;
+	local data = self.data
 	local percentSpacing = data.spacingMode == PathConstraintData.SpacingMode.percent
 	local percentSpacing = data.spacingMode == PathConstraintData.SpacingMode.percent
 	local rotateMode = data.rotateMode
 	local rotateMode = data.rotateMode
 	local tangents = rotateMode == PathConstraintData.RotateMode.tangent
 	local tangents = rotateMode == PathConstraintData.RotateMode.tangent
@@ -117,7 +117,7 @@ function PathConstraint:update ()
 		local i = 0
 		local i = 0
 		local n = spacesCount - 1
 		local n = spacesCount - 1
 		while i < n do
 		while i < n do
-			local bone = bones[i + 1];
+			local bone = bones[i + 1]
 			local setupLength = bone.data.length
 			local setupLength = bone.data.length
 			if setupLength < PathConstraint.epsilon then
 			if setupLength < PathConstraint.epsilon then
 				if scale then lengths[i + 1] = 0 end
 				if scale then lengths[i + 1] = 0 end
@@ -157,12 +157,12 @@ function PathConstraint:update ()
 	local boneX = positions[1]
 	local boneX = positions[1]
 	local boneY = positions[2]
 	local boneY = positions[2]
 	local offsetRotation = data.offsetRotation
 	local offsetRotation = data.offsetRotation
-	local tip = false;
+	local tip = false
 	if offsetRotation == 0 then
 	if offsetRotation == 0 then
 			tip = rotateMode == PathConstraintData.RotateMode.chain
 			tip = rotateMode == PathConstraintData.RotateMode.chain
 	else
 	else
-		tip = false;
-		local p = self.target.bone;
+		tip = false
+		local p = self.target.bone
 		if p.a * p.d - p.b * p.c > 0 then
 		if p.a * p.d - p.b * p.c > 0 then
 			offsetRotation = offsetRotation * utils.degRad
 			offsetRotation = offsetRotation * utils.degRad
 		else
 		else
@@ -210,8 +210,8 @@ function PathConstraint:update ()
 				cos = math_cos(r)
 				cos = math_cos(r)
 				sin = math_sin(r)
 				sin = math_sin(r)
 				local length = bone.data.length
 				local length = bone.data.length
-				boneX = boneX + (length * (cos * a - sin * c) - dx) * rotateMix;
-				boneY = boneY + (length * (sin * a + cos * c) - dy) * rotateMix;
+				boneX = boneX + (length * (cos * a - sin * c) - dx) * rotateMix
+				boneY = boneY + (length * (sin * a + cos * c) - dy) * rotateMix
 			else
 			else
 				r = r + offsetRotation
 				r = r + offsetRotation
 			end
 			end
@@ -249,7 +249,7 @@ function PathConstraint:computeWorldPositions (path, spacesCount, tangents, perc
 	if not path.constantSpeed then
 	if not path.constantSpeed then
 		local lengths = path.lengths
 		local lengths = path.lengths
 		if closed then curveCount = curveCount - 1 else curveCount = curveCount - 2 end
 		if closed then curveCount = curveCount - 1 else curveCount = curveCount - 2 end
-		local pathLength = lengths[curveCount + 1];
+		local pathLength = lengths[curveCount + 1]
 		if percentPosition then position = position * pathLength end
 		if percentPosition then position = position * pathLength end
 		if percentSpacing then
 		if percentSpacing then
 			i = 1
 			i = 1
@@ -258,12 +258,12 @@ function PathConstraint:computeWorldPositions (path, spacesCount, tangents, perc
 				i = i + 1
 				i = i + 1
 			end
 			end
 		end
 		end
-		world = utils.setArraySize(self.world, 8);
+		world = utils.setArraySize(self.world, 8)
 		i = 0
 		i = 0
 		local o = 0
 		local o = 0
 		local curve = 0
 		local curve = 0
 		while i < spacesCount do
 		while i < spacesCount do
-			local space = spaces[i + 1];
+			local space = spaces[i + 1]
 			position = position + space
 			position = position + space
 			local p = position
 			local p = position
 
 
@@ -331,14 +331,14 @@ function PathConstraint:computeWorldPositions (path, spacesCount, tangents, perc
 		world[verticesLength - 1 + 1] = world[1 + 1]
 		world[verticesLength - 1 + 1] = world[1 + 1]
 	else
 	else
 		curveCount = curveCount - 1
 		curveCount = curveCount - 1
-		verticesLength = verticesLength - 4;
+		verticesLength = verticesLength - 4
 		world = utils.setArraySize(self.world, verticesLength)
 		world = utils.setArraySize(self.world, verticesLength)
 		path:computeWorldVertices(target, 2, verticesLength, world, 0, 2)
 		path:computeWorldVertices(target, 2, verticesLength, world, 0, 2)
 	end
 	end
 
 
 	-- Curve lengths.
 	-- Curve lengths.
 	local curves = utils.setArraySize(self.curves, curveCount)
 	local curves = utils.setArraySize(self.curves, curveCount)
-	local pathLength = 0;
+	local pathLength = 0
 	local x1 = world[0 + 1]
 	local x1 = world[0 + 1]
 	local y1 = world[1 + 1]
 	local y1 = world[1 + 1]
 	local cx1 = 0
 	local cx1 = 0
@@ -392,7 +392,7 @@ function PathConstraint:computeWorldPositions (path, spacesCount, tangents, perc
 	if percentPosition then
 	if percentPosition then
 		position = position * pathLength
 		position = position * pathLength
 	else
 	else
-		position = position * pathLength / path.lengths[curveCount];
+		position = position * pathLength / path.lengths[curveCount]
 	end
 	end
 	if percentSpacing then
 	if percentSpacing then
 		i = 1
 		i = 1
@@ -496,7 +496,7 @@ function PathConstraint:computeWorldPositions (path, spacesCount, tangents, perc
 						local prev = segments[segment - 1 + 1]
 						local prev = segments[segment - 1 + 1]
 						p = segment + (p - prev) / (length - prev)
 						p = segment + (p - prev) / (length - prev)
 					end
 					end
-					break;
+					break
 				end
 				end
 				segment = segment + 1
 				segment = segment + 1
 			end
 			end
@@ -536,7 +536,7 @@ function PathConstraint:addCurvePosition(p, x1, y1, cx1, cy1, cx2, cy2, x2, y2,
 		out[o + 1] = x1
 		out[o + 1] = x1
 		out[o + 2] = y1
 		out[o + 2] = y1
 		out[o + 3] = math_atan2(cy1 - y1, cx1 - x1)
 		out[o + 3] = math_atan2(cy1 - y1, cx1 - x1)
-		return;
+		return
 	end
 	end
 	local tt = p * p
 	local tt = p * p
 	local ttt = tt * p
 	local ttt = tt * p

+ 0 - 100
spine-lua/spine-lua/RegionAttachment.lua

@@ -1,100 +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 AttachmentType = require "spine-lua.AttachmentType"
-
-local RegionAttachment = {}
-function RegionAttachment.new (name)
-	if not name then error("name cannot be nil", 2) end
-
-	local self = {
-		name = name,
-		type = AttachmentType.region,
-		x = 0, y = 0,
-		rotation = 0,
-		scaleX = 1, scaleY = 1,
-		width = 0, height = 0,
-		offset = {},
-		uvs = {},
-		r = 1, g = 1, b = 1, a = 1,
-		path = nil,
-		rendererObject = nil,
-		regionOffsetX = 0, regionOffsetY = 0,
-		regionWidth = 0, regionHeight = 0,
-		regionOriginalWidth = 0, regionOriginalHeight = 0
-	}
-
-	function self:updateOffset ()
-		local regionScaleX = self.width / self.regionOriginalWidth * self.scaleX
-		local regionScaleY = self.height / self.regionOriginalHeight * self.scaleY
-		local localX = -self.width / 2 * self.scaleX + self.regionOffsetX * regionScaleX
-		local localY = -self.height / 2 * self.scaleY + self.regionOffsetY * regionScaleY
-		local localX2 = localX + self.regionWidth * regionScaleX
-		local localY2 = localY + self.regionHeight * regionScaleY
-		local radians = self.rotation * math.pi / 180
-		local cos = math.cos(radians)
-		local sin = math.sin(radians)
-		local localXCos = localX * cos + self.x
-		local localXSin = localX * sin
-		local localYCos = localY * cos + self.y
-		local localYSin = localY * sin
-		local localX2Cos = localX2 * cos + self.x
-		local localX2Sin = localX2 * sin
-		local localY2Cos = localY2 * cos + self.y
-		local localY2Sin = localY2 * sin
-		local offset = self.offset
-		offset[0] = localXCos - localYSin -- X1
-		offset[1] = localYCos + localXSin -- Y1
-		offset[2] = localXCos - localY2Sin -- X2
-		offset[3] = localY2Cos + localXSin -- Y2
-		offset[4] = localX2Cos - localY2Sin -- X3
-		offset[5] = localY2Cos + localX2Sin -- Y3
-		offset[6] = localX2Cos - localYSin -- X4
-		offset[7] = localYCos + localX2Sin -- Y4
-	end
-
-	function self:computeWorldVertices (x, y, bone, worldVertices)
-		x = x + bone.worldX
-		y = y + bone.worldY
-		local m00, m01, m10, m11 = bone.m00, bone.m01, bone.m10, bone.m11
-		local offset = self.offset
-		local vertices = self.vertices;
-		vertices[0] = offset[0] * m00 + offset[1] * m01 + x
-		vertices[1] = offset[0] * m10 + offset[1] * m11 + y
-		vertices[2] = offset[2] * m00 + offset[3] * m01 + x
-		vertices[3] = offset[2] * m10 + offset[3] * m11 + y
-		vertices[4] = offset[4] * m00 + offset[5] * m01 + x
-		vertices[5] = offset[4] * m10 + offset[5] * m11 + y
-		vertices[6] = offset[6] * m00 + offset[7] * m01 + x
-		vertices[7] = offset[6] * m10 + offset[7] * m11 + y
-	end
-
-	return self
-end
-return RegionAttachment

+ 2 - 2
spine-lua/spine-lua/Skeleton.lua

@@ -460,7 +460,7 @@ function Skeleton:setSkinByReference(newSkin)
 end
 end
 
 
 function Skeleton:getAttachment (slotName, attachmentName)
 function Skeleton:getAttachment (slotName, attachmentName)
-	return self:getAttachmentByIndex(self.data.slotNameIndices[slotName], attachmentName)
+	return self:getAttachmentByIndex(self.data.nameToSlot[slotName].index, attachmentName)
 end
 end
 
 
 function Skeleton:getAttachmentByIndex (slotIndex, attachmentName)
 function Skeleton:getAttachmentByIndex (slotIndex, attachmentName)
@@ -520,7 +520,7 @@ end
 function Skeleton:getBounds(offset, size)
 function Skeleton:getBounds(offset, size)
 	if not offset then error("offset cannot be null.", 2) end
 	if not offset then error("offset cannot be null.", 2) end
 	if not size then error("size cannot be null.", 2) end
 	if not size then error("size cannot be null.", 2) end
-	local drawOrder = self.drawOrder;
+	local drawOrder = self.drawOrder
 	local minX = 99999999
 	local minX = 99999999
 	local minY = 99999999
 	local minY = 99999999
 	local maxX = -99999999
 	local maxX = -99999999

+ 4 - 4
spine-lua/spine-lua/SkeletonClipping.lua

@@ -118,7 +118,7 @@ function SkeletonClipping:clipTriangles(vertices, uvs, triangles, trianglesLengt
 		local u2 = uvs[vertexOffset]
 		local u2 = uvs[vertexOffset]
 		local v2 = uvs[vertexOffset + 1]
 		local v2 = uvs[vertexOffset + 1]
 
 
-		vertexOffset = (triangles[i + 2] - 1) * 2 + 1;
+		vertexOffset = (triangles[i + 2] - 1) * 2 + 1
 		local x3 = vertices[vertexOffset]
 		local x3 = vertices[vertexOffset]
 		local y3 = vertices[vertexOffset + 1]
 		local y3 = vertices[vertexOffset + 1]
 		local u3 = uvs[vertexOffset]
 		local u3 = uvs[vertexOffset]
@@ -135,7 +135,7 @@ function SkeletonClipping:clipTriangles(vertices, uvs, triangles, trianglesLengt
 					local d1 = x3 - x2
 					local d1 = x3 - x2
 					local d2 = x1 - x3
 					local d2 = x1 - x3
 					local d4 = y3 - y1
 					local d4 = y3 - y1
-					local d = 1 / (d0 * d2 + d1 * (y1 - y3));
+					local d = 1 / (d0 * d2 + d1 * (y1 - y3))
 
 
 					local clipOutputCount = clipOutputLength / 2
 					local clipOutputCount = clipOutputLength / 2
 					local clipOutputItems = clipOutput
 					local clipOutputItems = clipOutput
@@ -193,7 +193,7 @@ function SkeletonClipping:clipTriangles(vertices, uvs, triangles, trianglesLengt
 				clippedTrianglesItems[s] = index
 				clippedTrianglesItems[s] = index
 				clippedTrianglesItems[s + 1] = index + 1
 				clippedTrianglesItems[s + 1] = index + 1
 				clippedTrianglesItems[s + 2] = index + 2
 				clippedTrianglesItems[s + 2] = index + 2
-				index = index + 3;
+				index = index + 3
 				break
 				break
 			end
 			end
 			p = p + 1
 			p = p + 1
@@ -246,7 +246,7 @@ function SkeletonClipping:clip(x1, y1, x2, y2, x3, y3, clippingArea, output)
 			local inputX2 = inputVertices[ii + 2]
 			local inputX2 = inputVertices[ii + 2]
 			local inputY2 = inputVertices[ii + 3]
 			local inputY2 = inputVertices[ii + 3]
 			local side2 = deltaX * (inputY2 - edgeY2) - deltaY * (inputX2 - edgeX2) > 0
 			local side2 = deltaX * (inputY2 - edgeY2) - deltaY * (inputX2 - edgeX2) > 0
-			local continue = false;
+			local continue = false
 			if deltaX * (inputY - edgeY2) - deltaY * (inputX - edgeX2) > 0 then
 			if deltaX * (inputY - edgeY2) - deltaY * (inputX - edgeX2) > 0 then
 				if side2 then -- v1 inside, v2 inside
 				if side2 then -- v1 inside, v2 inside
 					table_insert(output, inputX2)
 					table_insert(output, inputX2)

+ 3 - 19
spine-lua/spine-lua/SkeletonData.lua

@@ -37,6 +37,7 @@ function SkeletonData.new ()
 		name,
 		name,
 		bones = {},
 		bones = {},
 		slots = {},
 		slots = {},
+		nameToSlot = {},
 		skins = {},
 		skins = {},
 		defaultSkin = nil,
 		defaultSkin = nil,
 		events = {},
 		events = {},
@@ -45,8 +46,7 @@ function SkeletonData.new ()
 		transformConstraints = {},
 		transformConstraints = {},
 		pathConstraints = {},
 		pathConstraints = {},
 		x, y, width, height,
 		x, y, width, height,
-		version, hash, imagesPath,
-		slotNameIndices = {}
+		version, hash, imagesPath
 	}
 	}
 	setmetatable(self, SkeletonData)
 	setmetatable(self, SkeletonData)
 
 
@@ -71,15 +71,7 @@ end
 
 
 function SkeletonData:findSlot (slotName)
 function SkeletonData:findSlot (slotName)
 	if not slotName then error("slotName cannot be nil.", 2) end
 	if not slotName then error("slotName cannot be nil.", 2) end
-	for i,slot in ipairs(self.slots) do
-		if slot.name == slotName then return slot end
-	end
-	return nil
-end
-
-function SkeletonData:findSlotIndex (slotName)
-	if not slotName then error("slotName cannot be nil.", 2) end
-	return self.slotNameIndices[slotName] or -1
+	return self.nameToSlot[slotName]
 end
 end
 
 
 function SkeletonData:findSkin (skinName)
 function SkeletonData:findSkin (skinName)
@@ -130,12 +122,4 @@ function SkeletonData:findPathConstraint (constraintName)
 	return nil
 	return nil
 end
 end
 
 
-function SkeletonData:findPathConstraintIndex (constraintName)
-	if not constraintName then error("constraintName cannot be nil.", 2) end
-	for i,constraint in ipairs(self.pathConstraints) do
-		if constraint.name == constraintName then return i end
-	end
-	return -1
-end
-
 return SkeletonData
 return SkeletonData

+ 537 - 272
spine-lua/spine-lua/SkeletonJson.lua

@@ -65,6 +65,8 @@ function SkeletonJson.new (attachmentLoader)
 	local readAttachment
 	local readAttachment
 	local readAnimation
 	local readAnimation
 	local readCurve
 	local readCurve
+	local readTimeline1
+	local readTimeline2
 	local getArray
 	local getArray
 
 
 	local getValue = function (map, name, default)
 	local getValue = function (map, name, default)
@@ -83,9 +85,6 @@ function SkeletonJson.new (attachmentLoader)
 		if skeletonMap then
 		if skeletonMap then
 			skeletonData.hash = skeletonMap["hash"]
 			skeletonData.hash = skeletonMap["hash"]
 			skeletonData.version = skeletonMap["spine"]
 			skeletonData.version = skeletonMap["spine"]
-			if ("3.8.75" == skeletonData.version) then
-				error("Unsupported skeleton data, please export with a newer version of Spine.")
-			end
 			skeletonData.x = skeletonMap["x"]
 			skeletonData.x = skeletonMap["x"]
 			skeletonData.y = skeletonMap["y"]
 			skeletonData.y = skeletonMap["y"]
 			skeletonData.width = skeletonMap["width"]
 			skeletonData.width = skeletonMap["width"]
@@ -105,17 +104,25 @@ function SkeletonJson.new (attachmentLoader)
 				if not parent then error("Parent bone not found: " .. parentName) end
 				if not parent then error("Parent bone not found: " .. parentName) end
 			end
 			end
 			local data = BoneData.new(i, boneName, parent)
 			local data = BoneData.new(i, boneName, parent)
-			data.length = getValue(boneMap, "length", 0) * scale;
-			data.x = getValue(boneMap, "x", 0) * scale;
-			data.y = getValue(boneMap, "y", 0) * scale;
-			data.rotation = getValue(boneMap, "rotation", 0);
-			data.scaleX = getValue(boneMap, "scaleX", 1);
-			data.scaleY = getValue(boneMap, "scaleY", 1);
-			data.shearX = getValue(boneMap, "shearX", 0);
-			data.shearY = getValue(boneMap, "shearY", 0);
+			data.length = getValue(boneMap, "length", 0) * scale
+			data.x = getValue(boneMap, "x", 0) * scale
+			data.y = getValue(boneMap, "y", 0) * scale
+			data.rotation = getValue(boneMap, "rotation", 0)
+			data.scaleX = getValue(boneMap, "scaleX", 1)
+			data.scaleY = getValue(boneMap, "scaleY", 1)
+			data.shearX = getValue(boneMap, "shearX", 0)
+			data.shearY = getValue(boneMap, "shearY", 0)
 			data.transformMode = TransformMode[getValue(boneMap, "transform", "normal")]
 			data.transformMode = TransformMode[getValue(boneMap, "transform", "normal")]
 			data.skinRequired = getValue(boneMap, "skin", false)
 			data.skinRequired = getValue(boneMap, "skin", false)
 
 
+			local color = boneMap["color"]
+			if color then
+				data.color = Color.newWith(tonumber(color:sub(1, 2), 16) / 255,
+					tonumber(color:sub(3, 4), 16) / 255,
+					tonumber(color:sub(5, 6), 16) / 255,
+					tonumber(color:sub(7, 8), 16) / 255)
+			end
+
 			table_insert(skeletonData.bones, data)
 			table_insert(skeletonData.bones, data)
 		end
 		end
 
 
@@ -138,18 +145,17 @@ function SkeletonJson.new (attachmentLoader)
 
 
 				local dark = slotMap["dark"]
 				local dark = slotMap["dark"]
 				if dark then
 				if dark then
-					data.darkColor = Color.newWith(1, 1, 1, 1)
-					data.darkColor:set(tonumber(dark:sub(1, 2), 16) / 255,
+					data.darkColor = Color.newWith(
+						tonumber(dark:sub(1, 2), 16) / 255,
 						tonumber(dark:sub(3, 4), 16) / 255,
 						tonumber(dark:sub(3, 4), 16) / 255,
-						tonumber(dark:sub(5, 6), 16) / 255,
-						0)
+						tonumber(dark:sub(5, 6), 16) / 255, 0)
 				end
 				end
 
 
 				data.attachmentName = getValue(slotMap, "attachment", nil)
 				data.attachmentName = getValue(slotMap, "attachment", nil)
 				data.blendMode = BlendMode[getValue(slotMap, "blend", "normal")]
 				data.blendMode = BlendMode[getValue(slotMap, "blend", "normal")]
 
 
 				table_insert(skeletonData.slots, data)
 				table_insert(skeletonData.slots, data)
-				skeletonData.slotNameIndices[data.name] = #skeletonData.slots
+				skeletonData.nameToSlot[data.name] = data
 			end
 			end
 		end
 		end
 
 
@@ -204,17 +210,19 @@ function SkeletonJson.new (attachmentLoader)
 
 
 				data.local_ = getValue(constraintMap, "local", false)
 				data.local_ = getValue(constraintMap, "local", false)
 				data.relative = getValue(constraintMap, "relative", false)
 				data.relative = getValue(constraintMap, "relative", false)
-				data.offsetRotation = getValue(constraintMap, "rotation", 0);
-				data.offsetX = getValue(constraintMap, "x", 0) * scale;
-				data.offsetY = getValue(constraintMap, "y", 0) * scale;
-				data.offsetScaleX = getValue(constraintMap, "scaleX", 0);
-				data.offsetScaleY = getValue(constraintMap, "scaleY", 0);
-				data.offsetShearY = getValue(constraintMap, "shearY", 0);
-
-				data.rotateMix = getValue(constraintMap, "rotateMix", 1);
-				data.translateMix = getValue(constraintMap, "translateMix", 1);
-				data.scaleMix = getValue(constraintMap, "scaleMix", 1);
-				data.shearMix = getValue(constraintMap, "shearMix", 1);
+				data.offsetRotation = getValue(constraintMap, "rotation", 0)
+				data.offsetX = getValue(constraintMap, "x", 0) * scale
+				data.offsetY = getValue(constraintMap, "y", 0) * scale
+				data.offsetScaleX = getValue(constraintMap, "scaleX", 0)
+				data.offsetScaleY = getValue(constraintMap, "scaleY", 0)
+				data.offsetShearY = getValue(constraintMap, "shearY", 0)
+
+				data.mixRotate = getValue(constraintMap, "rotateMix", 1)
+				data.mixX = getValue(constraintMap, "mixX", 1)
+				data.mixY = getValue(constraintMap, "mixY", data.mixX)
+				data.mixScaleX = getValue(constraintMap, "mixScaleX", 1)
+				data.mixScaleY = getValue(constraintMap, "mixScaleY", data.mixScaleX)
+				data.mixShearY = getValue(constraintMap, "mixShearY", 1)
 
 
 				table_insert(skeletonData.transformConstraints, data)
 				table_insert(skeletonData.transformConstraints, data)
 			end
 			end
@@ -222,8 +230,8 @@ function SkeletonJson.new (attachmentLoader)
 
 
 		-- Path constraints
 		-- Path constraints
 		if root["path"] then
 		if root["path"] then
-			for _,constraintMap in ipairs(root.path) do
-				local data = PathConstraintData.new(constraintMap.name);
+			for _,constraintMap in ipairs(root["path"]) do
+				local data = PathConstraintData.new(constraintMap.name)
 				data.order = getValue(constraintMap, "order", 0)
 				data.order = getValue(constraintMap, "order", 0)
 				data.skinRequired = getValue(constraintMap, "skin", false)
 				data.skinRequired = getValue(constraintMap, "skin", false)
 
 
@@ -233,20 +241,21 @@ function SkeletonJson.new (attachmentLoader)
 					table_insert(data.bones, bone)
 					table_insert(data.bones, bone)
 				end
 				end
 
 
-				local targetName = constraintMap.target;
+				local targetName = constraintMap.target
 				data.target = skeletonData:findSlot(targetName)
 				data.target = skeletonData:findSlot(targetName)
 				if data.target == nil then error("Path target slot not found: " .. targetName, 2) end
 				if data.target == nil then error("Path target slot not found: " .. targetName, 2) end
 
 
 				data.positionMode = PathConstraintData.PositionMode[getValue(constraintMap, "positionMode", "percent"):lower()]
 				data.positionMode = PathConstraintData.PositionMode[getValue(constraintMap, "positionMode", "percent"):lower()]
 				data.spacingMode = PathConstraintData.SpacingMode[getValue(constraintMap, "spacingMode", "length"):lower()]
 				data.spacingMode = PathConstraintData.SpacingMode[getValue(constraintMap, "spacingMode", "length"):lower()]
 				data.rotateMode = PathConstraintData.RotateMode[getValue(constraintMap, "rotateMode", "tangent"):lower()]
 				data.rotateMode = PathConstraintData.RotateMode[getValue(constraintMap, "rotateMode", "tangent"):lower()]
-				data.offsetRotation = getValue(constraintMap, "rotation", 0);
-				data.position = getValue(constraintMap, "position", 0);
+				data.offsetRotation = getValue(constraintMap, "rotation", 0)
+				data.position = getValue(constraintMap, "position", 0)
 				if data.positionMode == PathConstraintData.PositionMode.fixed then data.position = data.position * scale end
 				if data.positionMode == PathConstraintData.PositionMode.fixed then data.position = data.position * scale end
-				data.spacing = getValue(constraintMap, "spacing", 0);
+				data.spacing = getValue(constraintMap, "spacing", 0)
 				if data.spacingMode == PathConstraintData.SpacingMode.length or data.spacingMode == PathConstraintData.SpacingMode.fixed then data.spacing = data.spacing * scale end
 				if data.spacingMode == PathConstraintData.SpacingMode.length or data.spacingMode == PathConstraintData.SpacingMode.fixed then data.spacing = data.spacing * scale end
-				data.rotateMix = getValue(constraintMap, "rotateMix", 1);
-				data.translateMix = getValue(constraintMap, "translateMix", 1);
+				data.mixRotate = getValue(constraintMap, "mixRotate", 1)
+				data.mixX = getValue(constraintMap, "mixX", 1)
+				data.mixY = getValue(constraintMap, "mixY", data.mixX)
 
 
 				table_insert(skeletonData.pathConstraints, data)
 				table_insert(skeletonData.pathConstraints, data)
 			end
 			end
@@ -290,7 +299,7 @@ function SkeletonJson.new (attachmentLoader)
 				end
 				end
 				
 				
 				for slotName,slotMap in pairs(skinMap.attachments) do
 				for slotName,slotMap in pairs(skinMap.attachments) do
-					local slotIndex = skeletonData.slotNameIndices[slotName]
+					local slotIndex = skeletonData:findSlot(slotName).index
 					for attachmentName,attachmentMap in pairs(slotMap) do
 					for attachmentName,attachmentMap in pairs(slotMap) do
 						local attachment = readAttachment(attachmentMap, skin, slotIndex, attachmentName, skeletonData)
 						local attachment = readAttachment(attachmentMap, skin, slotIndex, attachmentName, skeletonData)
 						if attachment then
 						if attachment then
@@ -360,11 +369,11 @@ function SkeletonJson.new (attachmentLoader)
 			region.path = path
 			region.path = path
 			region.x = getValue(map, "x", 0) * scale
 			region.x = getValue(map, "x", 0) * scale
 			region.y = getValue(map, "y", 0) * scale
 			region.y = getValue(map, "y", 0) * scale
-			region.scaleX = getValue(map, "scaleX", 1);
-			region.scaleY = getValue(map, "scaleY", 1);
-			region.rotation = getValue(map, "rotation", 0);
-			region.width = map.width * scale;
-			region.height = map.height * scale;
+			region.scaleX = getValue(map, "scaleX", 1)
+			region.scaleY = getValue(map, "scaleY", 1)
+			region.rotation = getValue(map, "rotation", 0)
+			region.width = map.width * scale
+			region.height = map.height * scale
 
 
 			local color = map["color"]
 			local color = map["color"]
 			if color then
 			if color then
@@ -453,7 +462,7 @@ function SkeletonJson.new (attachmentLoader)
 					tonumber(color:sub(5, 6), 16) / 255,
 					tonumber(color:sub(5, 6), 16) / 255,
 					tonumber(color:sub(7, 8), 16) / 255)
 					tonumber(color:sub(7, 8), 16) / 255)
 			end
 			end
-			return path;
+			return path
 
 
 		elseif type == AttachmentType.point then
 		elseif type == AttachmentType.point then
 			local point = self.attachmentLoader:newPointAttachment(skin, name)
 			local point = self.attachmentLoader:newPointAttachment(skin, name)
@@ -536,124 +545,239 @@ function SkeletonJson.new (attachmentLoader)
 
 
 	readAnimation = function (map, name, skeletonData)
 	readAnimation = function (map, name, skeletonData)
 		local timelines = {}
 		local timelines = {}
-		local duration = 0
 		local scale = self.scale
 		local scale = self.scale
 
 
-		-- Slot timelines
+		-- Slot timelines.
 		local slotsMap = map["slots"]
 		local slotsMap = map["slots"]
 		if slotsMap then
 		if slotsMap then
-			for slotName,timelineMap in pairs(slotsMap) do
-				local slotIndex = skeletonData.slotNameIndices[slotName]
-
-				for timelineName,values in pairs(timelineMap) do
-					if timelineName == "color" then
-						local timeline = Animation.ColorTimeline.new(#values)
-						timeline.slotIndex = slotIndex
-
-						local frameIndex = 0
-						for _,valueMap in ipairs(values) do
-							local color = valueMap["color"]
-							timeline:setFrame(
-								frameIndex, getValue(valueMap, "time", 0),
-								tonumber(color:sub(1, 2), 16) / 255,
-								tonumber(color:sub(3, 4), 16) / 255,
-								tonumber(color:sub(5, 6), 16) / 255,
-								tonumber(color:sub(7, 8), 16) / 255
-							)
-							readCurve(valueMap, timeline, frameIndex)
-							frameIndex = frameIndex + 1
+			for slotName,slotMap in pairs(slotsMap) do
+				local slotIndex = skeletonData:findSlot(slotName).index
+				for timelineName,timelineMap in pairs(slotMap) do
+					if not timelineMap then
+					elseif timelineName == "attachment" then
+						local timeline = Animation.AttachmentTimeline.new(#timelineMap, slotIndex)
+						for i,keyMap in ipairs(timelineMap) do
+							timeline:setFrame(i + 1, getValue(keyMap, "time", 0), keyMap["name"])
 						end
 						end
 						table_insert(timelines, timeline)
 						table_insert(timelines, timeline)
-						duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.ColorTimeline.ENTRIES])
-					elseif timelineName == "twoColor" then
-						local timeline = Animation.TwoColorTimeline.new(#values)
-						timeline.slotIndex = slotIndex
-
-						local frameIndex = 0
-						for _,valueMap in ipairs(values) do
-							local light = valueMap["light"]
-							local dark = valueMap["dark"]
-							timeline:setFrame(
-								frameIndex, getValue(valueMap, "time", 0),
-								tonumber(light:sub(1, 2), 16) / 255,
-								tonumber(light:sub(3, 4), 16) / 255,
-								tonumber(light:sub(5, 6), 16) / 255,
-								tonumber(light:sub(7, 8), 16) / 255,
-								tonumber(dark:sub(1, 2), 16) / 255,
-								tonumber(dark:sub(3, 4), 16) / 255,
-								tonumber(dark:sub(5, 6), 16) / 255
-							)
-							readCurve(valueMap, timeline, frameIndex)
-							frameIndex = frameIndex + 1
+					elseif timelineName == "rgba" then
+						local timeline = Animation.RGBATimeline.new(#timelineMap, #timelineMap * 4, slotIndex)
+						local keyMap = timelineMap[1]
+						local time = getValue(keyMap, "time", 0)
+						local color = keyMap["color"]
+						local r = tonumber(color:sub(1, 2), 16) / 255
+						local g = tonumber(color:sub(3, 4), 16) / 255
+						local b = tonumber(color:sub(5, 6), 16) / 255
+						local a = tonumber(color:sub(7, 8), 16) / 255
+						local bezier = 0
+						for i,keyMap in ipairs(timelineMap) do
+							local frame = i - 1
+							timeline:setFrame(frame, time, r, g, b, a)
+							local nextMap = timelineMap[i + 1]
+							if not nextMap then
+								timeline:shrink(bezier)
+								break
+							end
+							local time2 = getValue(nextMap, "time", 0)
+							color = nextMap["color"]
+							local nr = tonumber(color:sub(1, 2), 16) / 255
+							local ng = tonumber(color:sub(3, 4), 16) / 255
+							local nb = tonumber(color:sub(5, 6), 16) / 255
+							local na = tonumber(color:sub(7, 8), 16) / 255
+							local curve = keyMap.curve
+							if curve then
+								bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1)
+								bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1)
+								bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1)
+								bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, a, na, 1)
+							end
+							time = time2
+							r = nr
+							g = ng
+							b = nb
+							a = na
 						end
 						end
 						table_insert(timelines, timeline)
 						table_insert(timelines, timeline)
-						duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.TwoColorTimeline.ENTRIES])
-					elseif timelineName == "attachment" then
-						local timeline = Animation.AttachmentTimeline.new(#values)
-						timeline.slotIndex = slotIndex
-
-						local frameIndex = 0
-						for _,valueMap in ipairs(values) do
-							local attachmentName = valueMap["name"]
-							timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), attachmentName)
-							frameIndex = frameIndex + 1
+					elseif timelineName == "rgb" then
+						local timeline = Animation.RGBTimeline.new(#timelineMap, #timelineMap * 3, slotIndex)
+						local keyMap = timelineMap[1]
+						local time = getValue(keyMap, "time", 0)
+						local color = keyMap["color"]
+						local r = tonumber(color:sub(1, 2), 16) / 255
+						local g = tonumber(color:sub(3, 4), 16) / 255
+						local b = tonumber(color:sub(5, 6), 16) / 255
+						local bezier = 0
+						for i,keyMap in ipairs(timelineMap) do
+							local frame = i - 1
+							timeline:setFrame(frame, time, r, g, b)
+							local nextMap = timelineMap[i + 1]
+							if not nextMap then
+								timeline:shrink(bezier)
+								break
+							end
+							local time2 = getValue(nextMap, "time", 0)
+							color = nextMap["color"]
+							local nr = tonumber(color:sub(1, 2), 16) / 255
+							local ng = tonumber(color:sub(3, 4), 16) / 255
+							local nb = tonumber(color:sub(5, 6), 16) / 255
+							local curve = keyMap.curve
+							if curve then
+								bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1)
+								bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1)
+								bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1)
+							end
+							time = time2
+							r = nr
+							g = ng
+							b = nb
+						end
+						table_insert(timelines, timeline)
+					elseif timelineName == "alpha" then
+						table_insert(timelines, readTimeline1(timelineMap, Animation.AlphaTimeline.new(#timelineMap, #timelineMap, slotIndex), 0, 1))
+					elseif timelineName == "rgba2" then
+						local timeline = Animation.RGBA2Timeline.new(#timelineMap, #timelineMap * 7, slotIndex)
+						local keyMap = timelineMap[1]
+						local time = getValue(keyMap, "time", 0)
+						local color = keyMap["light"]
+						local r = tonumber(color:sub(1, 2), 16) / 255
+						local g = tonumber(color:sub(3, 4), 16) / 255
+						local b = tonumber(color:sub(5, 6), 16) / 255
+						local a = tonumber(color:sub(7, 8), 16) / 255
+						color = keyMap["dark"]
+						local r2 = tonumber(color:sub(1, 2), 16) / 255
+						local g2 = tonumber(color:sub(3, 4), 16) / 255
+						local b2 = tonumber(color:sub(5, 6), 16) / 255
+						local bezier = 0
+						for i,keyMap in ipairs(timelineMap) do
+							local frame = i - 1
+							timeline:setFrame(frame, time, r, g, b, a, r2, g2, b2)
+							local nextMap = timelineMap[i + 1]
+							if not nextMap then
+								timeline:shrink(bezier)
+								break
+							end
+							local time2 = getValue(nextMap, "time", 0)
+							color = nextMap["light"]
+							local nr = tonumber(color:sub(1, 2), 16) / 255
+							local ng = tonumber(color:sub(3, 4), 16) / 255
+							local nb = tonumber(color:sub(5, 6), 16) / 255
+							local na = tonumber(color:sub(7, 8), 16) / 255
+							color = nextMap["dark"]
+							local nr2 = tonumber(color:sub(1, 2), 16) / 255
+							local ng2 = tonumber(color:sub(3, 4), 16) / 255
+							local nb2 = tonumber(color:sub(5, 6), 16) / 255
+							local curve = keyMap.curve
+							if curve then
+								bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1)
+								bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1)
+								bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1)
+								bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, a, na, 1)
+								bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, r2, nr2, 1)
+								bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, g2, ng2, 1)
+								bezier = readCurve(curve, timeline, bezier, frame, 6, time, time2, b2, nb2, 1)
+							end
+							time = time2
+							r = nr
+							g = ng
+							b = nb
+							a = na
+							r2 = nr2
+							g2 = ng2
+							b2 = nb2
+						end
+						table_insert(timelines, timeline)
+					elseif timelineName == "rgb2" then
+						local timeline = Animation.RGB2Timeline.new(#timelineMap, #timelineMap * 6, slotIndex)
+						local keyMap = timelineMap[1]
+						local time = getValue(keyMap, "time", 0)
+						local color = keyMap["light"]
+						local r = tonumber(color:sub(1, 2), 16) / 255
+						local g = tonumber(color:sub(3, 4), 16) / 255
+						local b = tonumber(color:sub(5, 6), 16) / 255
+						color = keyMap["dark"]
+						local r2 = tonumber(color:sub(1, 2), 16) / 255
+						local g2 = tonumber(color:sub(3, 4), 16) / 255
+						local b2 = tonumber(color:sub(5, 6), 16) / 255
+						local bezier = 0
+						for i,keyMap in ipairs(timelineMap) do
+							local frame = i - 1
+							timeline:setFrame(frame, time, r, g, b, r2, g2, b2)
+							local nextMap = timelineMap[i + 1]
+							if not nextMap then
+								timeline:shrink(bezier)
+								break
+							end
+							local time2 = getValue(nextMap, "time", 0)
+							color = nextMap["light"]
+							local nr = tonumber(color:sub(1, 2), 16) / 255
+							local ng = tonumber(color:sub(3, 4), 16) / 255
+							local nb = tonumber(color:sub(5, 6), 16) / 255
+							color = nextMap["dark"]
+							local nr2 = tonumber(color:sub(1, 2), 16) / 255
+							local ng2 = tonumber(color:sub(3, 4), 16) / 255
+							local nb2 = tonumber(color:sub(5, 6), 16) / 255
+							local curve = keyMap.curve
+							if curve then
+								bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1)
+								bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1)
+								bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1)
+								bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, r2, nr2, 1)
+								bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, g2, ng2, 1)
+								bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, b2, nb2, 1)
+							end
+							time = time2
+							r = nr
+							g = ng
+							b = nb
+							r2 = nr2
+							g2 = ng2
+							b2 = nb2
 						end
 						end
 						table_insert(timelines, timeline)
 						table_insert(timelines, timeline)
-						duration = math.max(duration, timeline.frames[timeline:getFrameCount() - 1])
-
 					else
 					else
-						error("Invalid frame type for a slot: " .. timelineName .. " (" .. slotName .. ")")
+						error("Invalid timeline type for a slot: " .. timelineName .. " (" .. slotName .. ")")
 					end
 					end
 				end
 				end
 			end
 			end
 		end
 		end
 
 
-		-- Bone timelines
+		-- Bone timelines.
 		local bonesMap = map["bones"]
 		local bonesMap = map["bones"]
 		if bonesMap then
 		if bonesMap then
-			for boneName,timelineMap in pairs(bonesMap) do
+			for boneName,boneMap in pairs(bonesMap) do
 				local boneIndex = skeletonData:findBoneIndex(boneName)
 				local boneIndex = skeletonData:findBoneIndex(boneName)
 				if boneIndex == -1 then error("Bone not found: " .. boneName) end
 				if boneIndex == -1 then error("Bone not found: " .. boneName) end
-
-				for timelineName,values in pairs(timelineMap) do
-					if timelineName == "rotate" then
-						local timeline = Animation.RotateTimeline.new(#values)
-						timeline.boneIndex = boneIndex
-
-						local frameIndex = 0
-						for _,valueMap in ipairs(values) do
-							timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), getValue(valueMap, "angle", 0))
-							readCurve(valueMap, timeline, frameIndex)
-							frameIndex = frameIndex + 1
-						end
-						table_insert(timelines, timeline)
-						duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.RotateTimeline.ENTRIES])
-
-					elseif timelineName == "translate" or timelineName == "scale" or timelineName == "shear" then
-						local timeline
-						local timelineScale = 1
-						local defaultValue = 0
-						if timelineName == "scale" then
-							timeline = Animation.ScaleTimeline.new(#values)
-							defaultValue = 1
-						elseif timelineName == "shear" then
-							timeline = Animation.ShearTimeline.new(#values)
-						else
-							timeline = Animation.TranslateTimeline.new(#values)
-							timelineScale = self.scale
-						end
-						timeline.boneIndex = boneIndex
-
-						local frameIndex = 0
-						for _,valueMap in ipairs(values) do
-							local x = getValue(valueMap, "x", defaultValue) * timelineScale
-							local y = getValue(valueMap, "y", defaultValue) * timelineScale
-							timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), x, y)
-							readCurve(valueMap, timeline, frameIndex)
-							frameIndex = frameIndex + 1
-						end
-						table_insert(timelines, timeline)
-						duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.TranslateTimeline.ENTRIES])
+				for timelineName,timelineMap in pairs(boneMap) do
+					if not timelineMap then
+					elseif timelineName == "rotate" then
+						table_insert(timelines, readTimeline1(timelineMap, Animation.RotateTimeline.new(#timelineMap, #timelineMap, boneIndex), 0, 1))
+					elseif timelineName == "translate" then
+						local timeline = Animation.TranslateTimeline.new(#timelineMap, #timelineMap * 2, boneIndex)
+						table_insert(timelines, readTimeline2(timelineMap, timeline, "x", "y", 0, scale))
+					elseif timelineName == "translatex" then
+						local timeline = Animation.TranslateXTimeline.new(#timelineMap, #timelineMap, boneIndex)
+						table_insert(timelines, readTimeline1(timelineMap, timeline, 0, scale))
+					elseif timelineName == "translatey" then
+						local timeline = Animation.TranslateYTimeline.new(#timelineMap, #timelineMap, boneIndex)
+						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))
+					elseif timelineName == "scalex" then
+						local timeline = Animation.ScaleXTimeline.new(#timelineMap, #timelineMap, boneIndex)
+						table_insert(timelines, readTimeline1(timelineMap, timeline, 1, 1))
+					elseif timelineName == "scaley" then
+						local timeline = Animation.ScaleYTimeline.new(#timelineMap, #timelineMap, boneIndex)
+						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))
+					elseif timelineName == "shearx" then
+						local timeline = Animation.ShearXTimeline.new(#timelineMap, #timelineMap, boneIndex)
+						table_insert(timelines, readTimeline1(timelineMap, timeline, 0, 1))
+					elseif timelineName == "sheary" then
+						local timeline = Animation.ShearYTimeline.new(#timelineMap, #timelineMap, boneIndex)
+						table_insert(timelines, readTimeline1(timelineMap, timeline, 0, 1))
 					else
 					else
 						error("Invalid timeline type for a bone: " .. timelineName .. " (" .. boneName .. ")")
 						error("Invalid timeline type for a bone: " .. timelineName .. " (" .. boneName .. ")")
 					end
 					end
@@ -664,96 +788,169 @@ function SkeletonJson.new (attachmentLoader)
 		-- IK timelines.
 		-- IK timelines.
 		local ik = map["ik"]
 		local ik = map["ik"]
 		if ik then
 		if ik then
-			for ikConstraintName,values in pairs(ik) do
-				local ikConstraint = skeletonData:findIkConstraint(ikConstraintName)
-				local timeline = Animation.IkConstraintTimeline.new(#values)
-				for i,other in pairs(skeletonData.ikConstraints) do
-					if other == ikConstraint then
-						timeline.ikConstraintIndex = i
-						break
+			for constraintName,timelineMap in pairs(ik) do
+				local keyMap = timelineMap[1]
+				if keyMap then
+					local constraintIndex = -1
+					for i,other in pairs(skeletonData.ikConstraints) do
+						if other.name == constraintName then
+							constraintIndex = i
+							break
+						end
+					end
+					local timeline = Animation.IkConstraintTimeline.new(#timelineMap, #timelineMap * 2, constraintIndex)
+					local time = getValue(keyMap, "time", 0)
+					local mix = getValue(keyMap, "mix", 1)
+					local softness = getValue(keyMap, "softness", 0) * scale
+					local bezier = 0
+					for i,keyMap in ipairs(timelineMap) do
+						local frame = i - 1
+						local bendPositive = 1
+						local compress = false
+						local stretch = false
+						if keyMap["bendPositive"] == false then bendPositive = -1 end
+						if keyMap["compress"] ~= nil then compress = keyMap["compress"] end
+						if keyMap["stretch"] ~= nil then stretch = keyMap["stretch"] end
+						timeline:setFrame(frame, time, mix, softness, bendPositive, compress, stretch)
+						local nextMap = timelineMap[i + 1]
+						if not nextMap then
+							timeline:shrink(bezier)
+							break
+						end
+						local time2 = getValue(nextMap, "time", 0)
+						color = nextMap["color"]
+						local mix2 = getValue(nextMap, "mix", 1)
+						local softness2 = getValue(nextMap, "softness", 0) * scale
+						local curve = keyMap.curve
+						if curve then
+							bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mix, mix2, 1)
+							bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, softness, softness2, scale)
+						end
+						time = time2
+						mix = mix2
+						softness = softness2
 					end
 					end
-				end
-				local frameIndex = 0
-				for _,valueMap in ipairs(values) do
-					local mix = 1
-					local softness = 0
-					if valueMap["mix"] ~= nil then mix = valueMap["mix"] end
-					if valueMap["softness"] ~= nil then softness = valueMap["softness"] * scale end
-					local bendPositive = 1
-					if valueMap["bendPositive"] == false then bendPositive = -1 end
-					local stretch = false
-					if valueMap["stretch"] ~= nil then stretch = valueMap["stretch"] end
-					local compress = false
-					if valueMap["compress"] ~= nil then compress = valueMap["compress"] end
-					timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), mix, softness, bendPositive, compress, stretch)
-					readCurve(valueMap, timeline, frameIndex)
-					frameIndex = frameIndex + 1
 				end
 				end
 				table_insert(timelines, timeline)
 				table_insert(timelines, timeline)
-				duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.IkConstraintTimeline.ENTRIES])
 			end
 			end
 		end
 		end
 
 
 		-- Transform constraint timelines.
 		-- Transform constraint timelines.
 		local transform = map["transform"]
 		local transform = map["transform"]
 		if transform then
 		if transform then
-			for constraintName, values in pairs(transform) do
-				local constraint = skeletonData:findTransformConstraint(constraintName)
-				local timeline = Animation.TransformConstraintTimeline.new(#values)
-				for i,other in pairs(skeletonData.transformConstraints) do
-					if other == constraint then
-						timeline.transformConstraintIndex = i
-						break
+			for constraintName, timelineMap in pairs(transform) do
+				local keyMap = timelineMap[1]
+				if keyMap then
+					local constraintIndex = -1
+					for i,other in pairs(skeletonData.transformConstraints) do
+						if other.name == constraintName then
+							constraintIndex = i
+							break
+						end
 					end
 					end
+					local timeline = Animation.TransformConstraintTimeline.new(#timelineMap, #timelineMap * 4, constraintIndex)
+					local time = getValue(keyMap, "time", 0)
+					local mixRotate = getValue(keyMap, "mixRotate", 0)
+					local mixX = getValue(keyMap, "mixX", 1)
+					local mixY = getValue(keyMap, "mixY", mixX)
+					local mixScaleX = getValue(keyMap, "mixScaleX", 1)
+					local mixScaleY = getValue(keyMap, "mixScaleY", mixScaleX)
+					local mixShearY = getValue(keyMap, "mixShearY", 1)
+					local bezier = 0
+					for i,keyMap in ipairs(timelineMap) do
+						local frame = i - 1
+						timeline:setFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY)
+						local nextMap = timelineMap[frame + 1]
+						if not nextMap then
+							timeline:shrink(bezier)
+							break
+						end
+						local time2 = getValue(nextMap, "time", 0)
+						local mixRotate2 = getValue(nextMap, "mixRotate", 1)
+						local mixX2 = getValue(nextMap, "mixX", 1)
+						local mixY2 = getValue(nextMap, "mixY", mixX2)
+						local mixScaleX2 = getValue(nextMap, "mixScaleX", 1)
+						local mixScaleY2 = getValue(nextMap, "mixScaleY", mixScaleX2)
+						local mixShearY2 = getValue(nextMap, "mixShearY", 1)
+						local curve = keyMap.curve
+						if curve then
+							bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mixRotate, mixRotate2, 1)
+							bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, mixX, mixX2, 1)
+							bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, mixY, mixY2, 1)
+							bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, mixScaleX, mixScaleX2, 1)
+							bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, mixScaleY, mixScaleY2, 1)
+							bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, mixShearY, mixShearY2, 1)
+						end
+						time = time2
+						mixRotate = mixRotate2
+						mixX = mixX2
+						mixY = mixY2
+						mixScaleX = mixScaleX2
+						mixScaleY = mixScaleY2
+						mixScaleX = mixScaleX2
+					end
+					table_insert(timelines, timeline)
 				end
 				end
-				local frameIndex = 0
-				for _,valueMap in ipairs(values) do
-					timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), getValue(valueMap, "rotateMix", 1), getValue(valueMap, "translateMix", 1), getValue(valueMap, "scaleMix", 1), getValue(valueMap, "shearMix", 1))
-					readCurve(valueMap, timeline, frameIndex)
-					frameIndex = frameIndex + 1
-				end
-				table_insert(timelines, timeline)
-				duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.TransformConstraintTimeline.ENTRIES])
 			end
 			end
 		end
 		end
 
 
 		-- Path constraint timelines.
 		-- Path constraint timelines.
 		if map.path then
 		if map.path then
 			for constraintName,constraintMap in pairs(map.path) do
 			for constraintName,constraintMap in pairs(map.path) do
-				local index = skeletonData:findPathConstraintIndex(constraintName)
-				if index == -1 then error("Path constraint not found: " .. constraintName, 2) end
-				local data = skeletonData.pathConstraints[index]
+				local constraint, constraintIndex = -1
+				for i,other in pairs(skeletonData.transformConstraints) do
+					if other.name == constraintName then
+						constraintIndex = i
+						constraint = other
+						break
+					end
+				end
 				for timelineName, timelineMap in pairs(constraintMap) do
 				for timelineName, timelineMap in pairs(constraintMap) do
-					if timelineName == "position" or timelineName == "spacing" then
-						local timeline = nil
-						local timelineScale = 1
-						if timelineName == "spacing" then
-							timeline = Animation.PathConstraintSpacingTimeline.new(#timelineMap)
-							if data.spacingMode == PathConstraintData.SpacingMode.length or data.spacingMode == PathConstraintData.SpacingMode.fixed then timelineScale = scale end
-						else
-							timeline = Animation.PathConstraintPositionTimeline.new(#timelineMap)
-							if data.positionMode == PathConstraintData.PositionMode.fixed then timelineScale = scale end
-						end
-						timeline.pathConstraintIndex = index
-						local frameIndex = 0
-						for _,valueMap in ipairs(timelineMap) do
-							timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), getValue(valueMap, timelineName, 0) * timelineScale)
-							readCurve(valueMap, timeline, frameIndex)
-							frameIndex = frameIndex + 1
-						end
-						table_insert(timelines, timeline)
-						duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.PathConstraintPositionTimeline.ENTRIES])
-					elseif timelineName == "mix" then
-						local timeline = Animation.PathConstraintMixTimeline.new(#timelineMap)
-						timeline.pathConstraintIndex = index
-						local frameIndex = 0
-						for _,valueMap in ipairs(timelineMap) do
-							timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), getValue(valueMap, "rotateMix", 1), getValue(valueMap, "translateMix", 1))
-							readCurve(valueMap, timeline, frameIndex)
-							frameIndex = frameIndex + 1
+					local keyMap = timelineMap[1]
+					if keyMap then
+						if timelineName == "position" then
+							local timeline = Animation.PathConstraintPositionTimeline.new(#timelineMap, #timelineMap, constraintIndex)
+							local timelineScale = 1
+							if constraint.positionMode == 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
+							table_insert(timelines, readTimeline1(timelineMap, timeline, 0, timelineScale))
+						elseif timelineName == "mix" then
+							local timeline = Animation.PathConstraintMixTimeline.new(#timelineMap, #timelineMap * 3, constraintIndex)
+							local time = getValue(keyMap, "time", 0)
+							local mixRotate = getValue(keyMap, "mixRotate", 1)
+							local mixX = getValue(keyMap, "mixX", 1)
+							local mixY = getValue(keyMap, "mixY", mixX)
+							local bezier = 0
+							for i,keyMap in ipairs(timelineMap) do
+								local frame = i - 1
+								timeline:setFrame(frame, time, mixRotate, mixX, mixY)
+								local nextMap = timelineMap[frame + 1]
+								if not nextMap then
+									timeline:shrink(bezier)
+									break
+								end
+								local time2 = getValue(nextMap, "time", 0)
+								local mixRotate2 = getValue(nextMap, "mixRotate", 1)
+								local mixX2 = getValue(nextMap, "mixX", 1)
+								local mixY2 = getValue(nextMap, "mixY", mixX2)
+								local curve = keyMap.curve
+								if curve then
+									bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mixRotate, mixRotate2, 1)
+									bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, mixX, mixX2, 1)
+									bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, mixY, mixY2, 1)
+								end
+								time = time2
+								mixRotate = mixRotate2
+								mixX = mixX2
+								mixY = mixY2
+								keyMap = nextMap
+							end
+							table_insert(timelines, timeline)
 						end
 						end
-						table_insert(timelines, timeline)
-						duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.PathConstraintMixTimeline.ENTRIES])
 					end
 					end
 				end
 				end
 			end
 			end
@@ -765,68 +962,72 @@ function SkeletonJson.new (attachmentLoader)
 				local skin = skeletonData:findSkin(deformName)
 				local skin = skeletonData:findSkin(deformName)
 				if not skin then error("Skin not found: " .. deformName, 2) end
 				if not skin then error("Skin not found: " .. deformName, 2) end
 				for slotName,slotMap in pairs(deformMap) do
 				for slotName,slotMap in pairs(deformMap) do
-					local slotIndex = skeletonData:findSlotIndex(slotName)
+					local slotIndex = skeletonData:findSlot(slotName).index
 					if slotIndex == -1 then error("Slot not found: " .. slotMap.name, 2) end
 					if slotIndex == -1 then error("Slot not found: " .. slotMap.name, 2) end
 					for timelineName,timelineMap in pairs(slotMap) do
 					for timelineName,timelineMap in pairs(slotMap) do
-						local attachment = skin:getAttachment(slotIndex, timelineName)
-						if not attachment then error("Deform attachment not found: " .. timelineMap.name, 2) end
-						local weighted = attachment.bones ~= nil
-						local vertices = attachment.vertices;
-						local deformLength = #vertices
-						if weighted then deformLength = math.floor(#vertices / 3) * 2 end
-
-						local timeline = Animation.DeformTimeline.new(#timelineMap)
-						timeline.slotIndex = slotIndex
-						timeline.attachment = attachment
-
-						local frameIndex = 0
-						for _,valueMap in ipairs(timelineMap) do
-							local deform = nil
-							local verticesValue = getValue(valueMap, "vertices", nil)
-							if verticesValue == nil then
-								deform = vertices
-								if weighted then deform = utils.newNumberArray(deformLength) end
-							else
-								deform = utils.newNumberArray(deformLength)
-								local start = getValue(valueMap, "offset", 0) + 1
-								utils.arrayCopy(verticesValue, 1, deform, start, #verticesValue)
-								if scale ~= 1 then
-									local i = start
-									local n = i + #verticesValue
-									while i < n do
-										deform[i] = deform[i] * scale
-										i = i + 1
+						local keyMap = timelineMap[1]
+						if keyMap then
+							local attachment = skin:getAttachment(slotIndex, timelineName)
+							if not attachment then error("Deform attachment not found: " .. timelineMap.name, 2) end
+							local weighted = attachment.bones ~= nil
+							local vertices = attachment.vertices
+							local deformLength = #vertices
+							if weighted then deformLength = math.floor(deformLength / 3) * 2 end
+
+							local timeline = Animation.DeformTimeline.new(#timelineMap, #timelineMap, slotIndex, attachment)
+							local bezier = 0
+							for i,keyMap in ipairs(timelineMap) do
+								local deform = nil
+								local verticesValue = getValue(keyMap, "vertices", nil)
+								if verticesValue == nil then
+									deform = vertices
+									if weighted then deform = utils.newNumberArray(deformLength) end
+								else
+									deform = utils.newNumberArray(deformLength)
+									local start = getValue(keyMap, "offset", 0) + 1
+									utils.arrayCopy(verticesValue, 1, deform, start, #verticesValue)
+									if scale ~= 1 then
+										local i = start
+										local n = i + #verticesValue
+										while i < n do
+											deform[i] = deform[i] * scale
+											i = i + 1
+										end
 									end
 									end
-								end
-								if not weighted then
-									local i = 1
-									local n = i + deformLength
-									while i < n do
-										deform[i] = deform[i] + vertices[i]
-										i = i + 1
+									if not weighted then
+										local i = 1
+										local n = i + deformLength
+										while i < n do
+											deform[i] = deform[i] + vertices[i]
+											i = i + 1
+										end
 									end
 									end
 								end
 								end
+								local frame = i - 1
+								timeline:setFrame(frame, time, mixRotate, mixX, mixY)
+								local nextMap = timelineMap[frame + 1]
+								if not nextMap then
+									timeline:shrink(bezier)
+									break
+								end
+								local time2 = getValue(nextMap, "time", 0)
+								local curve = keyMap.curve
+								if curve then bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, 0, 1, 1) end
+								time = time2
 							end
 							end
-
-							timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), deform)
-							readCurve(valueMap, timeline, frameIndex)
-							frameIndex = frameIndex + 1
+							table_insert(timelines, timeline)
 						end
 						end
-						table_insert(timelines, timeline)
-						duration = math.max(duration, timeline.frames[timeline:getFrameCount() - 1])
 					end
 					end
 				end
 				end
 			end
 			end
 		end
 		end
 
 
-		-- Draworder timeline.
-		local drawOrderValues = map["drawOrder"]
-		if not drawOrderValues then drawOrderValues = map["draworder"] end
-		if drawOrderValues then
-			local timeline = Animation.DrawOrderTimeline.new(#drawOrderValues)
+		-- Draw order timelines.
+		if map["drawOrder"] then
+			local timeline = Animation.DrawOrderTimeline.new(#map["drawOrder"])
 			local slotCount = #skeletonData.slots
 			local slotCount = #skeletonData.slots
-			local frameIndex = 0
-			for _,drawOrderMap in ipairs(drawOrderValues) do
+			local frame = 0
+			for _,drawOrderMap in ipairs(map["drawOrder"]) do
 				local drawOrder = nil
 				local drawOrder = nil
 				local offsets = drawOrderMap["offsets"]
 				local offsets = drawOrderMap["offsets"]
 				if offsets then
 				if offsets then
@@ -835,7 +1036,7 @@ function SkeletonJson.new (attachmentLoader)
 					local originalIndex = 1
 					local originalIndex = 1
 					local unchangedIndex = 1
 					local unchangedIndex = 1
 					for _,offsetMap in ipairs(offsets) do
 					for _,offsetMap in ipairs(offsets) do
-						local slotIndex = skeletonData:findSlotIndex(offsetMap["slot"])
+						local slotIndex = skeletonData:findSlot(offsetMap["slot"]).index
 						if slotIndex == -1 then error("Slot not found: " .. offsetMap["slot"]) end
 						if slotIndex == -1 then error("Slot not found: " .. offsetMap["slot"]) end
 						-- Collect unchanged items.
 						-- Collect unchanged items.
 						while originalIndex ~= slotIndex do
 						while originalIndex ~= slotIndex do
@@ -861,18 +1062,17 @@ function SkeletonJson.new (attachmentLoader)
 						end
 						end
 					end
 					end
 				end
 				end
-				timeline:setFrame(frameIndex, getValue(drawOrderMap, "time", 0), drawOrder)
-				frameIndex = frameIndex + 1
+				timeline:setFrame(frame, getValue(drawOrderMap, "time", 0), drawOrder)
+				frame = frame + 1
 			end
 			end
 			table_insert(timelines, timeline)
 			table_insert(timelines, timeline)
-			duration = math.max(duration, timeline.frames[timeline:getFrameCount() - 1])
 		end
 		end
 
 
-		-- Event timeline.
+		-- Event timelines.
 		local events = map["events"]
 		local events = map["events"]
 		if events then
 		if events then
 			local timeline = Animation.EventTimeline.new(#events)
 			local timeline = Animation.EventTimeline.new(#events)
-			local frameIndex = 0
+			local frame = 0
 			for _,eventMap in ipairs(events) do
 			for _,eventMap in ipairs(events) do
 				local eventData = skeletonData:findEvent(eventMap["name"])
 				local eventData = skeletonData:findEvent(eventMap["name"])
 				if not eventData then error("Event not found: " .. eventMap["name"]) end
 				if not eventData then error("Event not found: " .. eventMap["name"]) end
@@ -896,24 +1096,89 @@ function SkeletonJson.new (attachmentLoader)
 					event.volume = getValue(eventMap, "volume", 1)
 					event.volume = getValue(eventMap, "volume", 1)
 					event.balance = getValue(eventMap, "balance", 0)
 					event.balance = getValue(eventMap, "balance", 0)
 				end
 				end
-				timeline:setFrame(frameIndex, event)
-				frameIndex = frameIndex + 1
+				timeline:setFrame(frame, event)
+				frame = frame + 1
 			end
 			end
 			table_insert(timelines, timeline)
 			table_insert(timelines, timeline)
-			duration = math.max(duration, timeline.frames[timeline:getFrameCount() - 1])
 		end
 		end
 
 
+		local duration = 0
+		for _,timeline in ipairs(timelines) do
+			duration = math.max(duration, timeline:getDuration())
+		end
 		table_insert(skeletonData.animations, Animation.new(name, timelines, duration))
 		table_insert(skeletonData.animations, Animation.new(name, timelines, duration))
 	end
 	end
 
 
-	readCurve = function (map, timeline, frameIndex)
+	readCurve = function (map, timeline, frame)
 		local curve = map["curve"]
 		local curve = map["curve"]
 		if not curve then return end
 		if not curve then return end
 		if curve == "stepped" then
 		if curve == "stepped" then
-			timeline:setStepped(frameIndex)
+			timeline:setStepped(frame)
 		else
 		else
-			timeline:setCurve(frameIndex, getValue(map, "curve", 0), getValue(map, "c2", 0), getValue(map, "c3", 1), getValue(map, "c4", 1))
+			timeline:setCurve(frame, getValue(map, "curve", 0), getValue(map, "c2", 0), getValue(map, "c3", 1), getValue(map, "c4", 1))
+		end
+	end
+
+	readTimeline1 = function (keys, timeline, defaultValue, scale)
+		local keyMap = keys[1]
+		local time = getValue(keyMap, "time", 0)
+		local value = getValue(keyMap, "value", defaultValue) * scale
+		local bezier = 0
+		for i,keyMap in ipairs(keys) do
+			local frame = i - 1
+			timeline:setFrame(frame, time, value)
+			local nextMap = keys[frame + 1]
+			if not nextMap then break end
+			local time2 = getValue(nextMap, "time", 0)
+			local value2 = getValue(nextMap, "value", defaultValue) * scale
+			local curve = keyMap.curve
+			if curve then bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, value, value2, scale) end
+			time = time2
+			value = value2
+		end
+		timeline:shrink(bezier)
+		return timeline
+	end
+
+	readTimeline2 = function (keys, timeline, name1, name2, defaultValue, scale)
+		local keyMap = keys[1]
+		local time = getValue(keyMap, "time", 0)
+		local value1 = getValue(keyMap, name1, defaultValue) * scale
+		local value2 = getValue(keyMap, name2, defaultValue) * scale
+		local bezier = 0
+		for i,keyMap in ipairs(keys) do
+			local frame = i - 1
+			timeline:setFrame(frame, time, value1, value2)
+			local nextMap = keys[frame + 1]
+			if not nextMap then break end
+			local time2 = getValue(nextMap, "time", 0)
+			local nvalue1 = getValue(nextMap, name1, defaultValue) * scale
+			local nvalue2 = getValue(nextMap, name2, defaultValue) * scale
+			local curve = keyMap.curve
+			if curve then
+				bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, value1, nvalue1, scale)
+				bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, value2, nvalue2, scale)
+			end
+			time = time2
+			value1 = nvalue1
+			value2 = nvalue2
+		end
+		timeline:shrink(bezier)
+		return timeline
+	end
+
+	readCurve = function (curve, timeline, bezier, frame, value, time1, time2, value1, value2, scale)
+		if curve == "stepped" then
+			if value ~= 0 then timeline.setStepped(frame) end
+			return bezier
 		end
 		end
+		local i = value * 4
+		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)
+		return bezier + 1
 	end
 	end
 
 
 	getArray = function (map, name, scale)
 	getArray = function (map, name, scale)

+ 4 - 4
spine-lua/spine-lua/TransformConstraint.lua

@@ -97,7 +97,7 @@ function TransformConstraint:applyAbsoluteWorld ()
 	local tb = target.b
 	local tb = target.b
 	local tc = target.c
 	local tc = target.c
 	local td = target.d
 	local td = target.d
-	local degRadReflect = 0;
+	local degRadReflect = 0
 	if ta * td - tb * tc > 0 then degRadReflect = utils.degRad else degRadReflect = -utils.degRad end
 	if ta * td - tb * tc > 0 then degRadReflect = utils.degRad else degRadReflect = -utils.degRad end
 	local offsetRotation = self.data.offsetRotation * degRadReflect
 	local offsetRotation = self.data.offsetRotation * degRadReflect
 	local offsetShearY = self.data.offsetShearY * degRadReflect
 	local offsetShearY = self.data.offsetShearY * degRadReflect
@@ -184,7 +184,7 @@ function TransformConstraint:applyRelativeWorld ()
 	local tb = target.b
 	local tb = target.b
 	local tc = target.c
 	local tc = target.c
 	local td = target.d
 	local td = target.d
-	local degRadReflect = 0;
+	local degRadReflect = 0
 	if ta * td - tb * tc > 0 then degRadReflect = utils.degRad else degRadReflect = -utils.degRad end
 	if ta * td - tb * tc > 0 then degRadReflect = utils.degRad else degRadReflect = -utils.degRad end
 	local offsetRotation = self.data.offsetRotation * degRadReflect
 	local offsetRotation = self.data.offsetRotation * degRadReflect
 	local offsetShearY = self.data.offsetShearY * degRadReflect
 	local offsetShearY = self.data.offsetShearY * degRadReflect
@@ -242,7 +242,7 @@ function TransformConstraint:applyRelativeWorld ()
 			end
 			end
 			local b = bone.b
 			local b = bone.b
 			local d = bone.d
 			local d = bone.d
-			r = math_atan2(d, b) + (r - math_pi / 2 + offsetShearY) * shearMix;
+			r = math_atan2(d, b) + (r - math_pi / 2 + offsetShearY) * shearMix
 			local s = math_sqrt(b * b + d * d)
 			local s = math_sqrt(b * b + d * d)
 			bone.b = math_cos(r) * s
 			bone.b = math_cos(r) * s
 			bone.d = math_sin(r) * s
 			bone.d = math_sin(r) * s
@@ -297,7 +297,7 @@ function TransformConstraint:applyAbsoluteLocal ()
 			bone.shearY = bone.shearY + r * shearMix
 			bone.shearY = bone.shearY + r * shearMix
 		end
 		end
 
 
-		bone:updateWorldTransformWith(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY);
+		bone:updateWorldTransformWith(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY)
 	end
 	end
 end
 end
 
 

+ 21 - 21
spine-lua/spine-lua/Triangulator.lua

@@ -74,7 +74,7 @@ function Triangulator:triangulate (verticesArray)
 	end
 	end
 
 
 	self.triangles = {}
 	self.triangles = {}
-	local triangles = self.triangles;
+	local triangles = self.triangles
 
 
 	while vertexCount > 3 do
 	while vertexCount > 3 do
 		-- Find ear tip.
 		-- Find ear tip.
@@ -122,7 +122,7 @@ function Triangulator:triangulate (verticesArray)
 			if _next == 0 then
 			if _next == 0 then
 				repeat
 				repeat
 					if not isConcave[i] then
 					if not isConcave[i] then
-						break;
+						break
 					end
 					end
 					i = i - 1
 					i = i - 1
 				until i == 0
 				until i == 0
@@ -171,10 +171,10 @@ function Triangulator:decompose(verticesArray, triangles)
 	local vertices = verticesArray
 	local vertices = verticesArray
 
 
 	self.convexPolygons = {}
 	self.convexPolygons = {}
-	local convexPolygons = self.convexPolygons;
+	local convexPolygons = self.convexPolygons
 
 
 	self.convexPolygonsIndices = {}
 	self.convexPolygonsIndices = {}
-	local convexPolygonsIndices = self.convexPolygonsIndices;
+	local convexPolygonsIndices = self.convexPolygonsIndices
 
 
 	local polygonIndices = {}
 	local polygonIndices = {}
 	local polygon = {}
 	local polygon = {}
@@ -197,12 +197,12 @@ function Triangulator:decompose(verticesArray, triangles)
 		local y3 = vertices[t3 + 1]
 		local y3 = vertices[t3 + 1]
 
 
 		-- If the base of the last triangle is the same as this triangle, check if they form a convex polygon (triangle fan).
 		-- If the base of the last triangle is the same as this triangle, check if they form a convex polygon (triangle fan).
-		local merged = false;
+		local merged = false
 		if fanBaseIndex == t1 then
 		if fanBaseIndex == t1 then
-			local o = #polygon - 4 + 1;
-			local p = polygon;
-			local winding1 = self:winding(p[o], p[o + 1], p[o + 2], p[o + 3], x3, y3);
-			local winding2 = self:winding(x3, y3, p[1], p[2], p[3], p[4]);
+			local o = #polygon - 4 + 1
+			local p = polygon
+			local winding1 = self:winding(p[o], p[o + 1], p[o + 2], p[o + 3], x3, y3)
+			local winding2 = self:winding(x3, y3, p[1], p[2], p[3], p[4])
 			if winding1 == lastWinding and winding2 == lastWinding then
 			if winding1 == lastWinding and winding2 == lastWinding then
 				table_insert(polygon, x3)
 				table_insert(polygon, x3)
 				table_insert(polygon, y3)
 				table_insert(polygon, y3)
@@ -226,8 +226,8 @@ function Triangulator:decompose(verticesArray, triangles)
 			table_insert(polygon, y3)
 			table_insert(polygon, y3)
 			polygonIndices = {}
 			polygonIndices = {}
 			table_insert(polygonIndices, t1)
 			table_insert(polygonIndices, t1)
-			table_insert(polygonIndices, t2);
-			table_insert(polygonIndices, t3);
+			table_insert(polygonIndices, t2)
+			table_insert(polygonIndices, t3)
 			lastWinding = self:winding(x1, y1, x2, y2, x3, y3)
 			lastWinding = self:winding(x1, y1, x2, y2, x3, y3)
 			fanBaseIndex = t1
 			fanBaseIndex = t1
 		end
 		end
@@ -266,11 +266,11 @@ function Triangulator:decompose(verticesArray, triangles)
 				if ii ~= i then
 				if ii ~= i then
 					local otherIndices = convexPolygonsIndices[ii]
 					local otherIndices = convexPolygonsIndices[ii]
 					if (#otherIndices == 3) then
 					if (#otherIndices == 3) then
-						local otherFirstIndex = otherIndices[1];
-						local otherSecondIndex = otherIndices[2];
-						local otherLastIndex = otherIndices[3];
+						local otherFirstIndex = otherIndices[1]
+						local otherSecondIndex = otherIndices[2]
+						local otherLastIndex = otherIndices[3]
 
 
-						local otherPoly = convexPolygons[ii];
+						local otherPoly = convexPolygons[ii]
 						local x3 = otherPoly[#otherPoly - 2 + 1]
 						local x3 = otherPoly[#otherPoly - 2 + 1]
 						local y3 = otherPoly[#otherPoly - 1 + 1]
 						local y3 = otherPoly[#otherPoly - 1 + 1]
 
 
@@ -308,14 +308,14 @@ function Triangulator:decompose(verticesArray, triangles)
 		i = i - 1
 		i = i - 1
 	end
 	end
 
 
-	return convexPolygons;
+	return convexPolygons
 end
 end
 
 
 function Triangulator:isConcave(index, vertexCount, vertices, indices)
 function Triangulator:isConcave(index, vertexCount, vertices, indices)
-	local previous = indices[(vertexCount + index - 1) % vertexCount] * 2 + 1;
-	local current = indices[index] * 2 + 1;
-	local _next = indices[(index + 1) % vertexCount] * 2 + 1;
-	return not self:positiveArea(vertices[previous], vertices[previous + 1], vertices[current], vertices[current + 1], vertices[_next],vertices[_next + 1]);
+	local previous = indices[(vertexCount + index - 1) % vertexCount] * 2 + 1
+	local current = indices[index] * 2 + 1
+	local _next = indices[(index + 1) % vertexCount] * 2 + 1
+	return not self:positiveArea(vertices[previous], vertices[previous + 1], vertices[current], vertices[current + 1], vertices[_next],vertices[_next + 1])
 end
 end
 
 
 function Triangulator:positiveArea(p1x, p1y, p2x, p2y, p3x, p3y)
 function Triangulator:positiveArea(p1x, p1y, p2x, p2y, p3x, p3y)
@@ -328,7 +328,7 @@ function Triangulator:winding(p1x, p1y, p2x, p2y, p3x, p3y)
 	if p3x * py - p3y * px + px * p1y - p1x * py >= 0 then
 	if p3x * py - p3y * px + px * p1y - p1x * py >= 0 then
 		return 1
 		return 1
 	else
 	else
-		return -1;
+		return -1
 	end
 	end
 end
 end
 
 

+ 12 - 12
spine-lua/spine-lua/attachments/MeshAttachment.lua

@@ -84,8 +84,8 @@ function MeshAttachment:updateUVs ()
 			local i = 0
 			local i = 0
 			local n = #uvs
 			local n = #uvs
 			while i < n do
 			while i < n do
-				uvs[i + 1] = u + regionUVs[i + 2] * width;
-				uvs[i + 2] = v + (1 - regionUVs[i + 1]) * height;
+				uvs[i + 1] = u + regionUVs[i + 2] * width
+				uvs[i + 2] = v + (1 - regionUVs[i + 1]) * height
 				i = i + 2
 				i = i + 2
 			end
 			end
 		elseif region.degrees == 180 then
 		elseif region.degrees == 180 then
@@ -96,8 +96,8 @@ function MeshAttachment:updateUVs ()
 			local i = 0
 			local i = 0
 			local n = #uvs
 			local n = #uvs
 			while i < n do
 			while i < n do
-				uvs[i + 1] = u + (1 - regionUVs[i + 1]) * width;
-				uvs[i + 2] = v + (1 - regionUVs[i + 2]) * height;
+				uvs[i + 1] = u + (1 - regionUVs[i + 1]) * width
+				uvs[i + 2] = v + (1 - regionUVs[i + 2]) * height
 				i = i + 2
 				i = i + 2
 			end
 			end
 		elseif region.degrees == 270 then
 		elseif region.degrees == 270 then
@@ -108,20 +108,20 @@ function MeshAttachment:updateUVs ()
 			local i = 0
 			local i = 0
 			local n = #uvs
 			local n = #uvs
 			while i < n do
 			while i < n do
-				uvs[i + 1] = u + (1 - regionUVs[i + 2]) * width;
-				uvs[i + 2] = v + regionUVs[i + 1] * height;
+				uvs[i + 1] = u + (1 - regionUVs[i + 2]) * width
+				uvs[i + 2] = v + regionUVs[i + 1] * height
 				i = i + 2
 				i = i + 2
 			end
 			end
 		else
 		else
-			u = region.u - region.offsetX / textureWidth;
-			v = region.v - (region.originalHeight - region.offsetY - region.height) / textureHeight;
-			width = region.originalWidth / textureWidth;
-			height = region.originalHeight / textureHeight;
+			u = region.u - region.offsetX / textureWidth
+			v = region.v - (region.originalHeight - region.offsetY - region.height) / textureHeight
+			width = region.originalWidth / textureWidth
+			height = region.originalHeight / textureHeight
 			local i = 0
 			local i = 0
 			local n = #uvs
 			local n = #uvs
 			while i < n do
 			while i < n do
-				uvs[i + 1] = u + regionUVs[i + 1] * width;
-				uvs[i + 2] = v + regionUVs[i + 2] * height;
+				uvs[i + 1] = u + regionUVs[i + 1] * width
+				uvs[i + 2] = v + regionUVs[i + 2] * height
 				i = i + 2
 				i = i + 2
 			end
 			end
 		end
 		end

+ 1 - 0
spine-lua/spine-lua/attachments/RegionAttachment.lua

@@ -155,6 +155,7 @@ function RegionAttachment.new (name)
 end
 end
 
 
 function RegionAttachment:updateOffset ()
 function RegionAttachment:updateOffset ()
+	if not self.region then return end
 	local regionScaleX = self.width / self.region.originalWidth * self.scaleX
 	local regionScaleX = self.width / self.region.originalWidth * self.scaleX
 	local regionScaleY = self.height / self.region.originalHeight * self.scaleY
 	local regionScaleY = self.height / self.region.originalHeight * self.scaleY
 	local localX = -self.width / 2 * self.scaleX + self.region.offsetX * regionScaleX
 	local localX = -self.width / 2 * self.scaleX + self.region.offsetX * regionScaleX

+ 2 - 2
spine-lua/spine-lua/attachments/VertexAttachment.lua

@@ -36,8 +36,8 @@ local utils = require "spine-lua.utils"
 local AttachmentType = require "spine-lua.attachments.AttachmentType"
 local AttachmentType = require "spine-lua.attachments.AttachmentType"
 local Attachment = require "spine-lua.attachments.Attachment"
 local Attachment = require "spine-lua.attachments.Attachment"
 
 
-local nextID = 0;
-local SHL_11 = 2048;
+local nextID = 0
+local SHL_11 = 2048
 
 
 local VertexAttachment = {}
 local VertexAttachment = {}
 VertexAttachment.__index = VertexAttachment
 VertexAttachment.__index = VertexAttachment