Browse Source

[lua] Timelines 4.0 port.

Nathan Sweet 4 years ago
parent
commit
3b650f8db1

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

@@ -1,1611 +1,2059 @@
--------------------------------------------------------------------------------
--- 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 setmetatable = setmetatable
+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 name then error("name cannot be nil", 2) end
+	if not timelines then error("timelines cannot be nil", 2) end
+
+	local self = {
+		name = name,
+		timelines = timelines,
+		timelineIds = nil,
+		duration = duration
+	}
+
+	function self:setTimelines (timelines)
+		self.timelines = timelines
+
+		self.timelineIds = {}
+		for i,timeline in ipairs(self.timelines) do
+			for _,id in ipairs(timeline.propertyIds) do
+				timelineIds[id] = true
+			end
+		end
+	end
+
+	function self:hasTimeline (ids)
+		for _,id in ipairs(ids) do
+			if timelineIds[id] then return true end
+		end
+		return false
+	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
+
+	self:setTimelines(timelines)
+	return self
+end
+
+Animation.MixBlend = {
+	setup = 0,
+	first = 1,
+	replace = 2,
+	add = 3
+}
+local MixBlend = Animation.MixBlend
+
+Animation.MixDirection = {
+	mixIn = 0, mixOut = 1
+}
+local MixDirection = Animation.MixDirection
+
+Animation.Property = {
+	rotate = 0,
+	x = 1,
+	y = 2,
+	scaleX = 3,
+	scaleY = 4,
+	shearX = 5,
+	shearY = 6,
+
+	rgb = 7,
+	alpha = 8,
+	rgb2 = 9,
+
+	attachment = 10,
+	deform = 11,
+
+	event = 12,
+	drawOrder = 13,
+
+	ikConstraint = 14,
+	transformConstraint = 15,
+
+	pathConstraintPosition = 16,
+	pathConstraintSpacing = 17,
+	pathConstraintMix = 18
+}
+local Property = Animation.Property
+
+Animation.Timeline = {}
+function Animation.Timeline.new (frameCount, propertyIds)
+	local self = {
+		propertyIds = propertyIds,
+		frames = utils.newNumberArrayZero((frameCount - 1) * self:getFrameEntries())
+	}
+
+	function self:getFrameEntries ()
+		return 1
+	end
+	
+	function self:getFrameCount ()
+		return math_floor(zlen(self.frames) / self:getFrameEntries())
+	end
+	
+	function self:getDuration ()
+		return self.frames[zlen(self.frames) - self:getFrameEntries()]
+	end
+
+	return self
+end
+
+local function search1 (frames, time)
+	local n = zlen(frames)
+	while i <= n do
+		if frames[i] > time then return i - 1 end
+		i = i + 1
+	end
+	return n - 1
+end
+Animation.Timeline.search1 = search1
+
+local function search (frames, time, step)
+	local n = zlen(frames)
+	local i = step
+	while i <= n do
+		if frames[i] > time then return i - step end
+		i = i + step
+	end
+	return n - step
+end
+
+local LINEAR = 0
+local STEPPED = 1
+local BEZIER = 2
+local BEZIER_SIZE = 18
+
+Animation.CurveTimeline = {}
+function Animation.CurveTimeline.new (frameCount, bezierCount, propertyIds)
+	local LINEAR = 0
+	local STEPPED = 1
+	local BEZIER = 2
+	local BEZIER_SIZE = 10 * 2 - 1
+
+	local self = Animation.Timeline.new(frameCount, propertyIds)
+	self.curves = utils.newNumberArrayZero(frameCount + bezierCount * BEZIER_SIZE)
+
+	function self:getFrameCount ()
+		return math_floor(zlen(self.curves) / BEZIER_SIZE) + 1
+	end
+
+	function self:setStepped (frame)
+		self.curves[frame] = STEPPED
+	end
+
+	function self:setLinear (frame)
+		self.curves[frame] = LINEAR
+	end
+	
+	function self:shrink (bezierCount)
+		utils.setArraySize(self.curves, self:getFrameCount() + bezierCount * BEZIER_SIZE)
+	end
+
+	function self:setBezier (bezier, frame, value, time1, value1, cx1, cy1, cx2, cy2, time2, value2)
+		local curves = self.curves
+		local i = self:getFrameCount() + bezier * BEZIER_SIZE
+		if value == 0 then curves[frame] = BEZIER + i end
+		local tmpx = (time1 - cx1 * 2 + cx2) * 0.03
+		local tmpy = (value1 - cy1 * 2 + cy2) * 0.03
+		local dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006
+		local dddy = ((cy1 - cy2) * 3 - value1 + value2) * 0.006
+		local ddx = tmpx * 2 + dddx
+		local ddy = tmpy * 2 + dddy
+		local dx = (cx1 - time1) * 0.3 + tmpx + dddx * 0.16666667
+		local dy = (cy1 - value1) * 0.3 + tmpy + dddy * 0.16666667
+		local x = time1 + dx
+		local y = value1 + dy
+		local n = i + BEZIER_SIZE
+		while i < n do
+			curves[i] = x
+			curves[i + 1] = y
+			dx = dx + ddx
+			dy = dy + ddy
+			ddx = ddx + dddx
+			ddy = ddy + dddy
+			x = x + dx
+			y = y + dy
+			i = i + 2
+		end
+	end
+
+	function self:getBezierValue (time, frameIndex, valueOffset, i)
+		local curves = self.curves
+		if curves[i] > time then
+			local x = self.frames[frameIndex]
+			local y = self.frames[frameIndex + valueOffset]
+			return y + (time - x) / (curves[i] - x) * (curves[i + 1] - y)
+		end
+		local n = i + BEZIER_SIZE
+		while i < n do
+			if curves[i] >= time then
+				local x = curves[i - 2]
+				local y = curves[i - 1]
+				return y + (time - x) / (curves[i] - x) * (curves[i + 1] - y)
+			end
+			i = i + 2
+		end
+		frameIndex = frameIndex + self:getFrameEntries()
+		local x = curves[n - 2]
+		local y = curves[n - 1]
+		return y + (time - x) / (self.frames[frameIndex] - x) * (self.frames[frameIndex + valueOffset] - y)
+	end
+
+	return self
+end
+
+Animation.CurveTimeline1 = {}
+function Animation.CurveTimeline1.new (frameCount, bezierCount, propertyId)
+	local ENTRIES = 2
+	local VALUE = 1
+
+	local self = Animation.CurveTimeline.new(frameCount, bezierCount, { propertyId })
+
+	function self:getFrameEntries ()
+		return ENTRIES
+	end
+
+	function self:setFrame (frame, time, value)
+		frame = frame * ENTRIES
+		self.frames[frame] = time
+		self.frames[frame + VALUE] = value
+	end
+
+	function self:getCurveValue (time)
+		local frames = self.frames
+		local i = zlen(frames) - 2
+		local ii = 2
+		while ii <= i do
+			if frames[ii] > time then
+				i = ii - 2
+				break
+			end
+			ii = ii + 2
+		end
+		local curveType = self.curves[i / 2]
+		if curveType == LINEAR then
+			local before = frames[i]
+			local value = frames[i + VALUE]
+			return value + (time - before) / (frames[i + ENTRIES] - before) * (frames[i + ENTRIES + VALUE] - value)
+		elseif curveType == STEPPED then
+			return frames[i + VALUE]
+		end
+		return self:getBezierValue(time, i, VALUE, curveType - BEZIER)
+	end
+
+	return self
+end
+
+Animation.CurveTimeline2 = {}
+function Animation.CurveTimeline2.new (frameCount, bezierCount, propertyId1, propertyId2)
+	local ENTRIES = 3
+	local VALUE1 = 1
+	local VALUE2 = 2
+
+	local self = Animation.CurveTimeline.new(frameCount, bezierCount, { propertyId1, propertyId2 })
+
+	function self:getFrameEntries ()
+		return ENTRIES
+	end
+
+	function self:setFrame (frame, time, value1, value2)
+		frame = frame * ENTRIES
+		self.frames[frame] = time
+		self.frames[frame + VALUE1] = value1
+		self.frames[frame + VALUE2] = value2
+	end
+
+	return self
+end
+
+Animation.RotateTimeline = {}
+function Animation.RotateTimeline.new (frameCount, bezierCount, boneIndex)
+	local self = Animation.CurveTimeline1.new(frameCount, bezierCount, Property.rotate.."|"..boneIndex)
+	self.boneIndex = boneIndex
+
+	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
+		local bone = skeleton.bones[self.boneIndex]
+		if not bone.active then return end
+
+		local frames = self.frames
+		if time < frames[0] then
+			if blend == MixBlend.setup then
+				bone.rotation = bone.data.rotation
+			elseif blend == MixBlend.first then
+				bone.rotation = bone.rotation + (bone.data.rotation - bone.rotation) * alpha
+			end
+			return
+		end
+
+		local r = self:getCurveValue(time)
+		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
+		elseif blend == MixBlend.add then
+			bone.rotation = bone.rotation + r * alpha
+		end
+	end
+
+	return self
+end
+
+Animation.TranslateTimeline = {}
+function Animation.TranslateTimeline.new (frameCount, bezierCount, boneIndex)
+	local self = Animation.CurveTimeline2.new(frameCount, bezierCount,
+		Property.x.."|"..boneIndex,
+		Property.y.."|"..boneIndex
+	)
+	self.boneIndex = boneIndex
+
+	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
+		local bone = skeleton.bones[self.boneIndex]
+		if not bone.active then return end
+
+		local frames = self.frames
+		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
+		local frame = search2(frames, time, ENTRIES)
+		local curveType = self.curves[math_floor(i / ENTRIES)]
+		if curveType == LINEAR then
+			local before = frames[i]
+			x = frames[i + VALUE1]
+			y = frames[i + VALUE2]
+			local t = (time - before) / (frames[i + ENTRIES] - before)
+			x = x + (frames[i + ENTRIES + VALUE1] - x) * t
+			y = y + (frames[i + ENTRIES + VALUE2] - y) * t
+		elseif curveType == STEPPED then
+			x = frames[i + VALUE1]
+			y = frames[i + VALUE2]
+		else
+			x = self:getBezierValue(time, i, VALUE1, curveType - BEZIER)
+			y = self:getBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER)
+		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.TranslateXTimeline = {}
+function Animation.TranslateXTimeline.new (frameCount, bezierCount, boneIndex)
+	local self = Animation.CurveTimeline1.new(frameCount, bezierCount, Property.x.."|"..boneIndex)
+	self.boneIndex = boneIndex
+
+	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
+		local bone = skeleton.bones[self.boneIndex]
+		if not bone.active then return end
+
+		local frames = self.frames
+		if time < frames[0] then
+			if blend == MixBlend.setup then
+				bone.x = bone.data.x
+			elseif blend == MixBlend.first then
+				bone.x = bone.x + (bone.data.x - bone.x) * alpha
+			end
+			return
+		end
+
+		local x = self:getCurveValue(time)
+		if blend == MixBlend.setup then
+			bone.x = bone.data.x + x * alpha
+		elseif blend == MixBlend.first or blend == MixBlend.replace then
+			bone.x = bone.x + (bone.data.x + x - bone.x) * alpha
+		elseif blend == MixBlend.add then
+			bone.x = bone.x + x * alpha
+		end
+	end
+
+	return self
+end
+
+Animation.TranslateYTimeline = {}
+function Animation.TranslateYTimeline.new (frameCount, bezierCount, boneIndex)
+	local self = Animation.CurveTimeline1.new(frameCount, bezierCount, Property.x.."|"..boneIndex)
+	self.boneIndex = boneIndex
+
+	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
+		local bone = skeleton.bones[self.boneIndex]
+		if not bone.active then return end
+
+		local frames = self.frames
+		if time < frames[0] then
+			if blend == MixBlend.setup then
+				bone.y = bone.data.y
+			elseif blend == MixBlend.first then
+				bone.y = bone.y + (bone.data.y - bone.y) * alpha
+			end
+			return
+		end
+
+		local y = self:getCurveValue(time)
+		if blend == MixBlend.setup then
+			bone.y = bone.data.y + y * alpha
+		elseif blend == MixBlend.first or blend == MixBlend.replace then
+			bone.y = bone.y + (bone.data.y + y - bone.y) * alpha
+		elseif blend == MixBlend.add then
+			bone.y = bone.y + y * alpha
+		end
+	end
+
+	return self
+end
+
+Animation.ScaleTimeline = {}
+function Animation.ScaleTimeline.new (frameCount, bezierCount, boneIndex)
+	local self = Animation.CurveTimeline2.new(frameCount, bezierCount,
+		Property.scaleX.."|"..boneIndex,
+		Property.scaleY.."|"..boneIndex
+	)
+	self.boneIndex = boneIndex
+
+	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
+		local bone = skeleton.bones[self.boneIndex]
+		if not bone.active then return end
+
+		local frames = self.frames
+		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
+		local i = search2(frames, time, ENTRIES)
+		local curveType = self.curves[math_floor(i / ENTRIES)]
+		if curveType == LINEAR then
+			local before = frames[i]
+			x = frames[i + VALUE1]
+			y = frames[i + VALUE2]
+			local t = (time - before) / (frames[i + ENTRIES] - before)
+			x = x + (frames[i + ENTRIES + VALUE1] - x) * t
+			y = y + (frames[i + ENTRIES + VALUE2] - y) * t
+		elseif curveType == STEPPED then
+			x = frames[i + VALUE1]
+			y = frames[i + VALUE2]
+		else
+			x = self:getBezierValue(time, i, VALUE1, curveType - BEZIER)
+			y = self:getBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER)
+		end
+		x = x * bone.data.scaleX
+		y = y * bone.data.scaleY
+
+		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.mixOut 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.ScaleXTimeline = {}
+function Animation.ScaleXTimeline.new (frameCount, bezierCount, boneIndex)
+	local self = Animation.CurveTimeline1.new(frameCount, bezierCount, Property.scaleX.."|"..boneIndex)
+	self.boneIndex = boneIndex
+
+	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
+		local bone = skeleton.bones[self.boneIndex]
+		if not bone.active then return end
+
+		local frames = self.frames
+		if time < frames[0] then
+			if blend == MixBlend.setup then
+				bone.scaleX = bone.data.scaleX
+			elseif blend == MixBlend.first then
+				bone.scaleX = bone.scaleX + (bone.data.scaleX - bone.scaleX) * alpha
+			end
+			return
+		end
+
+		local x = self:getCurveValue(time) * bone.data.scaleX
+		if alpha == 1 then
+			if blend == MixBlend.add then
+				bone.scaleX = bone.scaleX + x - bone.data.scaleX
+			else
+				bone.scaleX = x
+			end
+		else
+			local bx = 0
+			if direction == MixDirection.mixOut then
+				if blend == MixBlend.setup then
+					bx = bone.data.scaleX
+					bone.scaleX = bx + (math_abs(x) * math_signum(bx) - bx) * alpha
+				elseif blend == MixBlend.first or blend == MixBlend.replace then
+					bx = bone.scaleX
+					bone.scaleX = bx + (math_abs(x) * math_signum(bx) - bx) * alpha
+				elseif blend == MixBlend.add then
+					bx = bone.scaleX
+					bone.scaleX = bx + (math_abs(x) * math_signum(bx) - bone.data.scaleX) * alpha
+				end
+			else
+				if blend == MixBlend.setup then
+					bx = math_abs(bone.data.scaleX) * math_signum(x)
+					bone.scaleX = bx + (x - bx) * alpha
+				elseif blend == MixBlend.first or blend == MixBlend.replace then
+					bx = math_abs(bone.scaleX) * math_signum(x)
+					bone.scaleX = bx + (x - bx) * alpha
+				elseif blend == MixBlend.add then
+					bx = math_signum(x)
+					bone.scaleX = math_abs(bone.scaleX) * bx + (x - math_abs(bone.data.scaleX) * bx) * alpha
+				end
+			end
+		end
+	end
+
+	return self
+end
+
+Animation.ScaleYTimeline = {}
+function Animation.ScaleYTimeline.new (frameCount, bezierCount, boneIndex)
+	local self = Animation.CurveTimeline1.new(frameCount, bezierCount, Property.scaleY.."|"..boneIndex)
+	self.boneIndex = boneIndex
+
+	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
+		local bone = skeleton.bones[self.boneIndex]
+		if not bone.active then return end
+
+		local frames = self.frames
+		if time < frames[0] then
+			if blend == MixBlend.setup then
+				bone.scaleY = bone.data.scaleY
+			elseif blend == MixBlend.first then
+				bone.scaleY = bone.scaleY + (bone.data.scaleY - bone.scaleY) * alpha
+			end
+			return
+		end
+
+		local y = self:getCurveValue(time) * bone.data.scaleY
+		if alpha == 1 then
+			if blend == MixBlend.add then
+				bone.scaleY = bone.scaleY + y - bone.data.scaleY
+			else
+				bone.scaleY = y
+			end
+		else
+			local by = 0
+			if direction == MixDirection.mixOut then
+				if blend == MixBlend.setup then
+					by = bone.data.scaleY
+					bone.scaleY = by + (math_abs(y) * math_signum(by) - by) * alpha
+				elseif blend == MixBlend.first or blend == MixBlend.replace then
+					by = bone.scaleY
+					bone.scaleY = by + (math_abs(y) * math_signum(by) - by) * alpha
+				elseif blend == MixBlend.add then
+					by = bone.scaleY
+					bone.scaleY = by + (math_abs(y) * math_signum(by) - bone.data.scaleY) * alpha
+				end
+			else
+				if blend == MixBlend.setup then
+					by = math_abs(bone.data.scaleY) * math_signum(y)
+					bone.scaleY = by + (y - by) * alpha
+				elseif blend == MixBlend.first or blend == MixBlend.replace then
+					by = math_abs(bone.scaleY) * math_signum(y)
+					bone.scaleY = by + (y - by) * alpha
+				elseif blend == MixBlend.add then
+					by = math_signum(y)
+					bone.scaleY = math_abs(bone.scaleY) * by + (y - math_abs(bone.data.scaleY) * by) * alpha
+				end
+			end
+		end
+	end
+
+	return self
+end
+
+Animation.ShearTimeline = {}
+function Animation.ShearTimeline.new (frameCount)
+	local self = Animation.CurveTimeline2.new(frameCount, bezierCount,
+		Property.shearX.."|"..boneIndex,
+		Property.shearY.."|"..boneIndex
+	)
+	self.boneIndex = boneIndex
+
+	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
+		local bone = skeleton.bones[self.boneIndex]
+		if not bone.active then return end
+
+		local frames = self.frames
+		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
+		local i = search2(frames, time, ENTRIES)
+		local curveType = self.curves[math_floor(i / ENTRIES)]
+		if curveType == LINEAR then
+			local before = frames[i]
+			x = frames[i + VALUE1]
+			y = frames[i + VALUE2]
+			local t = (time - before) / (frames[i + ENTRIES] - before)
+			x = x + (frames[i + ENTRIES + VALUE1] - x) * t
+			y = y + (frames[i + ENTRIES + VALUE2] - y) * t
+		elseif curveType == STEPPED then
+			x = frames[i + VALUE1]
+			y = frames[i + VALUE2]
+		else
+			x = self:getBezierValue(time, i, VALUE1, curveType - BEZIER)
+			y = self:getBezierValue(time, i, VALUE2, curveType + BEZIER_SIZE - BEZIER)
+		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.ShearXTimeline = {}
+function Animation.ShearXTimeline.new (frameCount)
+	local self = Animation.CurveTimeline1.new(frameCount, bezierCount, Property.shearX.."|"..boneIndex)
+	self.boneIndex = boneIndex
+
+	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
+		local bone = skeleton.bones[self.boneIndex]
+		if not bone.active then return end
+
+		local frames = self.frames
+		if time < frames[0] then
+			if blend == MixBlend.setup then
+				bone.shearX = bone.data.shearX
+			elseif blend == MixBlend.first then
+				bone.shearX = bone.shearX + (bone.data.shearX - bone.shearX) * alpha
+			end
+			return
+		end
+
+		local x = self:getCurveValue(time)
+		if blend == MixBlend.setup then
+			bone.shearX = bone.data.shearX + x * alpha
+		elseif blend == MixBlend.first or blend == MixBlend.replace then
+			bone.shearX = bone.shearX + (bone.data.shearX + x - bone.shearX) * alpha
+		elseif blend == MixBlend.add then
+			bone.shearX = bone.shearX + x * alpha
+		end
+	end
+
+	return self
+end
+
+Animation.ShearYTimeline = {}
+function Animation.ShearYTimeline.new (frameCount)
+	local self = Animation.CurveTimeline1.new(frameCount, bezierCount, Property.shearY.."|"..boneIndex)
+	self.boneIndex = boneIndex
+
+	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
+		local bone = skeleton.bones[self.boneIndex]
+		if not bone.active then return end
+
+		local frames = self.frames
+		if time < frames[0] then
+			if blend == MixBlend.setup then
+				bone.shearY = bone.data.shearY
+			elseif blend == MixBlend.first then
+				bone.shearY = bone.shearX + (bone.data.shearY - bone.shearY) * alpha
+			end
+			return
+		end
+
+		local y = self:getCurveValue(time)
+		if blend == MixBlend.setup then
+			bone.shearY = bone.data.shearY + y * alpha
+		elseif blend == MixBlend.first or blend == MixBlend.replace then
+			bone.shearY = bone.shearY + (bone.data.shearY + y - bone.shearY) * alpha
+		elseif blend == MixBlend.add then
+			bone.shearY = bone.shearY + y * alpha
+		end
+	end
+
+	return self
+end
+
+Animation.RGBATimeline = {}
+function Animation.RGBATimeline.new (frameCount, bezierCount, slotIndex)
+	local ENTRIES = 5
+	local R = 1
+	local G = 2
+	local B = 3
+	local A = 4
+
+	local self = Animation.CurveTimeline.new(frameCount, bezierCount, {
+		Property.rgb.."|"..slotIndex,
+		Property.alpha.."|"..slotIndex
+	})
+	self.slotIndex = slotIndex
+	
+	function self:getFrameEntries ()
+		return ENTRIES
+	end
+
+	function self:setFrame (frame, time, r, g, b, a)
+		frame = frame * ENTRIES
+		self.frames[frame] = time
+		self.frames[frame + R] = r
+		self.frames[frame + G] = g
+		self.frames[frame + B] = b
+		self.frames[frame + A] = a
+	end
+
+	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
+		local slot = skeleton.slots[self.slotIndex]
+		if not slot.bone.active then return end
+
+		local frames = self.frames
+		local color = slot.color
+		if time < frames[0] then
+			local setup = slot.data.color
+			if blend == MixBlend.setup then
+				color:setFrom(setup)
+			elseif blend == MixBlend.first then
+				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
+		local i = search2(frames, time, ENTRIES)
+		local curveType = self.curves[i / ENTRIES]
+		if curveType == LINEAR then
+			local before = frames[i]
+			r = frames[i + R]
+			g = frames[i + G]
+			b = frames[i + B]
+			a = frames[i + A]
+			local t = (time - before) / (frames[i + ENTRIES] - before)
+			r = r + (frames[i + ENTRIES + R] - r) * t
+			g = g + (frames[i + ENTRIES + G] - g) * t
+			b = b + (frames[i + ENTRIES + B] - b) * t
+			a = a + (frames[i + ENTRIES + A] - a) * t
+		elseif curveType == STEPPED then
+			r = frames[i + R]
+			g = frames[i + G]
+			b = frames[i + B]
+			a = frames[i + A]
+		else
+			r = self:getBezierValue(time, i, R, curveType - BEZIER)
+			g = self:getBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER)
+			b = self:getBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER)
+			a = self:getBezierValue(time, i, A, curveType + BEZIER_SIZE * 3 - BEZIER)
+		end
+
+		if alpha == 1 then
+			color:set(r, g, b, a)
+		else
+			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.RGBTimeline = {}
+function Animation.RGBTimeline.new (frameCount, bezierCount, slotIndex)
+	local ENTRIES = 4
+	local R = 1
+	local G = 2
+	local B = 3
+
+	local self = Animation.CurveTimeline.new(frameCount, bezierCount, { Property.rgb.."|"..slotIndex })
+	self.slotIndex = slotIndex
+	
+	function self:getFrameEntries ()
+		return ENTRIES
+	end
+
+	function self:setFrame (frame, time, r, g, b)
+		frame = frame * ENTRIES
+		self.frames[frame] = time
+		self.frames[frame + R] = r
+		self.frames[frame + G] = g
+		self.frames[frame + B] = b
+	end
+
+	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
+		local slot = skeleton.slots[self.slotIndex]
+		if not slot.bone.active then return end
+
+		local frames = self.frames
+		local color = slot.color
+		if time < frames[0] then
+			local setup = slot.data.color
+			if blend == MixBlend.setup then
+				color.r = setup.r
+				color.g = setup.g
+				color.b = setup.b
+			elseif blend == MixBlend.first then
+				color.r = color.r + (setup.r - color.r) * alpha
+				color.g = color.g + (setup.g - color.g) * alpha
+				color.b = color.b + (setup.b - color.b) * alpha
+			end
+			return
+		end
+
+		local r, g, b
+		local i = search2(frames, time, ENTRIES)
+		local curveType = self.curves[i / ENTRIES]
+		if curveType == LINEAR then
+			local before = frames[i]
+			r = frames[i + R]
+			g = frames[i + G]
+			b = frames[i + B]
+			local t = (time - before) / (frames[i + ENTRIES] - before)
+			r = r + (frames[i + ENTRIES + R] - r) * t
+			g = g + (frames[i + ENTRIES + G] - g) * t
+			b = b + (frames[i + ENTRIES + B] - b) * t
+		elseif curveType == STEPPED then
+			r = frames[i + R]
+			g = frames[i + G]
+			b = frames[i + B]
+		else
+			r = self:getBezierValue(time, i, R, curveType - BEZIER)
+			g = self:getBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER)
+			b = self:getBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER)
+		end
+
+		if alpha == 1 then
+			color.r = r
+			color.g = g
+			color.b = b
+		else
+			if blend == MixBlend.setup then
+				local setup = slot.data.color
+				color.r = setup.r
+				color.g = setup.g
+				color.b = setup.b
+			end
+			color.r = color.r + (r - color.r) * alpha
+			color.g = color.g + (g - color.g) * alpha
+			color.b = color.b + (b - color.b) * alpha
+		end
+	end
+
+	return self
+end
+
+Animation.AlphaTimeline = {}
+function Animation.AlphaTimeline.new (frameCount, bezierCount, slotIndex)
+	local self = Animation.CurveTimeline1.new(frameCount, bezierCount, Property.alpha.."|"..slotIndex)
+	self.slotIndex = slotIndex
+
+	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
+		local slot = skeleton.slots[self.slotIndex]
+		if not slot.bone.active then return end
+
+		local color = slot.color
+		if time < frames[0] then
+			local setup = slot.data.color
+			if blend == MixBlend.setup then
+				color.a = setup.a
+				return
+			else
+				color.a = color.a + (setup.a - color.a) * alpha
+			end
+			return
+		end
+
+		local a = self:getCurveValue(time)
+		if alpha == 1 then
+			color.a = a
+		else
+			if blend == MixBlend.setup then color.a = slot.data.color.a end
+			color.a = color.a + (a - color.a) * alpha
+		end
+	end
+
+	return self
+end
+
+Animation.RGBA2Timeline = {}
+function Animation.RGBA2Timeline.new (frameCount, bezierCount, slotIndex)
+	local ENTRIES = 8
+	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, bezierCount, {
+		Property.rgb.."|"..slotIndex,
+		Property.alpha.."|"..slotIndex,
+		Property.rgb2.."|"..slotIndex
+	})
+	self.slotIndex = slotIndex
+	
+	function self:getFrameEntries ()
+		return ENTRIES
+	end
+
+	function self:setFrame (frame, time, r, g, b, a, r2, g2, b2)
+		frame = frame * ENTRIES
+		self.frames[frame] = time
+		self.frames[frame + R] = r
+		self.frames[frame + G] = g
+		self.frames[frame + B] = b
+		self.frames[frame + A] = a
+		self.frames[frame + R2] = r2
+		self.frames[frame + G2] = g2
+		self.frames[frame + B2] = b2
+	end
+
+	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
+		local slot = skeleton.slots[self.slotIndex]
+		if not slot.bone.active then return end
+
+		local frames = self.frames
+		local light = slot.color
+		local dark = slot.darkColor
+		if time < frames[0] then
+			local setupLight = slot.data.color
+			local setupDark = slot.data.darkColor
+			if blend == MixBlend.setup then
+				light:setFrom(setupLight)
+				dark.r = setupDark.r
+				dark.g = setupDark.g
+				dark.b = setupDark.b
+			elseif blend == MixBlend.first then
+				light:add((setupLight.r - light.r) * alpha, (setupLight.g - light.g) * alpha, (setupLight.b - light.b) * alpha,
+					(setupLight.a - light.a) * alpha)
+				dark.r = dark.r + (setupDark.r - dark.r) * alpha
+				dark.g = dark.g + (setupDark.g - dark.g) * alpha
+				dark.b = dark.b + (setupDark.b - dark.b) * alpha
+			end
+			return
+		end
+
+		local r, g, b, a, r2, g2, b2
+		local i = search2(frames, time, ENTRIES)
+		local curveType = self.curves[math_floor(i / ENTRIES)]
+		if curveType == LINEAR then
+			local before = frames[i]
+			r = frames[i + R]
+			g = frames[i + G]
+			b = frames[i + B]
+			a = frames[i + A]
+			r2 = frames[i + R2]
+			g2 = frames[i + G2]
+			b2 = frames[i + B2]
+			local t = (time - before) / (frames[i + ENTRIES] - before)
+			r = r + (frames[i + ENTRIES + R] - r) * t
+			g = g + (frames[i + ENTRIES + G] - g) * t
+			b = b + (frames[i + ENTRIES + B] - b) * t
+			a = a + (frames[i + ENTRIES + A] - a) * t
+			r2 = r2 + (frames[i + ENTRIES + R2] - r2) * t
+			g2 = g2 + (frames[i + ENTRIES + G2] - g2) * t
+			b2 = b2 + (frames[i + ENTRIES + B2] - b2) * t
+		elseif curveType == STEPPED then
+			r = frames[i + R]
+			g = frames[i + G]
+			b = frames[i + B]
+			a = frames[i + A]
+			r2 = frames[i + R2]
+			g2 = frames[i + G2]
+			b2 = frames[i + B2]
+		else
+			r = self:getBezierValue(time, i, R, curveType - BEZIER)
+			g = self:getBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER)
+			b = self:getBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER)
+			a = self:getBezierValue(time, i, A, curveType + BEZIER_SIZE * 3 - BEZIER)
+			r2 = self:getBezierValue(time, i, R2, curveType + BEZIER_SIZE * 4 - BEZIER)
+			g2 = self:getBezierValue(time, i, G2, curveType + BEZIER_SIZE * 5 - BEZIER)
+			b2 = self:getBezierValue(time, i, B2, curveType + BEZIER_SIZE * 6 - BEZIER)
+		end
+
+		if alpha == 1 then
+			light:set(r, g, b, a)
+			dark.r = r2
+			dark.g = g2
+			dark.b = b2
+		else
+			if blend == MixBlend.setup then
+				light:setFrom(slot.data.color)
+				local setupDark = slot.data.darkColor
+				dark.r = setupDark.r
+				dark.g = setupDark.g
+				dark.b = setupDark.b
+			end
+			light:add((r - light.r) * alpha, (g - light.g) * alpha, (b - light.b) * alpha, (a - light.a) * alpha)
+			dark.r = dark.r + (r2 - dark.r) * alpha
+			dark.g = dark.g + (g2 - dark.g) * alpha
+			dark.b = dark.b + (b2 - dark.b) * alpha
+		end
+	end
+
+	return self
+end
+
+Animation.RGB2Timeline = {}
+function Animation.RGB2Timeline.new (frameCount, bezierCount, slotIndex)
+	local ENTRIES = 7
+	local R = 1
+	local G = 2
+	local B = 3
+	local R2 = 4
+	local G2 = 5
+	local B2 = 6
+
+	local self = Animation.CurveTimeline.new(frameCount, bezierCount, {
+		Property.rgb.."|"..slotIndex,
+		Property.rgb2.."|"..slotIndex
+	})
+	self.slotIndex = slotIndex
+
+	function self:getFrameEntries ()
+		return ENTRIES
+	end
+
+	function self:setFrame (frame, time, r, g, b, r2, g2, b2)
+		frame = frame * ENTRIES
+		self.frames[frame] = time
+		self.frames[frame + R] = r
+		self.frames[frame + G] = g
+		self.frames[frame + B] = b
+		self.frames[frame + R2] = r2
+		self.frames[frame + G2] = g2
+		self.frames[frame + B2] = b2
+	end
+
+	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
+		local slot = skeleton.slots[self.slotIndex]
+		if not slot.bone.active then return end
+
+		local frames = self.frames
+		local light = slot.color
+		local dark = slot.darkColor
+		if time < frames[0] then
+			local setupLight = slot.data.color
+			local setupDark = slot.data.darkColor
+			if blend == MixBlend.setup then
+				light.r = setupLight.r
+				light.g = setupLight.g
+				light.b = setupLight.b
+				dark.r = setupDark.r
+				dark.g = setupDark.g
+				dark.b = setupDark.b
+			elseif blend == MixBlend.first then
+				light.r = light.r + (setupLight.r - light.r) * alpha
+				light.g = light.g + (setupLight.g - light.g) * alpha
+				light.b = light.b + (setupLight.b - light.b) * alpha
+				dark.r = dark.r + (setupDark.r - dark.r) * alpha
+				dark.g = dark.g + (setupDark.g - dark.g) * alpha
+				dark.b = dark.b + (setupDark.b - dark.b) * alpha
+			end
+			return
+		end
+
+		local r, g, b, r2, g2, b2
+		local i = search2(frames, time, ENTRIES)
+		local curveType = self.curves[math_floor(i / ENTRIES)]
+		if curveType == LINEAR then
+			local before = frames[i]
+			r = frames[i + R]
+			g = frames[i + G]
+			b = frames[i + B]
+			r2 = frames[i + R2]
+			g2 = frames[i + G2]
+			b2 = frames[i + B2]
+			local t = (time - before) / (frames[i + ENTRIES] - before)
+			r = r + (frames[i + ENTRIES + R] - r) * t
+			g = g + (frames[i + ENTRIES + G] - g) * t
+			b = b + (frames[i + ENTRIES + B] - b) * t
+			r2 = r2 + (frames[i + ENTRIES + R2] - r2) * t
+			g2 = g2 + (frames[i + ENTRIES + G2] - g2) * t
+			b2 = b2 + (frames[i + ENTRIES + B2] - b2) * t
+		elseif curveType == STEPPED then
+			r = frames[i + R]
+			g = frames[i + G]
+			b = frames[i + B]
+			r2 = frames[i + R2]
+			g2 = frames[i + G2]
+			b2 = frames[i + B2]
+		else
+			r = self:getBezierValue(time, i, R, curveType - BEZIER)
+			g = self:getBezierValue(time, i, G, curveType + BEZIER_SIZE - BEZIER)
+			b = self:getBezierValue(time, i, B, curveType + BEZIER_SIZE * 2 - BEZIER)
+			r2 = self:getBezierValue(time, i, R2, curveType + BEZIER_SIZE * 4 - BEZIER)
+			g2 = self:getBezierValue(time, i, G2, curveType + BEZIER_SIZE * 5 - BEZIER)
+			b2 = self:getBezierValue(time, i, B2, curveType + BEZIER_SIZE * 6 - BEZIER)
+		end
+
+		if alpha == 1 then
+			light.r = r
+			light.g = g
+			light.b = b
+			dark.r = r2
+			dark.g = g2
+			dark.b = b2
+		else
+			if blend == MixBlend.setup then
+				local setupLight = slot.data.color
+				local setupDark = slot.data.darkColor
+				light.r = setupLight.r
+				light.g = setupLight.g
+				light.b = setupLight.b
+				dark.r = setupDark.r
+				dark.g = setupDark.g
+				dark.b = setupDark.b
+			end
+			light.r = light.r + (r - light.r) * alpha
+			light.g = light.g + (g - light.g) * alpha
+			light.b = light.b + (b - light.b) * alpha
+			dark.r = dark.r + (r2 - dark.r) * alpha
+			dark.g = dark.g + (g2 - dark.g) * alpha
+			dark.b = dark.b + (b2 - dark.b) * alpha
+		end
+	end
+
+	return self
+end
+
+Animation.AttachmentTimeline = {}
+function Animation.AttachmentTimeline.new (frameCount, bezierCount, slotIndex)
+	local self = Animation.Timeline.new(frameCount, { Property.attachment + "|" + slotIndex })
+	self.slotIndex = slotIndex
+	self.attachmentNames = {}
+
+	function self:getFrameCount ()
+		return zlen(self.frames)
+	end
+
+	function self:setFrame (frame, time, attachmentName)
+		self.frames[frame] = time
+		self.attachmentNames[frame] = attachmentName
+	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
+
+	local function setAttachment (skeleton, slot, attachmentName)
+		local attachmentName = self.attachmentNames[frameIndex]
+		if not attachmentName then
+			slot:setAttachment(nil)
+		else
+			slot:setAttachment(skeleton:getAttachmentByIndex(self.slotIndex, attachmentName))
+		end
+	end
+
+	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
+		local slot = skeleton.slots[self.slotIndex]
+		if not slot.bone.active then return end
+
+		if direction == MixDirection.mixOut then
+			if blend == MixBlend.setup then
+				self:setAttachment(skeleton, slot, slot.data.attachmentName)
+			end
+			return
+		end
+
+		if time < self.frames[0] then
+			if blend == MixBlend.setup or blend == MixBlend.first then
+				self:setAttachment(skeleton, slot, slot.data.attachmentName)
+			end
+			return
+		end
+
+		setAttachment(skeleton, slot, self.attachmentNames[search1(self.frames, time)])
+	end
+
+	return self
+end
+
+Animation.DeformTimeline = {}
+function Animation.DeformTimeline.new (frameCount, bezierCount, slotIndex, attachment)
+	local self = Animation.CurveTimeline.new(frameCount, bezierCount, { Property.deform + "|" + slotIndex + "|" + attachment.id })
+	self.slotIndex = slotIndex
+	self.attachment = attachment
+	self.vertices = {}
+
+	function self:getFrameCount ()
+		return zlen(self.frames)
+	end
+
+	function self:setFrame (frame, time, vertices)
+		self.frames[frame] = time
+		self.vertices[frame] = vertices
+	end
+
+	function self:setBezier (bezier, frame, value, time1, value1, cx1, cy1, cx2, cy2, time2, value2)
+		local curves = self.curves
+		local i = self:getFrameCount() + bezier * BEZIER_SIZE
+		if value == 0 then curves[frame] = BEZIER + i end
+		local tmpx = (time1 - cx1 * 2 + cx2) * 0.03
+		local tmpy = cy2 * 0.03 - cy1 * 0.06
+		local dddx = ((cx1 - cx2) * 3 - time1 + time2) * 0.006
+		local dddy = (cy1 - cy2 + 0.33333333) * 0.018
+		local ddx = tmpx * 2 + dddx
+		local ddy = tmpy * 2 + dddy
+		local dx = (cx1 - time1) * 0.3 + tmpx + dddx * 0.16666667
+		local dy = cy1 * 0.3 + tmpy + dddy * 0.16666667
+		local x = time1 + dx
+		local y = dy
+		local n = i + BEZIER_SIZE
+		while i < n do
+			curves[i] = x
+			curves[i + 1] = y
+			dx = dx + ddx
+			dy = dy + ddy
+			ddx = ddx + dddx
+			ddy = ddy + dddy
+			x = x + dx
+			y = y + dy
+			i = i + 2
+		end
+	end
+
+	function getCurvePercent (time, frame)
+		local curves = self.curves
+		local i = curves[frame]
+		if i == LINEAR then
+			local x = self.frames[frame]
+			return (time - x) / (self.frames[frame + self:getFrameEntries()] - x)
+		elseif i == STEPPED then
+			return 0
+		end
+		i = i - BEZIER
+		if curves[i] > time then
+			local x = self.frames[frame]
+			return curves[i + 1] * (time - x) / (curves[i] - x)
+		end
+		local n = i + BEZIER_SIZE
+		i = i + 2
+		while i < n do
+			if curves[i] >= time then
+				local x = curves[i - 2]
+				local y = curves[i - 1]
+				return y + (time - x) / (curves[i] - x) * (curves[i + 1] - y)
+			end
+			i = i + 2
+		end
+		local x = curves[n - 2]
+		local y = curves[n - 1]
+		return y + (1 - y) * (time - x) / (self.frames[frame + self:getFrameEntries()] - x)
+	end
+
+	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
+		local slot = skeleton.slots[self.slotIndex]
+		if not slot.bone.active then return end
+
+		local vertexAttachment = slot.attachment
+		if not vertexAttachment or not vertexAttachment.vertexAttachment or vertexAttachment.deformAttachment ~= self.attachment then return end
+
+		local frames = self.frames
+		local deform = slot.deform
+		if #deform == 0 then blend = MixBlend.setup end
+
+		local vertices = self.vertices
+		local vertexCount = #(vertices[0])
+
+		if time < frames[0] then
+			if blend == MixBlend.setup then
+				slot.deform = {}
+				return
+			elseif blend == MixBlend.first then
+				if alpha == 1 then
+					slot.deform = {}
+					return
+				end
+				utils.setArraySize(deform, vertexCount)
+				if not vertexAttachment.bones 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
+
+		utils.setArraySize(deform, vertexCount)
+		if time >= frames[zlen(frames) - 1] then -- Time is after last frame.
+			local lastVertices = vertices[zlen(frames) - 1]
+			if alpha == 1 then
+				if blend == MixBlend.add then
+					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
+					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
+					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
+					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 = search1(frames, time)
+		local percent = self:getCurvePercent(time, frame)
+		local prevVertices = vertices[frame]
+		local nextVertices = vertices[frame + 1]
+
+		if alpha == 1 then
+			if blend == MixBlend.add then
+				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
+				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
+				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 = {}
+local eventPropertyIds = { Property.event }
+function Animation.EventTimeline.new (frameCount)
+	local self = Animation.Timeline.new(frameCount, eventPropertyIds)
+	self.events = {}
+
+	function self:getFrameCount ()
+		return zlen(self.frames)
+	end
+
+	function self:setFrame (frame, event)
+		self.frames[frame] = event.time
+		self.events[frame] = 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 i
+		if lastTime < frames[0] then
+			i = 0
+		else
+			i = binarySearch1(frames, lastTime)
+			local i = frames[i]
+			while i > 0 do -- Fire multiple events with the same frame.
+				if frames[i - 1] ~= i then break end
+				i = i - 1
+			end
+		end
+		while i < frameCount and time >= frames[i] do
+			table.insert(firedEvents, self.events[i])
+			i = i + 1
+		end
+	end
+
+	return self
+end
+
+Animation.DrawOrderTimeline = {}
+local drawOrderPropertyIds = { Property.drawOrder }
+function Animation.DrawOrderTimeline.new (frameCount)
+	local self = Animation.Timeline.new(frameCount, drawOrderPropertyIds)
+	self.drawOrders = {}
+
+	function self:getFrameCount ()
+		return zlen(self.frames)
+	end
+
+	function self:setFrame (frame, time, drawOrder)
+		self.frames[frame] = time
+		self.drawOrders[frame] = drawOrder
+	end
+
+	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
+		if direction == MixDirection.mixOut then
+			if blend == MixBlend.setup then
+				for i,slot in ipairs(slots) do
+					drawOrder[i] = slots[i]
+				end
+			end
+			return
+		end
+
+		if time < self.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 drawOrderToSetupIndex = self.drawOrders[search1(self.frames, time)]
+		if not drawOrderToSetupIndex then
+			for i,slot in ipairs(slots) do
+				drawOrder[i] = slots[i]
+			end
+		else
+			local drawOrder = skeleton.drawOrder
+			local slots = skeleton.slots
+			for i,setupIndex in ipairs(drawOrderToSetupIndex) do
+				drawOrder[i] = skeleton.slots[setupIndex]
+			end
+		end
+	end
+
+	return self
+end
+
+Animation.IkConstraintTimeline = {}
+function Animation.IkConstraintTimeline.new (frameCount, bezierCount, ikConstraintIndex)
+	local ENTRIES = 6
+	local MIX = 1
+	local SOFTNESS = 2
+	local BEND_DIRECTION = 3
+	local COMPRESS = 4
+	local STRETCH = 5
+
+	local self = Animation.CurveTimeline.new(frameCount, bezierCount, { Property.ikConstraint + "|" + ikConstraintIndex })
+	self.ikConstraintIndex = ikConstraintIndex
+
+	function self:getFrameEntries ()
+		return ENTRIES
+	end
+
+	function self:setFrame (frame, time, mix, softness, bendDirection, compress, stretch)
+		frame = frame * ENTRIES
+		self.frames[frame] = time
+		self.frames[frame + MIX] = mix
+		self.frames[frame + SOFTNESS] = softness
+		self.frames[frame + BEND_DIRECTION] = bendDirection
+		if compress then
+			self.frames[frame + COMPRESS] = 1
+		else
+			self.frames[frame + COMPRESS] = 0
+		end
+		if stretch then
+			self.frames[frame + STRETCH] = 1
+		else
+			self.frames[frame + STRETCH] = 0
+		end
+	end
+
+	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
+		local constraint = skeleton.ikConstraints[self.ikConstraintIndex]
+		if not constraint.active then return end
+		
+		local frames = self.frames
+		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
+
+		local mix = 0
+		local softness = 0
+		local i = search(frames, time, ENTRIES)
+		local curveType = this.curves[i / ENTRIES]
+		if curveType == LINEAR then
+			local before = frames[i]
+			mix = frames[i + MIX]
+			softness = frames[i + SOFTNESS]
+			local t = (time - before) / (frames[i + ENTRIES] - before)
+			mix = mix + (frames[i + ENTRIES + MIX] - mix) * t
+			softness = softness + (frames[i + ENTRIES + SOFTNESS] - softness) * t
+		elseif curveType == STEPPED then
+			mix = frames[i + MIX]
+			softness = frames[i + SOFTNESS]
+		else
+			mix = self:getBezierValue(time, i, MIX, curveType - BEZIER)
+			softness = self:getBezierValue(time, i, SOFTNESS, curveType + BEZIER_SIZE - BEZIER)
+		end
+
+		if blend == MixBlend.setup then
+			constraint.mix = constraint.data.mix + (mix - constraint.data.mix) * alpha
+			constraint.softness = constraint.data.softness + (softness - constraint.data.softness) * alpha
+			if direction == MixDirection.mixOut then
+				constraint.bendDirection = constraint.data.bendDirection
+				constraint.compress = constraint.data.compress
+				constraint.stretch = constraint.data.stretch
+			else
+				constraint.bendDirection = math_floor(frames[i + BEND_DIRECTION])
+				if math_floor(frames[i + COMPRESS]) == 1 then constraint.compress = true else constraint.compress = false end
+				if math_floor(frames[i + STRETCH]) == 1 then constraint.stretch = true else constraint.stretch = false end
+			end
+		else
+			constraint.mix = constraint.mix + (mix - constraint.mix) * alpha
+			constraint.softness = constraint.softness + (softness - constraint.softness) * alpha
+			if direction == MixDirection.mixIn then
+				constraint.bendDirection = math_floor(frames[i + BEND_DIRECTION])
+				if math_floor(frames[i + COMPRESS]) == 1 then constraint.compress = true else constraint.compress = false end
+				if math_floor(frames[i + STRETCH]) == 1 then constraint.stretch = true else constraint.stretch = false end
+			end
+		end
+	end
+
+	return self
+end
+
+Animation.TransformConstraintTimeline = {}
+function Animation.TransformConstraintTimeline.new (frameCount, transformConstraintIndex)
+	local ENTRIES = 7
+	local ROTATE = 1
+	local X = 2
+	local Y = 3
+	local SCALEX = 4
+	local SCALEY = 5
+	local SHEARY = 6
+
+	local self = Animation.CurveTimeline.new(frameCount, bezierCount, { Property.transformConstraint + "|" + transformConstraintIndex })
+	self.transformConstraintIndex = transformConstraintIndex
+
+	function self:getFrameEntries ()
+		return ENTRIES
+	end
+
+	function self:setFrame (frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY)
+		frame = frame * ENTRIES
+		self.frames[frame] = time
+		self.frames[frame + ROTATE] = mixRotate
+		self.frames[frame + X] = mixX
+		self.frames[frame + Y] = mixY
+		self.frames[frame + SCALEX] = mixScaleX
+		self.frames[frame + SCALEY] = mixScaleY
+		self.frames[frame + SHEARY] = mixShearY
+	end
+
+	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
+		local constraint = skeleton.transformConstraints[self.transformConstraintIndex]
+		if not constraint.active then return end
+
+		local frames = self.frames
+		if time < frames[0] then
+			local data = constraint.data
+			if blend == MixBlend.setup then
+				constraint.mixRotate = data.mixRotate
+				constraint.mixX = data.mixX
+				constraint.mixY = data.mixY
+				constraint.mixScaleX = data.mixScaleX
+				constraint.mixScaleY = data.mixScaleY
+				constraint.mixShearY = data.mixShearY
+			elseif blend == MixBlend.first then
+				constraint.mixRotate = constraint.mixRotate + (data.mixRotate - constraint.mixRotate) * alpha
+				constraint.mixX = constraint.mixX + (data.mixX - constraint.mixX) * alpha
+				constraint.mixY = constraint.mixY + (data.mixY - constraint.mixY) * alpha
+				constraint.mixScaleX = constraint.mixScaleX + (data.mixScaleX - constraint.mixScaleX) * alpha
+				constraint.mixScaleY = constraint.mixScaleY + (data.mixScaleY - constraint.mixScaleY) * alpha
+				constraint.mixShearY = constraint.mixShearY + (data.mixShearY - constraint.mixShearY) * alpha
+			end
+			return
+		end
+
+		local rotate
+		local x
+		local y
+		local scaleX
+		local scaleY
+		local shearY
+		local i = search(frames, time, ENTRIES)
+		local curveType = this.curves[i / ENTRIES]
+		if curveType == LINEAR then
+			local before = frames[i]
+			rotate = frames[i + ROTATE]
+			x = frames[i + X]
+			y = frames[i + Y]
+			scaleX = frames[i + SCALEX]
+			scaleY = frames[i + SCALEY]
+			shearY = frames[i + SHEARY]
+			local t = (time - before) / (frames[i + ENTRIES] - before)
+			rotate = rotate + (frames[i + ENTRIES + ROTATE] - rotate) * t
+			x = x + (frames[i + ENTRIES + X] - x) * t
+			y = y + (frames[i + ENTRIES + Y] - y) * t
+			scaleX = scaleX + (frames[i + ENTRIES + SCALEX] - scaleX) * t
+			scaleY = scaleY + (frames[i + ENTRIES + SCALEY] - scaleY) * t
+			shearY = shearY + (frames[i + ENTRIES + SHEARY] - shearY) * t
+		elseif curveType == STEPPED then
+			rotate = frames[i + ROTATE]
+			x = frames[i + X]
+			y = frames[i + Y]
+			scaleX = frames[i + SCALEX]
+			scaleY = frames[i + SCALEY]
+			shearY = frames[i + SHEARY]
+		else
+			rotate = self:getBezierValue(time, i, ROTATE, curveType - BEZIER)
+			x = self:getBezierValue(time, i, X, curveType + BEZIER_SIZE - BEZIER)
+			y = self:getBezierValue(time, i, Y, curveType + BEZIER_SIZE * 2 - BEZIER)
+			scaleX = self:getBezierValue(time, i, SCALEX, curveType + BEZIER_SIZE * 3 - BEZIER)
+			scaleY = self:getBezierValue(time, i, SCALEY, curveType + BEZIER_SIZE * 4 - BEZIER)
+			shearY = self:getBezierValue(time, i, SHEARY, curveType + BEZIER_SIZE * 5 - BEZIER)
+		end
+
+		if blend == MixBlend.setup then
+			local data = constraint.data
+			constraint.mixRotate = data.mixRotate + (rotate - data.mixRotate) * alpha
+			constraint.mixX = data.mixX + (x - data.mixX) * alpha
+			constraint.mixY = data.mixY + (y - data.mixY) * alpha
+			constraint.mixScaleX = data.mixScaleX + (scaleX - data.mixScaleX) * alpha
+			constraint.mixScaleY = data.mixScaleY + (scaleY - data.mixScaleY) * alpha
+			constraint.mixShearY = data.mixShearY + (shearY - data.mixShearY) * alpha
+		else
+			constraint.mixRotate = constraint.mixRotate + (rotate - constraint.mixRotate) * alpha
+			constraint.mixX = constraint.mixX + (x - constraint.mixX) * alpha
+			constraint.mixY = constraint.mixY + (y - constraint.mixY) * alpha
+			constraint.mixScaleX = constraint.mixScaleX + (scaleX - constraint.mixScaleX) * alpha
+			constraint.mixScaleY = constraint.mixScaleY + (scaleY - constraint.mixScaleY) * alpha
+			constraint.mixShearY = constraint.mixShearY + (shearY - constraint.mixShearY) * alpha
+		end
+	end
+
+	return self
+end
+
+Animation.PathConstraintPositionTimeline = {}
+function Animation.PathConstraintPositionTimeline.new (frameCount, bezierCount, pathConstraintIndex)
+	local self = Animation.CurveTimeline1.new(frameCount, bezierCount, Property.pathConstraintPosition.."|"..pathConstraintIndex)
+	self.pathConstraintIndex = pathConstraintIndex
+
+	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
+		local constraint = skeleton.pathConstraints[self.pathConstraintIndex]
+		if not constraint.active then return end
+
+		local frames = self.frames
+		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 = self:getCurveValue(time)
+		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 = {}
+function Animation.PathConstraintSpacingTimeline.new (frameCount, bezierCount, pathConstraintIndex)
+	local self = Animation.CurveTimeline1.new(frameCount, bezierCount, Property.pathConstraintSpacing.."|"..pathConstraintIndex)
+	self.pathConstraintIndex = pathConstraintIndex
+
+	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
+		local constraint = skeleton.pathConstraints[self.pathConstraintIndex]
+		if not constraint.active then return end
+
+		local frames = self.frames
+		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 = self:getCurveValue(time)
+		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 = {}
+function Animation.PathConstraintMixTimeline.new (frameCount, bezierCount, pathConstraintIndex)
+	local ENTRIES = 4
+	local ROTATE = 1
+	local X = 2
+	local Y = 3
+
+	local self = Animation.CurveTimeline.new(frameCount, bezierCount, Property.pathConstraintMix.."|"..pathConstraintIndex)
+	self.pathConstraintIndex = pathConstraintIndex
+
+	function self:getFrameEntries ()
+		return ENTRIES
+	end
+
+	function self:setFrame (frame, time, mixRotate, mixX, mixY)
+		local frames = self.frames
+		frame = frame * ENTRIES
+		frames[frame] = time
+		frames[frame + ROTATE] = mixRotate
+		frames[frame + X] = mixX
+		frames[frame + Y] = mixY
+	end
+
+	function self:apply (skeleton, lastTime, time, events, alpha, blend, direction)
+		local constraint = skeleton.pathConstraints[self.pathConstraintIndex]
+		if not constraint.active then return end
+
+		local frames = self.frames
+		if time < frames[0] then
+			if blend == MixBlend.setup then
+				constraint.mixRotate = constraint.data.mixRotate;
+				constraint.mixX = constraint.data.mixX;
+				constraint.mixY = constraint.data.mixY;
+			elseif blend == MixBlend.first then
+				constraint.mixRotate = constraint.mixRotate + (constraint.data.mixRotate - constraint.mixRotate) * alpha;
+				constraint.mixX = constraint.mixX + (constraint.data.mixX - constraint.mixX) * alpha;
+				constraint.mixY = constraint.mixY + (constraint.data.mixY - constraint.mixY) * alpha;
+			end
+			return
+		end
+
+		local rotate
+		local x
+		local y
+		local i = search(frames, time, ENTRIES);
+		local curveType = self.curves[math_floor(i / 4)];
+		if curveType == LINEAR then
+			local before = frames[i];
+			rotate = frames[i + ROTATE];
+			x = frames[i + X];
+			y = frames[i + Y];
+			local t = (time - before) / (frames[i + ENTRIES] - before);
+			rotate = rotate + (frames[i + ENTRIES + ROTATE] - rotate) * t;
+			x = x + (frames[i + ENTRIES + X] - x) * t;
+			y = y + (frames[i + ENTRIES + Y] - y) * t;
+		elseif curveType == STEPPED then
+			rotate = frames[i + ROTATE];
+			x = frames[i + X];
+			y = frames[i + Y];
+		else
+			rotate = this.getBezierValue(time, i, ROTATE, curveType - BEZIER);
+			x = this.getBezierValue(time, i, X, curveType + BEZIER_SIZE - BEZIER);
+			y = this.getBezierValue(time, i, Y, curveType + BEZIER_SIZE * 2 - BEZIER);
+		end
+
+		if blend == MixBlend.setup then
+			local data = constraint.data;
+			constraint.mixRotate = data.mixRotate + (rotate - data.mixRotate) * alpha;
+			constraint.mixX = data.mixX + (x - data.mixX) * alpha;
+			constraint.mixY = data.mixY + (y - data.mixY) * alpha;
+		else
+			constraint.mixRotate = constraint.mixRotate + (rotate - constraint.mixRotate) * alpha;
+			constraint.mixX = constraint.mixX + (x - constraint.mixX) * alpha;
+			constraint.mixY = constraint.mixY + (y - constraint.mixY) * alpha;
+		end
+	end
+
+	return self
+end
+
+return Animation

+ 17 - 17
spine-lua/spine-lua/AnimationState.lua

@@ -313,9 +313,9 @@ function AnimationState:updateMixingFrom (to, delta)
 	from.trackLast = from.nextTrackLast
 
 	-- Require mixTime > 0 to ensure the mixing from entry was applied at least once.
-	if (to.mixTime > 0 and to.mixTime >= to.mixDuration) then
+	if to.mixTime > 0 and to.mixTime >= to.mixDuration then
 		-- Require totalAlpha == 0 to ensure mixing is complete, unless mixDuration == 0 (the transition is a single frame).
-		if (from.totalAlpha == 0 or to.mixDuration == 0) then
+		if from.totalAlpha == 0 or to.mixDuration == 0 then
 			to.mixingFrom = from.mixingFrom
 			if from.mixingFrom then from.mixingFrom.mixingTo = to end
 			to.interruptAlpha = from.interruptAlpha
@@ -365,7 +365,7 @@ function AnimationState:apply (skeleton)
 						if timeline.type == Animation.TimelineType.attachment then
 							self:applyAttachmentTimeline(timeline, skeleton, animationTime, blend, true)
 						else
-							timeline:apply(skeleton, animationLast, animationTime, self.events, mix, blend, MixDirection._in)
+							timeline:apply(skeleton, animationLast, animationTime, self.events, mix, blend, MixDirection.mixIn)
 						end
 					end
 				else
@@ -383,7 +383,7 @@ function AnimationState:apply (skeleton)
 						elseif timeline.type == Animation.TimelineType.attachment then
 							self:applyAttachmentTimeline(timeline, skeleton, animationTime, timelineBlend, true)
 						else
-							timeline:apply(skeleton, animationLast, animationTime, self.events, mix, timelineBlend, MixDirection._in)
+							timeline:apply(skeleton, animationLast, animationTime, self.events, mix, timelineBlend, MixDirection.mixIn)
 						end
 					end
 				end
@@ -444,7 +444,7 @@ function AnimationState:applyMixingFrom (to, skeleton, blend)
 
 	if blend == MixBlend.add then
 		for i,timeline in ipairs(timelines) do
-			timeline:apply(skeleton, animationLast, animationTime, events, alphaMix, blend, MixDirection.out)
+			timeline:apply(skeleton, animationLast, animationTime, events, alphaMix, blend, MixDirection.mixOut)
 		end
 	else
 		local timelineMode = from.timelineMode
@@ -457,7 +457,7 @@ function AnimationState:applyMixingFrom (to, skeleton, blend)
 
 		for i,timeline in ipairs(timelines) do
 			local skipSubsequent = false
-			local direction = MixDirection.out
+			local direction = MixDirection.mixOut
 			local timelineBlend = MixBlend.setup
 			local alpha = 0
 			if timelineMode[i] == SUBSEQUENT then
@@ -486,8 +486,8 @@ function AnimationState:applyMixingFrom (to, skeleton, blend)
 				elseif timeline.type == Animation.TimelineType.attachment then
 					self:applyAttachmentTimeline(timeline, skeleton, animationTime, timelineBlend, attachments)
 				else
-					if (drawOrder and timeline.type == Animation.TimelineType.drawOrder and timelineBlend == MixBlend.setup) then
-						direction = MixDirection._in
+					if drawOrder and timeline.type == Animation.TimelineType.drawOrder and timelineBlend == MixBlend.setup then
+						direction = MixDirection.mixIn
 					end
 					timeline:apply(skeleton, animationLast, animationTime, self.events, alpha, timelineBlend, direction)
 				end
@@ -495,7 +495,7 @@ function AnimationState:applyMixingFrom (to, skeleton, blend)
 		end
 	end
 
-	if (to.mixDuration > 0) then
+	if to.mixDuration > 0 then
 		self:queueEvents(from, animationTime)
 	end
 	self.events = {}
@@ -516,7 +516,7 @@ function AnimationState:applyAttachmentTimeline(timeline, skeleton, time, blend,
 		end
 	else
 		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
 		else
 			frameIndex = Animation.binarySearch(frames, time, 1) - 1
@@ -529,7 +529,7 @@ function AnimationState:applyAttachmentTimeline(timeline, skeleton, time, blend,
 end
 
 function AnimationState:setAttachment(skeleton, slot, attachmentName, attachments)
-	if (attachmentName == nil) then
+	if attachmentName == nil then
 		slot.attachment = nil
 	else
 		slot.attachment = skeleton:getAttachmentByIndex(slot.data.index, attachmentName)
@@ -544,7 +544,7 @@ function AnimationState:applyRotateTimeline (timeline, skeleton, time, alpha, bl
 	end
 
 	if alpha == 1 then
-		timeline:apply(skeleton, 0, time, nil, 1, blend, MixDirection._in)
+		timeline:apply(skeleton, 0, time, nil, 1, blend, MixDirection.mixIn)
 		return
 	end
 
@@ -730,7 +730,7 @@ function AnimationState:setAnimationByName (trackIndex, animationName, loop)
 end
 
 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.", 2) end
 	local interrupt = true
 	local current = self:expandToIndex(trackIndex)
 	local queue = self.queue
@@ -756,12 +756,12 @@ end
 
 function AnimationState:addAnimationByName (trackIndex, animationName, loop, delay)
 	local animation = self.data.skeletonData:findAnimation(animationName)
-	if not animation then error("Animation not found: " + animationName) end
+	if not animation then error("Animation not found: " + animationName, 2) end
 	return self:addAnimation(trackIndex, animation, loop, delay)
 end
 
 function AnimationState:addAnimation (trackIndex, animation, loop, delay)
-	if not animation then error("animation cannot be null.") end
+	if not animation then error("animation cannot be null.", 2) end
 
 	local last = self:expandToIndex(trackIndex)
 	if last then
@@ -911,7 +911,7 @@ function AnimationState:_animationsChanged ()
 				end
 
 				repeat
-					if (entry.mixingTo == nil or entry.mixBlend ~= MixBlend.add) then
+					if entry.mixingTo == nil or entry.mixBlend ~= MixBlend.add then
 						self:computeHold(entry)
 					end
 					entry = entry.mixingTo
@@ -930,7 +930,7 @@ function AnimationState:computeHold(entry)
 	local timelineHoldMix = entry.timelineHoldMix
 	local propertyIDs = self.propertyIDs
 
-	if (to and to.holdPrevious) then
+	if to and to.holdPrevious then
 		local i = 1
 		while i <= timelinesCount do
 			local id = "" .. timelines[i]:getPropertyId()

+ 2 - 2
spine-lua/spine-lua/Atlas.lua

@@ -43,13 +43,13 @@ function Atlas.parse(atlasPath, atlasBase)
 	end
 
 	if not atlasPath then
-		error("Error: " .. atlasPath .. ".atlas" .. " doesn't exist!")
+		error("Error: " .. atlasPath .. ".atlas" .. " doesn't exist!", 2)
 		return nil
 	end
 
 	local atlasLines = spine.utils.readFile( atlasPath, atlasBase )
 	if not atlasLines then
-		error("Error: " .. atlasPath .. ".atlas" .. " unable to read!")
+		error("Error: " .. atlasPath .. ".atlas" .. " unable to read!", 2)
 		return nil
 	end
 

+ 2 - 2
spine-lua/spine-lua/AtlasAttachmentLoader.lua

@@ -51,7 +51,7 @@ end
 
 function AtlasAttachmentLoader:newRegionAttachment (skin, name, path)
 	local region = self.atlas:findRegion(path)
-	if not region then error("Region not found in atlas: " .. path .. " (region attachment: " .. name .. ")") end
+	if not region then error("Region not found in atlas: " .. path .. " (region attachment: " .. name .. ")", 2) end
 	region.renderObject = region
 	local attachment = RegionAttachment.new(name)
 	attachment:setRegion(region)
@@ -61,7 +61,7 @@ end
 
 function AtlasAttachmentLoader:newMeshAttachment (skin, name, path)
 	local region = self.atlas:findRegion(path)
-	if not region then error("Region not found in atlas: " .. path .. " (mesh attachment: " .. name .. ")") end
+	if not region then error("Region not found in atlas: " .. path .. " (mesh attachment: " .. name .. ")", 2) end
 	region.renderObject = region
 	local attachment = MeshAttachment.new(name)
 	attachment.region = region

+ 5 - 5
spine-lua/spine-lua/IkConstraint.lua

@@ -125,9 +125,9 @@ function IkConstraint:apply1 (bone, targetX, targetY, compress, stretch, uniform
 	rotationIK = rotationIK + math_deg(math_atan2(ty, tx))
 	if bone.ascaleX < 0 then rotationIK = rotationIK + 180 end
 	if rotationIK > 180 then
-	rotationIK = rotationIK - 360
-	elseif (rotationIK < -180) then
-	rotationIK = rotationIK + 360
+		rotationIK = rotationIK - 360
+	elseif rotationIK < -180 then
+		rotationIK = rotationIK + 360
 	end
 	local sx = bone.ascaleX
 	local sy = bone.ascaleY
@@ -262,7 +262,7 @@ function IkConstraint:apply2 (parent, child, targetX, targetY, bendDir, stretch,
 		d = c1 * c1 - 4 * c2 * c
 		if d >= 0 then
 			local q = math_sqrt(d)
-			if (c1 < 0) then q = -q end
+			if c1 < 0 then q = -q end
 			q = -(c1 + q) / 2
 			local r0 = q / c2
 			local r1 = c / q
@@ -285,7 +285,7 @@ function IkConstraint:apply2 (parent, child, targetX, targetY, bendDir, stretch,
 			local maxDist = maxX * maxX
 			local maxY = 0
 			c = -a * l1 / (aa - bb)
-			if (c >= -1 and c <= 1) then
+			if c >= -1 and c <= 1 then
 				c = math_acos(c)
 				x = a * math_cos(c) + l1
 				y = b * math_sin(c)

+ 1 - 1
spine-lua/spine-lua/Interpolation.lua

@@ -36,7 +36,7 @@ function interpolation.apply (func, start, _end, a)
 end
 
 function interpolation.pow2(a)
-	if (a <= 0.5) then return math_pow(a * 2, 2) / 2 end
+	if a <= 0.5 then return math_pow(a * 2, 2) / 2 end
 	return math_pow((a - 1) * 2, 2) / -2 + 1
 end
 

+ 1 - 1
spine-lua/spine-lua/Skeleton.lua

@@ -438,7 +438,7 @@ function Skeleton:setSkin (skinName)
 end
 
 function Skeleton:setSkinByReference(newSkin)
-	if (self.skin == newSkin) then return end
+	if self.skin == newSkin then return end
 	if newSkin then
 		if self.skin then
 			newSkin:attachAll(self, self.skin)

+ 2 - 2
spine-lua/spine-lua/SkeletonBounds.lua

@@ -59,7 +59,7 @@ function SkeletonBounds:update (skeleton, updateAabb)
 	local slots = skeleton.slots
 
 	for _,slot in ipairs(skeleton.slots) do
-		if (slot.bone.active) then
+		if slot.bone.active then
 			local attachment = slot.attachment
 			if attachment and attachment.type == AttachmentType.boundingbox then
 				local boundingBox = attachment
@@ -110,7 +110,7 @@ end
 function SkeletonBounds:aabbIntersectsSegment (x1, y1, x2, y2)
 	local minX, minY, maxX, maxY = self.minX, self.minY, self.maxX, self.maxY
 	if (x1 <= minX and x2 <= minX) or (y1 <= minY and y2 <= minY) or (x1 >= maxX and x2 >= maxX) or (y1 >= maxY and y2 >= maxY) then
-			return false
+		return false
 	end
 	local m = (y2 - y1) / (x2 - x1)
 	local y = m * (minX - x1) + y1

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

@@ -128,9 +128,9 @@ function SkeletonClipping:clipTriangles(vertices, uvs, triangles, trianglesLengt
 		while p <= polygonsCount do
 			local s = #clippedVertices + 1
 			local clipOutput = {}
-			if (self:clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) then
+			if self:clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput) then
 				local clipOutputLength = #clipOutput
-				if (clipOutputLength > 0) then
+				if clipOutputLength > 0 then
 					local d0 = y2 - y3
 					local d1 = x3 - x2
 					local d2 = x1 - x3
@@ -295,7 +295,7 @@ function SkeletonClipping:clip(x1, y1, x2, y2, x3, y3, clippingArea, output)
 		table_insert(output, output[1])
 		table_insert(output, output[2])
 
-		if (i == clippingVerticesLast) then break end
+		if i == clippingVerticesLast then break end
 		local temp = output
 		output = input
 		for i, _ in ipairs(output) do
@@ -340,7 +340,7 @@ function SkeletonClipping:makeClockwise(polygon)
 		area = area + p1x * p2y - p2x * p1y
 		i = i + 2
 	end
-	if (area < 0) then return end
+	if area < 0 then return end
 
 	i = 1
 	local lastX = verticesLength - 2 + 1

+ 17 - 14
spine-lua/spine-lua/SkeletonJson.lua

@@ -48,6 +48,9 @@ local TransformMode = require "spine-lua.TransformMode"
 local utils = require "spine-lua.utils"
 local Color = require "spine-lua.Color"
 
+local math_max = math.max
+local math_floor = math.floor
+
 local SkeletonJson = {}
 function SkeletonJson.new (attachmentLoader)
 	if not attachmentLoader then attachmentLoader = AttachmentLoader.new() end
@@ -200,13 +203,13 @@ function SkeletonJson.new (attachmentLoader)
 
 				for _,boneName in ipairs(constraintMap.bones) do
 					local bone = skeletonData:findBone(boneName)
-					if not bone then error("Transform constraint bone not found: " .. boneName, 2) end
+					if not bone then error("Transform constraint bone not found: " .. boneName) end
 					table_insert(data.bones, bone)
 				end
 
 				local targetName = constraintMap.target
 				data.target = skeletonData:findBone(targetName)
-				if not data.target then error("Transform constraint target bone not found: " .. (targetName or "none"), 2) end
+				if not data.target then error("Transform constraint target bone not found: " .. (targetName or "none")) end
 
 				data.local_ = getValue(constraintMap, "local", false)
 				data.relative = getValue(constraintMap, "relative", false)
@@ -237,13 +240,13 @@ function SkeletonJson.new (attachmentLoader)
 
 				for _,boneName in ipairs(constraintMap.bones) do
 					local bone = skeletonData:findBone(boneName)
-					if not bone then error("Path constraint bone not found: " .. boneName, 2) end
+					if not bone then error("Path constraint bone not found: " .. boneName) end
 					table_insert(data.bones, bone)
 				end
 
 				local targetName = constraintMap.target
 				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) end
 
 				data.positionMode = PathConstraintData.PositionMode[getValue(constraintMap, "positionMode", "percent"):lower()]
 				data.spacingMode = PathConstraintData.SpacingMode[getValue(constraintMap, "spacingMode", "length"):lower()]
@@ -269,7 +272,7 @@ function SkeletonJson.new (attachmentLoader)
 				if skinMap["bones"] then
 					for _, entry in ipairs(skinMap["bones"]) do
 						local bone = skeletonData:findBone(entry)
-						if bone == nil then error("Skin bone not found:  " .. entry, 2) end
+						if bone == nil then error("Skin bone not found:  " .. entry) end
 						table_insert(skin.bones, bone)
 					end
 				end
@@ -277,7 +280,7 @@ function SkeletonJson.new (attachmentLoader)
 				if skinMap["ik"] then
 					for _, entry in ipairs(skinMap["ik"]) do
 						local constraint = skeletonData:findIkConstraint(entry)
-						if constraint == nil then error("Skin IK constraint not found:  " .. entry, 2) end
+						if constraint == nil then error("Skin IK constraint not found:  " .. entry) end
 						table_insert(skin.constraints, constraint)
 					end
 				end
@@ -285,7 +288,7 @@ function SkeletonJson.new (attachmentLoader)
 				if skinMap["transform"] then
 					for _, entry in ipairs(skinMap["transform"]) do
 						local constraint = skeletonData:findTransformConstraint(entry)
-						if constraint == nil then error("Skin transform constraint not found:  " .. entry, 2) end
+						if constraint == nil then error("Skin transform constraint not found:  " .. entry) end
 						table_insert(skin.constraints, constraint)
 					end
 				end
@@ -293,7 +296,7 @@ function SkeletonJson.new (attachmentLoader)
 				if skinMap["path"] then
 					for _, entry in ipairs(skinMap["path"]) do
 						local constraint = skeletonData:findPathConstraint(entry)
-						if constraint == nil then error("Skin path constraint not found:  " .. entry, 2) end
+						if constraint == nil then error("Skin path constraint not found:  " .. entry) end
 						table_insert(skin.constraints, constraint)
 					end
 				end
@@ -915,7 +918,7 @@ function SkeletonJson.new (attachmentLoader)
 							table_insert(timelines, readTimeline1(timelineMap, timeline, 0, timelineScale))
 						elseif timelineName == "spacing" then
 							local timeline = Animation.PathConstraintSpacingTimeline.new(#timelineMap, #timelineMap, constraintIndex)
-							local timelineScale = 1;
+							local timelineScale = 1
 							if data.spacingMode == SpacingMode.Length or data.spacingMode == SpacingMode.Fixed then timelineScale = scale end
 							table_insert(timelines, readTimeline1(timelineMap, timeline, 0, timelineScale))
 						elseif timelineName == "mix" then
@@ -960,19 +963,19 @@ function SkeletonJson.new (attachmentLoader)
 		if map.deform then
 			for deformName, deformMap in pairs(map.deform) do
 				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) end
 				for slotName,slotMap in pairs(deformMap) do
 					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) end
 					for timelineName,timelineMap in pairs(slotMap) do
 						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
+							if not attachment then error("Deform attachment not found: " .. timelineMap.name) end
 							local weighted = attachment.bones ~= nil
 							local vertices = attachment.vertices
 							local deformLength = #vertices
-							if weighted then deformLength = math.floor(deformLength / 3) * 2 end
+							if weighted then deformLength = math_floor(deformLength / 3) * 2 end
 
 							local timeline = Animation.DeformTimeline.new(#timelineMap, #timelineMap, slotIndex, attachment)
 							local bezier = 0
@@ -1104,7 +1107,7 @@ function SkeletonJson.new (attachmentLoader)
 
 		local duration = 0
 		for _,timeline in ipairs(timelines) do
-			duration = math.max(duration, timeline:getDuration())
+			duration = math_max(duration, timeline:getDuration())
 		end
 		table_insert(skeletonData.animations, Animation.new(name, timelines, duration))
 	end

+ 1 - 1
spine-lua/spine-lua/Skin.lua

@@ -187,7 +187,7 @@ function Skin:attachAll(skeleton, oldSkin)
 		local slotAttachment = slot.attachment
 		if slotAttachment then
 			local dictionary = oldSkin.attachments[i]
-			if (dictionary) then
+			if dictionary then
 				for key, value in pairs(dictionary) do
 					local skinAttachment = value
 					if slotAttachment == skinAttachment then

+ 3 - 3
spine-lua/spine-lua/Triangulator.lua

@@ -111,7 +111,7 @@ function Triangulator:triangulate (verticesArray)
 					end
 					ii = (ii + 1) % vertexCount
 				end
-				if (not goToHead) then
+				if not goToHead then
 					breakLoop = true
 					break
 				end
@@ -244,7 +244,7 @@ function Triangulator:decompose(verticesArray, triangles)
 	n = #convexPolygons
 	while i <= n do
 		polygonIndices = convexPolygonsIndices[i]
-		if (#polygonIndices > 0) then
+		if #polygonIndices > 0 then
 			local firstIndex = polygonIndices[1]
 			local lastIndex = polygonIndices[#polygonIndices]
 
@@ -265,7 +265,7 @@ function Triangulator:decompose(verticesArray, triangles)
 			while ii <= n do
 				if ii ~= i then
 					local otherIndices = convexPolygonsIndices[ii]
-					if (#otherIndices == 3) then
+					if #otherIndices == 3 then
 						local otherFirstIndex = otherIndices[1]
 						local otherSecondIndex = otherIndices[2]
 						local otherLastIndex = otherIndices[3]

+ 1 - 1
spine-lua/spine-lua/attachments/Attachment.lua

@@ -47,7 +47,7 @@ function Attachment.new (name, attachmentType)
 end
 
 function Attachment:copy ()
-	error("Attachment copy not implemented.")
+	error("Attachment copy not implemented.", 2)
 end
 
 return Attachment

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

@@ -45,6 +45,7 @@ setmetatable(VertexAttachment, { __index = Attachment })
 
 function VertexAttachment.new (name, attachmentType)
 	local self = Attachment.new(name, attachmentType)
+	self.vertexAttachment = true
 	self.bones = nil
 	self.vertices = nil
 	self.worldVerticesLength = 0

+ 4 - 4
spine-lua/spine-lua/utils.lua

@@ -169,17 +169,17 @@ end
 function utils.randomTriangularWith(min, max, mode)
 	local u = math.random()
 	local d = max - min
-	if (u <= (mode - min) / d) then return min + math_sqrt(u * d * (mode - min)) end
+	if u <= (mode - min) / d then return min + math_sqrt(u * d * (mode - min)) end
 	return max - math_sqrt((1 - u) * d * (max - mode))
 end
 
 function utils.testBit(value, bit)
-	if (value == nil) then return 0 end
+	if value == nil then return 0 end
 	return value % (2 * bit) >= bit
 end
 
 function utils.setBit(value, bit)
-	if (value == nil) then return 0 end
+	if value == nil then return 0 end
 	if value % (2 * bit) >= bit then
 		return value
 	end
@@ -187,7 +187,7 @@ function utils.setBit(value, bit)
 end
 
 function utils.clearBit(value, bit)
-	if (value == nil) then return 0 end
+	if value == nil then return 0 end
 	if value % (2 * bit) >= bit then
 		return value - bit
 	end

+ 1 - 1
spine-lua/spine-lua/vertexeffects/SwirlEffect.lua

@@ -69,7 +69,7 @@ function SwirlEffect:transform (vertex)
 	local x = vertex.x - self.worldX
 	local y = vertex.y - self.worldY
 	local dist = math_sqrt(x * x + y * y)
-	if (dist < self.radius) then
+	if dist < self.radius then
 		local theta = interpolation.apply(self.interpolation, 0, self.angleRad, (self.radius - dist) / self.radius)
 		local cos = math_cos(theta)
 		local sin = math_sin(theta)