Эх сурвалжийг харах

[lua] Move spine-lua source to spine-lua subfolder.

Nathan Sweet 4 жил өмнө
parent
commit
fe9baaf849
52 өөрчлөгдсөн 8807 нэмэгдсэн , 8806 устгасан
  1. 4 5
      spine-corona/README.md
  2. 6 4
      spine-love/README.md
  3. 1612 1612
      spine-lua/spine-lua/Animation.lua
  4. 1007 1007
      spine-lua/spine-lua/AnimationState.lua
  5. 58 58
      spine-lua/spine-lua/AnimationStateData.lua
  6. 104 104
      spine-lua/spine-lua/Atlas.lua
  7. 0 0
      spine-lua/spine-lua/AtlasAttachmentLoader.lua
  8. 68 68
      spine-lua/spine-lua/AttachmentLoader.lua
  9. 36 36
      spine-lua/spine-lua/AttachmentType.lua
  10. 36 36
      spine-lua/spine-lua/BlendMode.lua
  11. 320 320
      spine-lua/spine-lua/Bone.lua
  12. 55 55
      spine-lua/spine-lua/BoneData.lua
  13. 61 61
      spine-lua/spine-lua/BoundingBoxAttachment.lua
  14. 83 83
      spine-lua/spine-lua/Color.lua
  15. 46 46
      spine-lua/spine-lua/Event.lua
  16. 46 46
      spine-lua/spine-lua/EventData.lua
  17. 334 334
      spine-lua/spine-lua/IkConstraint.lua
  18. 50 50
      spine-lua/spine-lua/IkConstraintData.lua
  19. 0 0
      spine-lua/spine-lua/Interpolation.lua
  20. 93 93
      spine-lua/spine-lua/MeshAttachment.lua
  21. 563 563
      spine-lua/spine-lua/PathConstraint.lua
  22. 70 70
      spine-lua/spine-lua/PathConstraintData.lua
  23. 100 100
      spine-lua/spine-lua/RegionAttachment.lua
  24. 571 571
      spine-lua/spine-lua/Skeleton.lua
  25. 199 199
      spine-lua/spine-lua/SkeletonBounds.lua
  26. 0 0
      spine-lua/spine-lua/SkeletonClipping.lua
  27. 141 141
      spine-lua/spine-lua/SkeletonData.lua
  28. 936 936
      spine-lua/spine-lua/SkeletonJson.lua
  29. 220 220
      spine-lua/spine-lua/Skin.lua
  30. 89 89
      spine-lua/spine-lua/Slot.lua
  31. 57 57
      spine-lua/spine-lua/SlotData.lua
  32. 258 258
      spine-lua/spine-lua/TextureAtlas.lua
  33. 53 53
      spine-lua/spine-lua/TextureAtlasRegion.lua
  34. 39 39
      spine-lua/spine-lua/TextureFilter.lua
  35. 51 51
      spine-lua/spine-lua/TextureRegion.lua
  36. 34 34
      spine-lua/spine-lua/TextureWrap.lua
  37. 343 343
      spine-lua/spine-lua/TransformConstraint.lua
  38. 49 49
      spine-lua/spine-lua/TransformConstraintData.lua
  39. 0 0
      spine-lua/spine-lua/TransformMode.lua
  40. 0 0
      spine-lua/spine-lua/Triangulator.lua
  41. 53 53
      spine-lua/spine-lua/attachments/Attachment.lua
  42. 39 39
      spine-lua/spine-lua/attachments/AttachmentType.lua
  43. 53 53
      spine-lua/spine-lua/attachments/BoundingBoxAttachment.lua
  44. 0 0
      spine-lua/spine-lua/attachments/ClippingAttachment.lua
  45. 180 180
      spine-lua/spine-lua/attachments/MeshAttachment.lua
  46. 61 61
      spine-lua/spine-lua/attachments/PathAttachment.lua
  47. 0 0
      spine-lua/spine-lua/attachments/PointAttachment.lua
  48. 264 264
      spine-lua/spine-lua/attachments/RegionAttachment.lua
  49. 168 168
      spine-lua/spine-lua/attachments/VertexAttachment.lua
  50. 197 197
      spine-lua/spine-lua/utils.lua
  51. 0 0
      spine-lua/spine-lua/vertexeffects/JitterEffect.lua
  52. 0 0
      spine-lua/spine-lua/vertexeffects/SwirlEffect.lua

+ 4 - 5
spine-corona/README.md

@@ -23,10 +23,9 @@ spine-corona does not yet support loading the binary format.
 ## Setup
 
 1. Download the Spine Runtimes source using [git](https://help.github.com/articles/set-up-git) or by downloading it as a zip via the download button above.
-1. Copy the contents of `spine-lua` to `spine-corona/spine-lua`.
-1. Run the `main.lua` file using Corona. Tap/click to switch between skeletons
+2. Copy the `spine-lua/spine-lua` folder to `spine-corona`.
+3. Run the `main.lua` file using Corona. Tap/click to switch between skeletons
 
-Alternatively, the `spine-lua` and `spine-corona/spine-corona` directories can be copied into your project. Note that the require statements use `spine-lua.Xxx`, so the spine-lua files must be in a `spine-lua` directory in your project.
-
-When using the `EmmyLua` plugin for IntelliJ IDEA, create a launch configuration pointing at the `Corona Simulator` executable (e.g. ` /Applications/Corona/Corona Simulator.app/Contents/MacOS/Corona Simulator` on macOS), set the working directory to `spine-corona` and set the parameters to `main.lua`.
+Alternatively, the `spine-lua` and `spine-corona/spine-corona` directories can be copied into your project. Note that the spine-corona `require` statements use `spine-lua.Xxx`, so the spine-lua files must be in a `spine-lua` directory in your project.
 
+When using the `EmmyLua` plugin for IntelliJ IDEA, create a launch configuration pointing at the `Corona Simulator` executable (e.g. ` /Applications/Corona/Corona Simulator.app/Contents/MacOS/Corona Simulator` on macOS), set the working directory to `spine-corona`, and set the parameters to `main.lua`.

+ 6 - 4
spine-love/README.md

@@ -3,6 +3,7 @@
 The spine-love runtime provides functionality to load, manipulate and render [Spine](http://esotericsoftware.com) skeletal animation data using [LÖVE](https://love2d.org/). spine-love is based on [spine-lua](../spine-lua).
 
 ## Licensing
+
 You are welcome to evaluate the Spine Runtimes and the examples we provide in this repository free of charge.
 
 You can integrate the Spine Runtimes into your software free of charge, but users of your software must have their own [Spine license](https://esotericsoftware.com/spine-purchase). Please make your users aware of this requirement! This option is often chosen by those making development tools, such as an SDK, game toolkit, or software library.
@@ -15,7 +16,7 @@ For the official legal terms governing the Spine Runtimes, please read the [Spin
 
 spine-love works with data exported from Spine 3.9.x.
 
-spine-love supports all Spine features except for blending modes other than normal.
+spine-love supports all Spine features except for blending modes other than `normal`.
 
 spine-love does not yet support loading the binary format.
 
@@ -32,11 +33,12 @@ Alternatively, the `spine-lua` and `spine-love/spine-love` directories can be co
  * To enable two color tinting, pass `true` to `SkeletonRenderer.new()`.
 
 ## Examples
+
 If you want to run and debug the example project, use IntelliJ IDEA with the EmmyLua plugin.
 
-1. Install IntelliJ IDEA and the EmmyLua plugin.
-2. Install LÖVE.
-3. Copy the contents of `spine-lua` to `spine-love/spine-lua`.
+1. Install [IntelliJ IDEA](https://www.jetbrains.com/idea/) and the [EmmyLua plugin](https://plugins.jetbrains.com/plugin/9768-emmylua).
+2. Install [LÖVE](https://love2d.org/).
+3. Copy the `spine-lua/spine-lua` folder to `spine-love`.
 4. Open the `spine-love` folder in IntelliJ IDEA.
 5. Create a new launch configuration of the type `Lua Application`, with the following settings
     1.  `Program` should point at the `love` or `love.exe` executable, e.g. `/Applications/love.app/Contents/MacOS/love` on macOS.

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

@@ -1,1612 +1,1612 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
--- FIXME
--- All the indexing in this file is zero based. We use zlen()
--- instead of the # operator. Initialization of number arrays
--- is performed via utils.newNumberArrayZero. This needs
--- to be rewritten using one-based indexing for better performance
-
-local utils = require "spine-lua.utils"
-local AttachmentType = require "spine-lua.attachments.AttachmentType"
-local math_floor = math.floor
-local math_abs = math.abs
-local math_signum = utils.signum
-
-local function zlen(array)
-	return #array + 1
-end
-
-local Animation = {}
-function Animation.new (name, timelines, duration)
-	if not timelines then error("timelines cannot be nil", 2) end
-
-	local self = {
-		name = name,
-		timelines = timelines,
-		timelineIds = {},
-		duration = duration
-	}
-	
-	for i,timeline in ipairs(self.timelines) do
-		self.timelineIds[timeline:getPropertyId()] = true
-	end
-
-	function self:hasTimeline(id)
-		return self.timelineIds[id] == true
-	end
-
-	function self:apply (skeleton, lastTime, time, loop, events, alpha, blend, direction)
-		if not skeleton then error("skeleton cannot be nil.", 2) end
-
-		if loop and duration > 0 then
-			time = time % self.duration
-			if lastTime > 0 then lastTime = lastTime % self.duration end
-		end
-
-		for i,timeline in ipairs(self.timelines) do
-			timeline:apply(skeleton, lastTime, time, events, alpha, blend, direction)
-		end
-	end
-
-	return self
-end
-
-local function binarySearch (values, target, step)
-	local low = 0
-	local high = math.floor(zlen(values) / step - 2)
-	if high == 0 then return step end
-	local current = math.floor(high / 2)
-	while true do
-		if values[(current + 1) * step] <= target then
-			low = current + 1
-		else
-			high = current
-		end
-		if low == high then return (low + 1) * step end
-		current = math.floor((low + high) / 2)
-	end
-end
-Animation.binarySearch = binarySearch
-
-local function binarySearch1 (values, target)
-	local low = 0
-	local high = math.floor(zlen(values)	- 2)
-	if high == 0 then return 1 end
-	local current = math.floor(high / 2)
-	while true do
-		if values[current + 1] <= target then
-			low = current + 1
-		else
-			high = current
-		end
-		if low == high then return low + 1 end
-		current = math.floor((low + high) / 2)
-	end
-end
-
-local function linearSearch (values, target, step)
-	local i = 0
-	local last = zlen(values) - step
-	while i <= last do
-		if (values[i] > target) then return i end
-		i = i + step
-	end
-	return -1
-end
-
-Animation.MixBlend = {
-	setup = 0,
-	first = 1,
-	replace = 2,
-	add = 3
-}
-local MixBlend = Animation.MixBlend
-
-Animation.MixDirection = {
-	_in = 0, out = 1
-}
-local MixDirection = Animation.MixDirection
-
-Animation.TimelineType = {
-	rotate = 0, translate = 1, scale = 2, shear = 3,
-	attachment = 4, color = 5, deform = 6,
-	event = 7, drawOrder = 8,
-	ikConstraint = 9, transformConstraint = 10,
-	pathConstraintPosition = 11, pathConstraintSpacing = 12, pathConstraintMix = 13,
-	twoColor = 14
-}
-local TimelineType = Animation.TimelineType
-local SHL_24 = 16777216
-local SHL_27 = 134217728
-
-Animation.CurveTimeline = {}
-function Animation.CurveTimeline.new (frameCount)
-	local LINEAR = 0
-	local STEPPED = 1
-	local BEZIER = 2
-	local BEZIER_SIZE = 10 * 2 - 1
-
-	local self = {
-		curves = utils.newNumberArrayZero((frameCount - 1) * BEZIER_SIZE) -- type, x, y, ...
-	}
-
-	function self:getFrameCount ()
-		return math.floor(zlen(self.curves) / BEZIER_SIZE) + 1
-	end
-
-	function self:setStepped (frameIndex)
-		self.curves[frameIndex * BEZIER_SIZE] = STEPPED
-	end
-
-	function self:getCurveType (frameIndex)
-		local index = frameIndex * BEZIER_SIZE
-		if index == zlen(self.curves) then return LINEAR end
-		local type = self.curves[index]
-		if type == LINEAR then return LINEAR end
-		if type == STEPPED then return STEPPED end
-		return BEZIER
-	end
-
-	function self:setCurve (frameIndex, cx1, cy1, cx2, cy2)
-			local tmpx = (-cx1 * 2 + cx2) * 0.03
-			local tmpy = (-cy1 * 2 + cy2) * 0.03
-			local dddfx = ((cx1 - cx2) * 3 + 1) * 0.006
-			local dddfy = ((cy1 - cy2) * 3 + 1) * 0.006
-			local ddfx = tmpx * 2 + dddfx
-			local ddfy = tmpy * 2 + dddfy
-			local dfx = cx1 * 0.3 + tmpx + dddfx * 0.16666667
-			local dfy = cy1 * 0.3 + tmpy + dddfy * 0.16666667
-
-			local i = frameIndex * BEZIER_SIZE
-			local curves = self.curves
-			curves[i] = BEZIER
-			i = i + 1
-
-			local x = dfx
-			local y = dfy
-			local n = i + BEZIER_SIZE - 1
-			while i < n do
-				curves[i] = x
-				curves[i + 1] = y
-				dfx = dfx + ddfx
-				dfy = dfy + ddfy
-				ddfx = ddfx + dddfx
-				ddfy = ddfy + dddfy
-				x = x + dfx
-				y = y + dfy
-				i = i + 2
-			end
-	end
-
-	function self:getCurvePercent (frameIndex, percent)
-		percent = utils.clamp(percent, 0, 1)
-		local curves = self.curves
-		local i = frameIndex * BEZIER_SIZE
-		local type = curves[i]
-		if type == LINEAR then return percent end
-		if type == STEPPED then return 0 end
-		i = i + 1
-		local x
-		local n = i + BEZIER_SIZE - 1
-		local start = i
-		while i < n do
-			x = curves[i]
-			if x >= percent then
-				local prevX, prevY
-				if i == start then
-					prevX = 0
-					prevY = 0
-				else
-					prevX = curves[i - 2]
-					prevY = curves[i - 1]
-				end
-				return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX)
-			end
-			i = i + 2
-		end
-		local y = curves[i - 1]
-		return y + (1 - y) * (percent - x) / (1 - x) -- Last point is 1,1.
-	end
-
-	return self
-end
-
-Animation.RotateTimeline = {}
-Animation.RotateTimeline.ENTRIES = 2
-Animation.RotateTimeline.PREV_TIME = -2
-Animation.RotateTimeline.PREV_ROTATION = -1
-Animation.RotateTimeline.ROTATION = 1
-function Animation.RotateTimeline.new (frameCount)
-	local ENTRIES = Animation.RotateTimeline.ENTRIES
-	local PREV_TIME = Animation.RotateTimeline.PREV_TIME
-	local PREV_ROTATION = Animation.RotateTimeline.PREV_ROTATION
-	local ROTATION = Animation.RotateTimeline.ROTATION
-
-	local self = Animation.CurveTimeline.new(frameCount)
-	self.boneIndex = -1
-	self.frames = utils.newNumberArrayZero(frameCount * 2)
-	self.type = TimelineType.rotate
-
-	function self:getPropertyId ()
-		return TimelineType.rotate * SHL_24 + self.boneIndex
-	end
-
-	function self:setFrame (frameIndex, time, degrees)
-		frameIndex = frameIndex * 2
-		self.frames[frameIndex] = time
-		self.frames[frameIndex + ROTATION] = degrees
-	end
-
-	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
-		local frames = self.frames
-
-		local bone = skeleton.bones[self.boneIndex]
-		if not bone.active then return end
-		if time < frames[0] then
-			if blend == MixBlend.setup then
-				bone.rotation = bone.data.rotation
-			elseif blend == MixBlend.first then
-				local r = bone.data.rotation - bone.rotation
-				bone.rotation = bone.rotation + (r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360) * alpha
-			end
-			return
-		end
-
-		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
-			local r = frames[zlen(frames) + PREV_ROTATION]
-			if blend == MixBlend.setup then
-				bone.rotation = bone.data.rotation + r * alpha
-			elseif blend == MixBlend.first or blend == MixBlend.replace then
-				r = r + bone.data.rotation - bone.rotation
-				r = r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360 -- Wrap within -180 and 180.
-				bone.rotation = bone.rotation + r * alpha;
-			elseif blend == MixBlend.add then
-				bone.rotation = bone.rotation + r * alpha;
-			end
-			return;
-		end
-
-		-- Interpolate between the last frame and the current frame.
-		local frame = binarySearch(frames, time, ENTRIES)
-		local prevRotation = frames[frame + PREV_ROTATION]
-		local frameTime = frames[frame]
-		local percent = self:getCurvePercent((math.floor(frame / 2)) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
-
-		local r = frames[frame + ROTATION] - prevRotation
-		r = prevRotation + (r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360) * percent
-		if blend == MixBlend.setup then
-			bone.rotation = bone.data.rotation + (r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360) * alpha
-		elseif blend == MixBlend.first or blend == MixBlend.replace then
-			r = r + bone.data.rotation - bone.rotation;
-			bone.rotation = bone.rotation + (r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360) * alpha
-		elseif blend == MixBlend.add then
-			bone.rotation = bone.rotation + (r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360) * alpha
-		end
-	end
-
-	return self
-end
-
-Animation.TranslateTimeline = {}
-Animation.TranslateTimeline.ENTRIES = 3
-function Animation.TranslateTimeline.new (frameCount)
-	local ENTRIES = Animation.TranslateTimeline.ENTRIES
-	local PREV_TIME = -3
-	local PREV_X = -2
-	local PREV_Y = -1
-	local X = 1
-	local Y = 2
-
-	local self = Animation.CurveTimeline.new(frameCount)
-	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
-	self.boneIndex = -1
-	self.type = TimelineType.translate
-
-	function self:getPropertyId ()
-		return TimelineType.translate * SHL_24 + self.boneIndex
-	end
-
-	function self:setFrame (frameIndex, time, x, y)
-		frameIndex = frameIndex * ENTRIES
-		self.frames[frameIndex] = time
-		self.frames[frameIndex + X] = x
-		self.frames[frameIndex + Y] = y
-	end
-
-	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
-		local frames = self.frames
-
-		local bone = skeleton.bones[self.boneIndex]
-		if not bone.active then return end
-
-		if time < frames[0] then
-			if blend == MixBlend.setup then
-				bone.x = bone.data.x
-				bone.y = bone.data.y
-			elseif blend == MixBlend.first then
-				bone.x = bone.x + (bone.data.x - bone.x) * alpha
-				bone.y = bone.y + (bone.data.y - bone.y) * alpha
-			end
-			return
-		end
-
-		local x = 0
-		local y = 0
-		if time >= frames[zlen(frames) - ENTRIES] then -- // Time is after last frame.
-			x = frames[zlen(frames) + PREV_X];
-			y = frames[zlen(frames) + PREV_Y];
-		else
-			-- Interpolate between the previous frame and the current frame.
-			local frame = binarySearch(frames, time, ENTRIES)
-			x = frames[frame + PREV_X]
-			y = frames[frame + PREV_Y]
-			local frameTime = frames[frame]
-			local percent = self:getCurvePercent(math_floor(frame / ENTRIES) - 1,
-				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
-			x = x + (frames[frame + X] - x) * percent
-			y = y + (frames[frame + Y] - y) * percent
-		end
-		if blend == MixBlend.setup then
-			bone.x = bone.data.x + x * alpha
-			bone.y = bone.data.y + y * alpha
-		elseif blend == MixBlend.first or blend == MixBlend.replace then
-			bone.x = bone.x + (bone.data.x + x - bone.x) * alpha
-			bone.y = bone.y + (bone.data.y + y - bone.y) * alpha
-		elseif blend == MixBlend.add then
-			bone.x = bone.x + x * alpha
-			bone.y = bone.y + y * alpha
-		end
-	end
-
-	return self
-end
-
-Animation.ScaleTimeline = {}
-Animation.ScaleTimeline.ENTRIES = Animation.TranslateTimeline.ENTRIES
-function Animation.ScaleTimeline.new (frameCount)
-	local ENTRIES = Animation.ScaleTimeline.ENTRIES
-	local PREV_TIME = -3
-	local PREV_X = -2
-	local PREV_Y = -1
-	local X = 1
-	local Y = 2
-
-	local self = Animation.TranslateTimeline.new(frameCount)
-	self.type = TimelineType.scale
-
-	function self:getPropertyId ()
-		return TimelineType.scale * SHL_24 + self.boneIndex
-	end
-
-	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
-		local frames = self.frames
-
-		local bone = skeleton.bones[self.boneIndex]
-		if not bone.active then return end
-
-		if time < frames[0] then
-			if blend == MixBlend.setup then
-				bone.scaleX = bone.data.scaleX
-				bone.scaleY = bone.data.scaleY
-			elseif blend == MixBlend.first then
-				bone.scaleX = bone.scaleX + (bone.data.scaleX - bone.scaleX) * alpha
-				bone.scaleY = bone.scaleY + (bone.data.scaleY - bone.scaleY) * alpha
-			end
-			return
-		end
-
-		local x = 0
-		local y = 0
-		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
-			x = frames[zlen(frames) + PREV_X] * bone.data.scaleX
-			y = frames[zlen(frames) + PREV_Y] * bone.data.scaleY
-		else
-			-- Interpolate between the previous frame and the current frame.
-			local frame = binarySearch(frames, time, ENTRIES)
-			x = frames[frame + PREV_X]
-			y = frames[frame + PREV_Y]
-			local frameTime = frames[frame]
-			local percent = self:getCurvePercent(math_floor(frame / ENTRIES) - 1,
-				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
-
-			x = (x + (frames[frame + X] - x) * percent) * bone.data.scaleX
-			y = (y + (frames[frame + Y] - y) * percent) * bone.data.scaleY
-		end
-		if alpha == 1 then
-			if blend == MixBlend.add then
-				bone.scaleX = bone.scaleX + x - bone.data.scaleX
-				bone.scaleY = bone.scaleY + y - bone.data.scaleY
-			else
-				bone.scaleX = x
-				bone.scaleY = y
-			end
-		else
-			local bx = 0
-			local by = 0
-			if direction == MixDirection.out then
-				if blend == MixBlend.setup then
-					bx = bone.data.scaleX
-					by = bone.data.scaleY
-					bone.scaleX = bx + (math_abs(x) * math_signum(bx) - bx) * alpha
-					bone.scaleY = by + (math_abs(y) * math_signum(by) - by) * alpha
-				elseif blend == MixBlend.first or blend == MixBlend.replace then
-					bx = bone.scaleX
-					by = bone.scaleY
-					bone.scaleX = bx + (math_abs(x) * math_signum(bx) - bx) * alpha
-					bone.scaleY = by + (math_abs(y) * math_signum(by) - by) * alpha
-				elseif blend == MixBlend.add then
-					bx = bone.scaleX
-					by = bone.scaleY
-					bone.scaleX = bx + (math_abs(x) * math_signum(bx) - bone.data.scaleX) * alpha
-					bone.scaleY = by + (math_abs(y) * math_signum(by) - bone.data.scaleY) * alpha
-				end
-			else
-				if blend == MixBlend.setup then
-					bx = math_abs(bone.data.scaleX) * math_signum(x)
-					by = math_abs(bone.data.scaleY) * math_signum(y)
-					bone.scaleX = bx + (x - bx) * alpha
-					bone.scaleY = by + (y - by) * alpha
-				elseif blend == MixBlend.first or blend == MixBlend.replace then
-					bx = math_abs(bone.scaleX) * math_signum(x)
-					by = math_abs(bone.scaleY) * math_signum(y)
-					bone.scaleX = bx + (x - bx) * alpha
-					bone.scaleY = by + (y - by) * alpha
-				elseif blend == MixBlend.add then
-					bx = math_signum(x)
-					by = math_signum(y)
-					bone.scaleX = math_abs(bone.scaleX) * bx + (x - math_abs(bone.data.scaleX) * bx) * alpha
-					bone.scaleY = math_abs(bone.scaleY) * by + (y - math_abs(bone.data.scaleY) * by) * alpha
-				end
-			end
-		end
-	end
-
-	return self
-end
-
-Animation.ShearTimeline = {}
-Animation.ShearTimeline.ENTRIES = Animation.TranslateTimeline.ENTRIES
-function Animation.ShearTimeline.new (frameCount)
-	local ENTRIES = Animation.ShearTimeline.ENTRIES
-	local PREV_TIME = -3
-	local PREV_X = -2
-	local PREV_Y = -1
-	local X = 1
-	local Y = 2
-
-	local self = Animation.TranslateTimeline.new(frameCount)
-	self.type = TimelineType.shear
-
-	function self:getPropertyId ()
-		return TimelineType.shear * SHL_24 + self.boneIndex
-	end
-
-	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
-		local frames = self.frames
-
-		local bone = skeleton.bones[self.boneIndex]
-		if not bone.active then return end
-
-		if time < frames[0] then
-			if blend == MixBlend.setup then
-				bone.shearX = bone.data.shearX
-				bone.shearY = bone.data.shearY
-			elseif blend == MixBlend.first then
-				bone.shearX = bone.shearX + (bone.data.shearX - bone.shearX) * alpha
-				bone.shearY = bone.shearX + (bone.data.shearY - bone.shearY) * alpha
-			end
-			return
-		end
-
-		local x = 0
-		local y = 0
-		if time >= frames[zlen(frames) - ENTRIES] then -- // Time is after last frame.
-			x = frames[zlen(frames) + PREV_X]
-			y = frames[zlen(frames) + PREV_Y]
-		else
-			-- Interpolate between the previous frame and the current frame.
-			local frame = binarySearch(frames, time, ENTRIES)
-			x = frames[frame + PREV_X]
-			y = frames[frame + PREV_Y]
-			local frameTime = frames[frame]
-			local percent = self:getCurvePercent(math_floor(frame / ENTRIES) - 1,
-				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
-
-			x = x + (frames[frame + X] - x) * percent
-			y = y + (frames[frame + Y] - y) * percent
-		end
-		if blend == MixBlend.setup then
-			bone.shearX = bone.data.shearX + x * alpha
-			bone.shearY = bone.data.shearY + y * alpha
-		elseif blend == MixBlend.first or blend == MixBlend.replace then
-			bone.shearX = bone.shearX + (bone.data.shearX + x - bone.shearX) * alpha
-			bone.shearY = bone.shearY + (bone.data.shearY + y - bone.shearY) * alpha
-		elseif blend == MixBlend.add then
-			bone.shearX = bone.shearX + x * alpha
-			bone.shearY = bone.shearY + y * alpha
-		end
-	end
-
-	return self
-end
-
-Animation.ColorTimeline = {}
-Animation.ColorTimeline.ENTRIES = 5
-function Animation.ColorTimeline.new (frameCount)
-	local ENTRIES = Animation.ColorTimeline.ENTRIES
-	local PREV_TIME = -5
-	local PREV_R = -4
-	local PREV_G = -3
-	local PREV_B = -2
-	local PREV_A = -1
-	local R = 1
-	local G = 2
-	local B = 3
-	local A = 4
-
-	local self = Animation.CurveTimeline.new(frameCount)
-	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
-	self.slotIndex = -1
-	self.type = TimelineType.color
-
-	function self:getPropertyId ()
-		return TimelineType.color * SHL_24 + self.slotIndex
-	end
-
-	function self:setFrame (frameIndex, time, r, g, b, a)
-		frameIndex = frameIndex * ENTRIES
-		self.frames[frameIndex] = time
-		self.frames[frameIndex + R] = r
-		self.frames[frameIndex + G] = g
-		self.frames[frameIndex + B] = b
-		self.frames[frameIndex + A] = a
-	end
-
-	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
-		local frames = self.frames
-		local slot = skeleton.slots[self.slotIndex]
-		if not slot.bone.active then return end
-		if time < frames[0] then
-			if blend == MixBlend.setup then
-				slot.color:setFrom(slot.data.color)
-			elseif blend == MixBlend.first then
-				local color = slot.color
-				local setup = slot.data.color
-				color:add((setup.r - color.r) * alpha, (setup.g - color.g) * alpha, (setup.b - color.b) * alpha,
-						(setup.a - color.a) * alpha)
-			end
-			return
-		end
-
-		local r, g, b, a
-		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
-			local i = zlen(frames)
-			r = frames[i + PREV_R]
-			g = frames[i + PREV_G]
-			b = frames[i + PREV_B]
-			a = frames[i + PREV_A]
-		else
-			-- Interpolate between the last frame and the current frame.
-			local frame = binarySearch(frames, time, ENTRIES)
-			r = frames[frame + PREV_R]
-			g = frames[frame + PREV_G]
-			b = frames[frame + PREV_B]
-			a = frames[frame + PREV_A]
-			local frameTime = frames[frame]
-			local percent = self:getCurvePercent(math.floor(frame / ENTRIES) - 1,
-					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
-
-			r = r + (frames[frame + R] - r) * percent
-			g = g + (frames[frame + G] - g) * percent
-			b = b + (frames[frame + B] - b) * percent
-			a = a + (frames[frame + A] - a) * percent
-		end
-		if alpha == 1 then
-			slot.color:set(r, g, b, a)
-		else
-			local color = slot.color
-			if blend == MixBlend.setup then color:setFrom(slot.data.color) end
-			color:add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha)
-		end
-	end
-
-	return self
-end
-
-Animation.TwoColorTimeline = {}
-Animation.TwoColorTimeline.ENTRIES = 8
-function Animation.TwoColorTimeline.new (frameCount)
-	local ENTRIES = Animation.TwoColorTimeline.ENTRIES
-	local PREV_TIME = -8
-	local PREV_R = -7
-	local PREV_G = -6
-	local PREV_B = -5
-	local PREV_A = -4
-	local PREV_R2 = -3
-	local PREV_G2 = -2
-	local PREV_B2 = -1
-	local R = 1
-	local G = 2
-	local B = 3
-	local A = 4
-	local R2 = 5
-	local G2 = 6
-	local B2 = 7
-
-	local self = Animation.CurveTimeline.new(frameCount)
-	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
-	self.slotIndex = -1
-	self.type = TimelineType.twoColor
-
-	function self:getPropertyId ()
-		return TimelineType.twoColor * SHL_24 + self.slotIndex
-	end
-
-	function self:setFrame (frameIndex, time, r, g, b, a, r2, g2, b2)
-		frameIndex = frameIndex * ENTRIES
-		self.frames[frameIndex] = time
-		self.frames[frameIndex + R] = r
-		self.frames[frameIndex + G] = g
-		self.frames[frameIndex + B] = b
-		self.frames[frameIndex + A] = a
-		self.frames[frameIndex + R2] = r2
-		self.frames[frameIndex + G2] = g2
-		self.frames[frameIndex + B2] = b2
-	end
-
-	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
-		local frames = self.frames
-		local slot = skeleton.slots[self.slotIndex]
-		if not slot.bone.active then return end
-
-		if time < frames[0] then
-			if blend == MixBlend.setup then
-				slot.color:setFrom(slot.data.color)
-				slot.darkColor:setFrom(slot.data.darkColor)
-			elseif blend == MixBlend.first then
-				local light = slot.color
-				local dark = slot.darkColor
-				local setupLight = slot.data.color
-				local setupDark = slot.data.darkColor
-				light:add((setupLight.r - light.r) * alpha, (setupLight.g - light.g) * alpha, (setupLight.b - light.b) * alpha,
-					(setupLight.a - light.a) * alpha)
-				dark:add((setupDark.r - dark.r) * alpha, (setupDark.g - dark.g) * alpha, (setupDark.b - dark.b) * alpha, 0)
-			end
-			return
-		end
-
-		local r, g, b, a, r2, g2, b2
-		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
-			local i = zlen(frames)
-			r = frames[i + PREV_R]
-			g = frames[i + PREV_G]
-			b = frames[i + PREV_B]
-			a = frames[i + PREV_A]
-			r2 = frames[i + PREV_R2]
-			g2 = frames[i + PREV_G2]
-			b2 = frames[i + PREV_B2]
-		else
-			-- Interpolate between the last frame and the current frame.
-			local frame = binarySearch(frames, time, ENTRIES)
-			r = frames[frame + PREV_R]
-			g = frames[frame + PREV_G]
-			b = frames[frame + PREV_B]
-			a = frames[frame + PREV_A]
-			r2 = frames[frame + PREV_R2]
-			g2 = frames[frame + PREV_G2]
-			b2 = frames[frame + PREV_B2]
-			local frameTime = frames[frame]
-			local percent = self:getCurvePercent(math.floor(frame / ENTRIES) - 1,
-					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
-
-			r = r + (frames[frame + R] - r) * percent
-			g = g + (frames[frame + G] - g) * percent
-			b = b + (frames[frame + B] - b) * percent
-			a = a + (frames[frame + A] - a) * percent
-			r2 = r2 + (frames[frame + R2] - r2) * percent
-			g2 = g2 + (frames[frame + G2] - g2) * percent
-			b2 = b2 + (frames[frame + B2] - b2) * percent
-		end
-		if alpha == 1 then
-			slot.color:set(r, g, b, a)
-			slot.darkColor:set(r2, g2, b2, 1)
-		else
-			local light = slot.color
-			local dark = slot.darkColor
-			if blend == MixBlend.setup then
-				light:setFrom(slot.data.color)
-				dark:setFrom(slot.data.darkColor)
-			end
-			light:add((r - light.r) * alpha, (g - light.g) * alpha, (b - light.b) * alpha, (a - light.a) * alpha)
-			dark:add((r2 - dark.r) * alpha, (g2 - dark.g) * alpha, (b2 - dark.b) * alpha, 0)
-		end
-	end
-
-	return self
-end
-
-Animation.AttachmentTimeline = {}
-function Animation.AttachmentTimeline.new (frameCount)
-	local self = {
-		frames = utils.newNumberArrayZero(frameCount), -- time, ...
-		attachmentNames = {},
-		slotIndex = -1,
-		type = TimelineType.attachment
-	}
-
-	function self:getFrameCount ()
-		return zlen(self.frames)
-	end
-
-	function self:setFrame (frameIndex, time, attachmentName)
-		self.frames[frameIndex] = time
-		self.attachmentNames[frameIndex] = attachmentName
-	end
-
-	function self:getPropertyId ()
-		return TimelineType.attachment * SHL_24 + self.slotIndex
-	end
-
-	function self:setAttachment(skeleton, slot, attachmentName)
-		attachmentName = slot.data.attachmentName
-		if not attachmentName then
-			slot:setAttachment(nil)
-		else
-			slot:setAttachment(skeleton:getAttachmentByIndex(self.slotIndex, attachmentName))
-		end
-	end
-
-	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
-		local slot = skeleton.slots[self.slotIndex]
-		if not slot.bone.active then return end
-		local attachmentName
-		if direction == MixDirection.out then
-			if blend == MixBlend.setup then
-				self:setAttachment(skeleton, slot, slot.data.attachmentName)
-			end
-			return;
-		end
-
-		local frames = self.frames
-		if time < frames[0] then
-			if blend == MixBlend.setup or blend == MixBlend.first then
-				self:setAttachment(skeleton, slot, slot.data.attachmentName)
-			end
-			return
-		end
-
-		local frameIndex = 0
-		if time >= frames[zlen(frames) - 1] then
-			frameIndex = zlen(frames) - 1
-		else
-			frameIndex = binarySearch(frames, time, 1) - 1
-		end
-
-		attachmentName = self.attachmentNames[frameIndex]
-		if not attachmentName then
-			slot:setAttachment(nil)
-		else
-			slot:setAttachment(skeleton:getAttachmentByIndex(self.slotIndex, attachmentName))
-		end
-	end
-
-	return self
-end
-
-Animation.DeformTimeline = {}
-function Animation.DeformTimeline.new (frameCount)
-	local self = Animation.CurveTimeline.new(frameCount)
-	self.frames = utils.newNumberArrayZero(frameCount)
-	self.frameVertices = utils.newNumberArrayZero(frameCount)
-	self.slotIndex = -1
-	self.attachment = nil
-	self.type = TimelineType.deform
-
-	function self:getPropertyId ()
-		return TimelineType.deform * SHL_27 + self.attachment.id + self.slotIndex
-	end
-
-	function self:setFrame (frameIndex, time, vertices)
-		self.frames[frameIndex] = time
-		self.frameVertices[frameIndex] = vertices
-	end
-
-	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
-		local slot = skeleton.slots[self.slotIndex]
-		if not slot.bone.active then return end
-
-		local slotAttachment = slot.attachment
-		if not slotAttachment then return end
-		if not (slotAttachment.type == AttachmentType.mesh or slotAttachment.type == AttachmentType.linkedmesh or slotAttachment.type == AttachmentType.path or slotAttachment.type == AttachmentType.boundingbox) then return end
-		if slotAttachment.deformAttachment ~= self.attachment then return end
-
-		local frames = self.frames
-		local deformArray = slot.deform
-		if #(deformArray) == 0 then blend = MixBlend.setup end
-
-		local frameVertices = self.frameVertices
-		local vertexCount = #(frameVertices[0])
-
-		if time < frames[0] then
-			local vertexAttachment = slotAttachment;
-			if blend == MixBlend.setup then
-				slot.deform = {}
-				return;
-			elseif blend == MixBlend.first then
-				if (alpha == 1) then
-					slot.deform = {}
-					return;
-				end
-
-				local deform = utils.setArraySize(deformArray, vertexCount)
-				if (vertexAttachment.bones == nil) then
-					local setupVertices = vertexAttachment.vertices
-					local i = 1
-					while i <= vertexCount do
-						deform[i] = deform[i] + (setupVertices[i] - deform[i]) * alpha
-						i = i + 1
-					end
-				else
-					alpha = 1 - alpha
-					local i = 1
-					while i <= vertexCount do
-						deform[i] = deform[i] * alpha
-						i = i + 1
-					end
-				end
-			end
-			return
-		end
-
-		local deform = utils.setArraySize(deformArray, vertexCount)
-		if time >= frames[zlen(frames) - 1] then -- Time is after last frame.
-			local lastVertices = frameVertices[zlen(frames) - 1]
-			if alpha == 1 then
-				if blend == MixBlend.add then
-					local vertexAttachment = slotAttachment
-					if vertexAttachment.bones == nil then
-						-- Unweighted vertex positions, with alpha.
-						local setupVertices = vertexAttachment.vertices
-						local i = 1
-						while i <= vertexCount do
-							deform[i] = deform[i] + lastVertices[i] - setupVertices[i]
-							i = i + 1
-						end
-					else
-						-- Weighted deform offsets, with alpha.
-						local i = 1
-						while i <= vertexCount do
-							deform[i] = deform[i] + lastVertices[i]
-							i = i + 1
-						end
-					end
-				else
-					local i = 1
-					while i <= vertexCount do
-						deform[i] = lastVertices[i]
-						i = i + 1
-					end
-				end
-			else
-				if blend == MixBlend.setup then
-					local vertexAttachment = slotAttachment
-					if vertexAttachment.bones == nil then
-						-- Unweighted vertex positions, with alpha.
-						local setupVertices = vertexAttachment.vertices
-						local i = 1
-						while i <= vertexCount do
-							local setup = setupVertices[i]
-							deform[i] = setup + (lastVertices[i] - setup) * alpha
-							i = i + 1
-						end
-					else
-						-- Weighted deform offsets, with alpha.
-						local i = 1
-						while i <= vertexCount do
-							deform[i] = lastVertices[i] * alpha
-							i = i + 1
-						end
-					end
-				elseif blend == MixBlend.first or blend == MixBlend.replace then
-					local i = 1
-					while i <= vertexCount do
-						deform[i] = deform[i] + (lastVertices[i] - deform[i]) * alpha
-						i = i + 1
-					end
-					local vertexAttachment = slotAttachment
-					if vertexAttachment.bones == nil then
-						local setupVertices = vertexAttachment.vertices
-						local i = 1
-						while i <= vertexCount do
-							deform[i] = deform[i] + (lastVertices[i] - setupVertices[i]) * alpha
-							i = i + 1
-						end
-					else
-						-- Weighted deform offsets, with alpha.
-						local i = 1
-						while i <= vertexCount do
-							deform[i] = deform[i] + lastVertices[i] * alpha
-							i = i + 1
-						end
-					end
-				elseif blend == MixBlend.add then
-					local vertexAttachment = slotAttachment
-					if vertexAttachment.bones == nil then
-						local setupVertices = vertexAttachment.vertices
-						local i = 1
-						while i <= vertexCount do
-							deform[i] = deform[i] + (lastVertices[i] - setupVertices[i]) * alpha
-							i = i + 1
-						end
-					else
-						-- Weighted deform offsets, with alpha.
-						local i = 1
-						while i <= vertexCount do
-							deform[i] = deform[i] + lastVertices[i] * alpha
-							i = i + 1
-						end
-					end
-				end
-			end
-			return;
-		end
-
-		-- Interpolate between the previous frame and the current frame.
-		local frame = binarySearch(frames, time, 1)
-		local prevVertices = frameVertices[frame - 1]
-		local nextVertices = frameVertices[frame]
-		local frameTime = frames[frame]
-		local percent = self:getCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime))
-
-		if alpha == 1 then
-			if blend == MixBlend.add then
-				local vertexAttachment = slotAttachment
-				if vertexAttachment.bones == nil then
-					-- Unweighted vertex positions, with alpha.
-					local setupVertices = vertexAttachment.vertices
-					local i = 1
-					while i <= vertexCount do
-						local prev = prevVertices[i]
-						deform[i] = deform[i] + prev + (nextVertices[i] - prev) * precent - setupVertices[i]
-						i = i + 1
-					end
-				else
-					-- Weighted deform offsets, with alpha.
-					local i = 1
-					while i <= vertexCount do
-						local prev = prevVertices[i]
-						deform[i] = deform[i] + prev + (nextVertices[i] - prev) * percent
-						i = i + 1
-					end
-				end
-			else
-				local i = 1
-				while i <= vertexCount do
-					local prev = prevVertices[i]
-					deform[i] = prev + (nextVertices[i] - prev) * percent
-					i = i + 1
-				end
-			end
-		else
-			if blend == MixBlend.setup then
-				local vertexAttachment = slotAttachment
-				if vertexAttachment.bones == nil then
-					-- Unweighted vertex positions, with alpha.
-					local setupVertices = vertexAttachment.vertices
-					local i = 1
-					while i <= vertexCount do
-						local prev = prevVertices[i]
-						local setup = setupVertices[i]
-						deform[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha
-						i = i + 1
-					end
-				else
-					-- Weighted deform offsets, with alpha.
-					local i = 1
-					while i <= vertexCount do
-						local prev = prevVertices[i]
-						deform[i] = (prev + (nextVertices[i] - prev) * percent) * alpha
-						i = i + 1
-					end
-				end
-			elseif blend == MixBlend.first or blend == MixBlend.replace then
-				local i = 1
-				while i <= vertexCount do
-					local prev = prevVertices[i]
-					deform[i] = deform[i] + (prev + (nextVertices[i] - prev) * percent - deform[i]) * alpha
-					i = i + 1
-				end
-			elseif blend == MixBlend.add then
-				local vertexAttachment = slotAttachment
-				if vertexAttachment.bones == nil then
-					local setupVertices = vertexAttachment.vertices
-					local i = 1
-					while i <= vertexCount do
-						local prev = prevVertices[i]
-						deform[i] = deform[i] + (prev + (nextVertices[i] - prev) * percent - setupVertices[i]) * alpha
-						i = i + 1
-					end
-				else
-					-- Weighted deform offsets, with alpha.
-					local i = 1
-					while i <= vertexCount do
-						local prev = prevVertices[i]
-						deform[i] = deform[i] + (prev + (nextVertices[i] - prev) * percent) * alpha
-						i = i + 1
-					end
-				end
-			end
-		end
-	end
-
-	return self
-end
-
-Animation.EventTimeline = {}
-function Animation.EventTimeline.new (frameCount)
-	local self = {
-		frames = utils.newNumberArrayZero(frameCount),
-		events = {},
-		type = TimelineType.event
-	}
-
-	function self:getPropertyId ()
-		return TimelineType.event * SHL_24
-	end
-
-	function self:getFrameCount ()
-		return zlen(self.frames)
-	end
-
-	function self:setFrame (frameIndex, event)
-		self.frames[frameIndex] = event.time
-		self.events[frameIndex] = event
-	end
-
-	-- Fires events for frames > lastTime and <= time.
-	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
-		if not firedEvents then return end
-
-		local frames = self.frames
-		local frameCount = zlen(frames)
-
-		if lastTime > time then -- Fire events after last time for looped animations.
-			self:apply(skeleton, lastTime, 999999, firedEvents, alpha, blend, direction)
-			lastTime = -1
-		elseif lastTime >= frames[frameCount - 1] then -- Last time is after last frame.
-			return
-		end
-		if time < frames[0] then return end -- Time is before first frame.
-
-		local frame
-		if lastTime < frames[0] then
-			frame = 0
-		else
-			frame = binarySearch1(frames, lastTime)
-			local frame = frames[frame]
-			while frame > 0 do -- Fire multiple events with the same frame.
-				if frames[frame - 1] ~= frame then break end
-				frame = frame - 1
-			end
-		end
-		local events = self.events
-		while frame < frameCount and time >= frames[frame] do
-			table.insert(firedEvents, events[frame])
-			frame = frame + 1
-		end
-	end
-
-	return self
-end
-
-Animation.DrawOrderTimeline = {}
-function Animation.DrawOrderTimeline.new (frameCount)
-	local self = {
-		frames = utils.newNumberArrayZero(frameCount),
-		drawOrders = {},
-		type = TimelineType.drawOrder
-	}
-
-	function self:getPropertyId ()
-		return TimelineType.drawOrder * SHL_24
-	end
-
-	function self:getFrameCount ()
-		return zlen(self.frames)
-	end
-
-	function self:setFrame (frameIndex, time, drawOrder)
-		self.frames[frameIndex] = time
-		self.drawOrders[frameIndex] = drawOrder
-	end
-
-	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
-		local drawOrder = skeleton.drawOrder
-		local slots = skeleton.slots
-		if direction == MixDirection.out then
-			if blend == MixBlend.setup then
-				for i,slot in ipairs(slots) do
-					drawOrder[i] = slots[i]
-				end
-			end
-			return;
-		end
-
-		local frames = self.frames
-		if time < frames[0] then
-			if blend == MixBlend.setup or blend == MixBlend.first then
-				for i,slot in ipairs(slots) do
-					drawOrder[i] = slots[i]
-				end
-			end
-			return
-		end
-
-		local frame
-		if time >= frames[zlen(frames) - 1] then -- Time is after last frame.
-			frame = zlen(frames) - 1
-		else
-			frame = binarySearch1(frames, time) - 1
-		end
-
-		local drawOrderToSetupIndex = self.drawOrders[frame]
-		if not drawOrderToSetupIndex then
-			for i,slot in ipairs(slots) do
-				drawOrder[i] = slots[i]
-			end
-		else
-			for i,setupIndex in ipairs(drawOrderToSetupIndex) do
-				drawOrder[i] = skeleton.slots[setupIndex]
-			end
-		end
-	end
-
-	return self
-end
-
-Animation.IkConstraintTimeline = {}
-Animation.IkConstraintTimeline.ENTRIES = 6
-function Animation.IkConstraintTimeline.new (frameCount)
-	local ENTRIES = Animation.IkConstraintTimeline.ENTRIES
-	local PREV_TIME = -6
-	local PREV_MIX = -5
-	local PREV_SOFTNESS = -4
-	local PREV_BEND_DIRECTION = -3
-	local PREV_COMPRESS = -2
-	local PREV_STRETCH = -1
-	local MIX = 1
-	local SOFTNESS = 2
-	local BEND_DIRECTION = 3
-	local COMPRESS = 4
-	local STRETCH = 5
-
-	local self = Animation.CurveTimeline.new(frameCount)
-	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES) -- time, mix, softness, bendDirection, compress, stretch, ...
-	self.ikConstraintIndex = -1
-	self.type = TimelineType.ikConstraint
-
-	function self:getPropertyId ()
-		return TimelineType.ikConstraint * SHL_24 + self.ikConstraintIndex
-	end
-
-	function self:setFrame (frameIndex, time, mix, softness, bendDirection, compress, stretch)
-		frameIndex = frameIndex * ENTRIES
-		self.frames[frameIndex] = time
-		self.frames[frameIndex + MIX] = mix
-		self.frames[frameIndex + SOFTNESS] = softness
-		self.frames[frameIndex + BEND_DIRECTION] = bendDirection
-		if (compress) then
-			self.frames[frameIndex + COMPRESS] = 1
-		else
-			self.frames[frameIndex + COMPRESS] = 0
-		end
-		if (stretch) then
-			self.frames[frameIndex + STRETCH] = 1
-		else
-			self.frames[frameIndex + STRETCH] = 0
-		end
-	end
-
-	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
-		local frames = self.frames
-
-		local constraint = skeleton.ikConstraints[self.ikConstraintIndex]
-		if not constraint.active then return end
-		if time < frames[0] then
-			if blend == MixBlend.setup then
-				constraint.mix = constraint.data.mix
-				constraint.softness = constraint.data.softness
-				constraint.bendDirection = constraint.data.bendDirection
-				constraint.compress = constraint.data.compress
-				constraint.stretch = constraint.data.stretch
-			elseif blend == MixBlend.first then
-				constraint.mix = constraint.mix + (constraint.data.mix - constraint.mix) * alpha
-				constraint.softness = constraint.softness + (constraint.data.softness - constraint.softness) * alpha
-				constraint.bendDirection = constraint.data.bendDirection
-				constraint.compress = constraint.data.compress
-				constraint.stretch = constraint.data.stretch
-			end
-			return
-		end
-
-		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
-			if blend == MixBlend.setup then
-				constraint.mix = constraint.data.mix + (frames[zlen(frames) + PREV_MIX] - constraint.data.mix) * alpha
-				constraint.softness = constraint.data.softness + (frames[zlen(frames) + PREV_SOFTNESS] - constraint.data.softness) * alpha
-				if direction == MixDirection.out then
-					constraint.bendDirection = constraint.data.bendDirection
-					constraint.compress = constraint.data.compress
-					constraint.stretch = constraint.data.stretch
-				else
-					constraint.bendDirection = math_floor(frames[zlen(frames) + PREV_BEND_DIRECTION]);
-					if (math_floor(frames[zlen(frames) + PREV_COMPRESS]) == 1) then constraint.compress = true else constraint.compress = false end
-					if (math_floor(frames[zlen(frames) + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end
-				end
-			else
-				constraint.mix = constraint.mix + (frames[zlen(frames) + PREV_MIX] - constraint.mix) * alpha
-				constraint.softness = constraint.softness + (frames[zlen(frames) + PREV_SOFTNESS] - constraint.softness) * alpha
-				if direction == MixDirection._in then
-					constraint.bendDirection = math_floor(frames[zlen(frames) + PREV_BEND_DIRECTION])
-					if (math_floor(frames[zlen(frames) + PREV_COMPRESS]) == 1) then constraint.compress = true else constraint.compress = false end
-					if (math_floor(frames[zlen(frames) + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end
-				end
-			end
-			return
-		end
-
-		-- Interpolate between the previous frame and the current frame.
-		local frame = binarySearch(frames, time, ENTRIES)
-		local mix = frames[frame + PREV_MIX]
-		local softness = frames[frame + PREV_SOFTNESS]
-		local frameTime = frames[frame]
-		local percent = self:getCurvePercent(math.floor(frame / ENTRIES) - 1,
-				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
-
-		if blend == MixBlend.setup then
-			constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha
-			constraint.softness = constraint.data.softness + (softness + (frames[frame + SOFTNESS] - softness) * percent - constraint.data.softness) * alpha
-			if direction == MixDirection.out then
-				constraint.bendDirection = constraint.data.bendDirection
-				constraint.compress = constraint.data.compress
-				constraint.stretch = constraint.data.stretch
-			else
-				constraint.bendDirection = math_floor(frames[frame + PREV_BEND_DIRECTION])
-				if (math_floor(frames[frame + PREV_COMPRESS]) == 1) then constraint.compress = true else constraint.compress = false end
-				if (math_floor(frames[frame + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end
-			end
-		else
-			constraint.mix = constraint.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha
-			constraint.softness = constraint.softness + (softness + (frames[frame + SOFTNESS] - softness) * percent - constraint.softness) * alpha
-			if direction == MixDirection._in then
-				constraint.bendDirection = math_floor(frames[frame + PREV_BEND_DIRECTION])
-				if (math_floor(frames[frame + PREV_COMPRESS]) == 1) then constraint.compress = true else constraint.compress = false end
-				if (math_floor(frames[frame + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end
-			end
-		end
-	end
-
-	return self
-end
-
-Animation.TransformConstraintTimeline = {}
-Animation.TransformConstraintTimeline.ENTRIES = 5
-function Animation.TransformConstraintTimeline.new (frameCount)
-	local ENTRIES = Animation.TransformConstraintTimeline.ENTRIES
-	local PREV_TIME = -5
-	local PREV_ROTATE = -4
-	local PREV_TRANSLATE = -3
-	local PREV_SCALE = -2
-	local PREV_SHEAR = -1
-	local ROTATE = 1
-	local TRANSLATE = 2
-	local SCALE = 3
-	local SHEAR = 4
-
-	local self = Animation.CurveTimeline.new(frameCount)
-	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
-	self.transformConstraintIndex = -1
-	self.type = TimelineType.transformConstraint
-
-	function self:getPropertyId ()
-		return TimelineType.transformConstraint * SHL_24 + self.transformConstraintIndex
-	end
-
-	function self:setFrame (frameIndex, time, rotateMix, translateMix, scaleMix, shearMix)
-		frameIndex = frameIndex * ENTRIES
-		self.frames[frameIndex] = time
-		self.frames[frameIndex + ROTATE] = rotateMix
-		self.frames[frameIndex + TRANSLATE] = translateMix
-		self.frames[frameIndex + SCALE] = scaleMix
-		self.frames[frameIndex + SHEAR] = shearMix
-	end
-
-	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
-		local frames = self.frames
-
-		local constraint = skeleton.transformConstraints[self.transformConstraintIndex]
-		if not constraint.active then return end
-
-		if time < frames[0] then
-			local data = constraint.data
-			if blend == MixBlend.setup then
-				constraint.rotateMix = data.rotateMix
-				constraint.translateMix = data.translateMix
-				constraint.scaleMix = data.scaleMix
-				constraint.shearMix = data.shearMix
-			elseif blend == MixBlend.first then
-				constraint.rotateMix = constraint.rotateMix + (data.rotateMix - constraint.rotateMix) * alpha
-				constraint.translateMix = constraint.translateMix + (data.translateMix - constraint.translateMix) * alpha
-				constraint.scaleMix = constraint.scaleMix + (data.scaleMix - constraint.scaleMix) * alpha
-				constraint.shearMix = constraint.shearMix + (data.shearMix - constraint.shearMix) * alpha
-			end
-			return
-		end
-
-		local rotate = 0
-		local translate = 0
-		local scale = 0
-		local shear = 0
-		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
-			local i = zlen(frames)
-			rotate = frames[i + PREV_ROTATE]
-			translate = frames[i + PREV_TRANSLATE]
-			scale = frames[i + PREV_SCALE]
-			shear = frames[i + PREV_SHEAR]
-		else
-			-- Interpolate between the previous frame and the current frame.
-			local frame = binarySearch(frames, time, ENTRIES)
-			rotate = frames[frame + PREV_ROTATE]
-			translate = frames[frame + PREV_TRANSLATE]
-			scale = frames[frame + PREV_SCALE]
-			shear = frames[frame + PREV_SHEAR]
-			local frameTime = frames[frame]
-			local percent = self:getCurvePercent(math_floor(frame / ENTRIES) - 1,
-				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
-
-			rotate = rotate + (frames[frame + ROTATE] - rotate) * percent
-			translate = translate + (frames[frame + TRANSLATE] - translate) * percent
-			scale = scale + (frames[frame + SCALE] - scale) * percent
-			shear = shear + (frames[frame + SHEAR] - shear) * percent
-		end
-		if blend == MixBlend.setup then
-			local data = constraint.data
-			constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha
-			constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha
-			constraint.scaleMix = data.scaleMix + (scale - data.scaleMix) * alpha
-			constraint.shearMix = data.shearMix + (shear - data.shearMix) * alpha
-		else
-			constraint.rotateMix = constraint.rotateMix + (rotate - constraint.rotateMix) * alpha
-			constraint.translateMix = constraint.translateMix + (translate - constraint.translateMix) * alpha
-			constraint.scaleMix = constraint.scaleMix + (scale - constraint.scaleMix) * alpha
-			constraint.shearMix = constraint.shearMix + (shear - constraint.shearMix) * alpha
-		end
-	end
-
-	return self
-end
-
-Animation.PathConstraintPositionTimeline = {}
-Animation.PathConstraintPositionTimeline.ENTRIES = 2
-function Animation.PathConstraintPositionTimeline.new (frameCount)
-	local ENTRIES = Animation.PathConstraintPositionTimeline.ENTRIES
-	local PREV_TIME = -2
-	local PREV_VALUE = -1
-	local VALUE = 1
-
-	local self = Animation.CurveTimeline.new(frameCount)
-	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
-	self.pathConstraintIndex = -1
-	self.type = TimelineType.pathConstraintPosition
-
-	function self:getPropertyId ()
-		return TimelineType.pathConstraintPosition * SHL_24 + self.pathConstraintIndex
-	end
-
-	function self:setFrame (frameIndex, time, value)
-		frameIndex = frameIndex * ENTRIES
-		self.frames[frameIndex] = time
-		self.frames[frameIndex + VALUE] = value
-	end
-
-	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
-		local frames = self.frames
-
-		local constraint = skeleton.pathConstraints[self.pathConstraintIndex]
-		if not constraint.active then return end
-
-		if (time < frames[0]) then
-			if blend == MixBlend.setup then
-				constraint.position = constraint.data.position
-			elseif blend == MixBlend.first then
-				constraint.position = constraint.position + (constraint.data.position - constraint.position) * alpha
-			end
-			return
-		end
-
-		local position = 0
-		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
-			position = frames[zlen(frames) + PREV_VALUE]
-		else
-			-- Interpolate between the previous frame and the current frame.
-			local frame = binarySearch(frames, time, ENTRIES)
-			position = frames[frame + PREV_VALUE]
-			local frameTime = frames[frame]
-			local percent = self:getCurvePercent(math_floor(frame / ENTRIES) - 1,
-				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
-
-			position = position + (frames[frame + VALUE] - position) * percent
-		end
-		if blend == MixBlend.setup then
-			constraint.position = constraint.data.position + (position - constraint.data.position) * alpha
-		else
-			constraint.position = constraint.position + (position - constraint.position) * alpha
-		end
-	end
-
-	return self
-end
-
-Animation.PathConstraintSpacingTimeline = {}
-Animation.PathConstraintSpacingTimeline.ENTRIES = 2
-function Animation.PathConstraintSpacingTimeline.new (frameCount)
-	local ENTRIES = Animation.PathConstraintSpacingTimeline.ENTRIES
-	local PREV_TIME = -2
-	local PREV_VALUE = -1
-	local VALUE = 1
-
-	local self = Animation.CurveTimeline.new(frameCount)
-	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
-	self.pathConstraintIndex = -1
-	self.type = TimelineType.pathConstraintSpacing
-
-	function self:getPropertyId ()
-		return TimelineType.pathConstraintSpacing * SHL_24 + self.pathConstraintIndex
-	end
-
-	function self:setFrame (frameIndex, time, value)
-		frameIndex = frameIndex * ENTRIES
-		self.frames[frameIndex] = time
-		self.frames[frameIndex + VALUE] = value
-	end
-
-	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
-		local frames = self.frames
-
-		local constraint = skeleton.pathConstraints[self.pathConstraintIndex]
-		if not constraint.active then return end
-
-		if (time < frames[0]) then
-			if blend == MixBlend.setup then
-				constraint.spacing = constraint.data.spacing
-			elseif blend == MixBlend.first then
-				constraint.spacing = constraint.spacing + (constraint.data.spacing - constraint.spacing) * alpha
-			end
-			return
-		end
-
-		local spacing = 0
-		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
-			spacing = frames[zlen(frames) + PREV_VALUE]
-		else
-			-- Interpolate between the previous frame and the current frame.
-			local frame = binarySearch(frames, time, ENTRIES)
-			spacing = frames[frame + PREV_VALUE]
-			local frameTime = frames[frame]
-			local percent = self:getCurvePercent(math_floor(frame / ENTRIES) - 1,
-				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
-
-			spacing = spacing + (frames[frame + VALUE] - spacing) * percent
-		end
-
-		if blend == MixBlend.setup then
-			constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha
-		else
-			constraint.spacing = constraint.spacing + (spacing - constraint.spacing) * alpha
-		end
-	end
-
-	return self
-end
-
-Animation.PathConstraintMixTimeline = {}
-Animation.PathConstraintMixTimeline.ENTRIES = 3
-function Animation.PathConstraintMixTimeline.new (frameCount)
-	local ENTRIES = Animation.PathConstraintMixTimeline.ENTRIES
-	local PREV_TIME = -3
-	local PREV_ROTATE = -2
-	local PREV_TRANSLATE = -1
-	local ROTATE = 1
-	local TRANSLATE = 2
-
-	local self = Animation.CurveTimeline.new(frameCount)
-	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
-	self.pathConstraintIndex = -1
-	self.type = TimelineType.pathConstraintMix
-
-	function self:getPropertyId ()
-		return TimelineType.pathConstraintMix * SHL_24 + self.pathConstraintIndex
-	end
-
-	function self:setFrame (frameIndex, time, rotateMix, translateMix)
-		frameIndex = frameIndex * ENTRIES
-		self.frames[frameIndex] = time
-		self.frames[frameIndex + ROTATE] = rotateMix
-		self.frames[frameIndex + TRANSLATE] = translateMix
-	end
-
-	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
-		local frames = self.frames
-
-		local constraint = skeleton.pathConstraints[self.pathConstraintIndex]
-		if not constraint.active then return end
-
-		if (time < frames[0]) then
-			if blend == MixBlend.setup then
-				constraint.rotateMix = constraint.data.rotateMix
-				constraint.translateMix = constraint.data.translateMix
-			elseif blend == MixBlend.first then
-				constraint.rotateMix = constraint.rotateMix + (constraint.data.rotateMix - constraint.rotateMix) * alpha
-				constraint.translateMix = constraint.translateMix + (constraint.data.translateMix - constraint.translateMix) * alpha
-			end
-			return
-		end
-
-		local rotate = 0
-		local translate = 0
-		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
-			rotate = frames[zlen(frames) + PREV_ROTATE]
-			translate = frames[zlen(frames) + PREV_TRANSLATE]
-		else
-			-- Interpolate between the previous frame and the current frame.
-			local frame = binarySearch(frames, time, ENTRIES)
-			rotate = frames[frame + PREV_ROTATE]
-			translate = frames[frame + PREV_TRANSLATE]
-			local frameTime = frames[frame]
-			local percent = self:getCurvePercent(math_floor(frame / ENTRIES) - 1,
-				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
-
-			rotate = rotate + (frames[frame + ROTATE] - rotate) * percent
-			translate = translate + (frames[frame + TRANSLATE] - translate) * percent
-		end
-
-		if blend == MixBlend.setup then
-			constraint.rotateMix = constraint.data.rotateMix + (rotate - constraint.data.rotateMix) * alpha
-			constraint.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha
-		else
-			constraint.rotateMix = constraint.rotateMix + (rotate - constraint.rotateMix) * alpha
-			constraint.translateMix = constraint.translateMix + (translate - constraint.translateMix) * alpha
-		end
-	end
-
-	return self
-end
-
-return Animation
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+-- FIXME
+-- All the indexing in this file is zero based. We use zlen()
+-- instead of the # operator. Initialization of number arrays
+-- is performed via utils.newNumberArrayZero. This needs
+-- to be rewritten using one-based indexing for better performance
+
+local utils = require "spine-lua.utils"
+local AttachmentType = require "spine-lua.attachments.AttachmentType"
+local math_floor = math.floor
+local math_abs = math.abs
+local math_signum = utils.signum
+
+local function zlen(array)
+	return #array + 1
+end
+
+local Animation = {}
+function Animation.new (name, timelines, duration)
+	if not timelines then error("timelines cannot be nil", 2) end
+
+	local self = {
+		name = name,
+		timelines = timelines,
+		timelineIds = {},
+		duration = duration
+	}
+	
+	for i,timeline in ipairs(self.timelines) do
+		self.timelineIds[timeline:getPropertyId()] = true
+	end
+
+	function self:hasTimeline(id)
+		return self.timelineIds[id] == true
+	end
+
+	function self:apply (skeleton, lastTime, time, loop, events, alpha, blend, direction)
+		if not skeleton then error("skeleton cannot be nil.", 2) end
+
+		if loop and duration > 0 then
+			time = time % self.duration
+			if lastTime > 0 then lastTime = lastTime % self.duration end
+		end
+
+		for i,timeline in ipairs(self.timelines) do
+			timeline:apply(skeleton, lastTime, time, events, alpha, blend, direction)
+		end
+	end
+
+	return self
+end
+
+local function binarySearch (values, target, step)
+	local low = 0
+	local high = math.floor(zlen(values) / step - 2)
+	if high == 0 then return step end
+	local current = math.floor(high / 2)
+	while true do
+		if values[(current + 1) * step] <= target then
+			low = current + 1
+		else
+			high = current
+		end
+		if low == high then return (low + 1) * step end
+		current = math.floor((low + high) / 2)
+	end
+end
+Animation.binarySearch = binarySearch
+
+local function binarySearch1 (values, target)
+	local low = 0
+	local high = math.floor(zlen(values)	- 2)
+	if high == 0 then return 1 end
+	local current = math.floor(high / 2)
+	while true do
+		if values[current + 1] <= target then
+			low = current + 1
+		else
+			high = current
+		end
+		if low == high then return low + 1 end
+		current = math.floor((low + high) / 2)
+	end
+end
+
+local function linearSearch (values, target, step)
+	local i = 0
+	local last = zlen(values) - step
+	while i <= last do
+		if (values[i] > target) then return i end
+		i = i + step
+	end
+	return -1
+end
+
+Animation.MixBlend = {
+	setup = 0,
+	first = 1,
+	replace = 2,
+	add = 3
+}
+local MixBlend = Animation.MixBlend
+
+Animation.MixDirection = {
+	_in = 0, out = 1
+}
+local MixDirection = Animation.MixDirection
+
+Animation.TimelineType = {
+	rotate = 0, translate = 1, scale = 2, shear = 3,
+	attachment = 4, color = 5, deform = 6,
+	event = 7, drawOrder = 8,
+	ikConstraint = 9, transformConstraint = 10,
+	pathConstraintPosition = 11, pathConstraintSpacing = 12, pathConstraintMix = 13,
+	twoColor = 14
+}
+local TimelineType = Animation.TimelineType
+local SHL_24 = 16777216
+local SHL_27 = 134217728
+
+Animation.CurveTimeline = {}
+function Animation.CurveTimeline.new (frameCount)
+	local LINEAR = 0
+	local STEPPED = 1
+	local BEZIER = 2
+	local BEZIER_SIZE = 10 * 2 - 1
+
+	local self = {
+		curves = utils.newNumberArrayZero((frameCount - 1) * BEZIER_SIZE) -- type, x, y, ...
+	}
+
+	function self:getFrameCount ()
+		return math.floor(zlen(self.curves) / BEZIER_SIZE) + 1
+	end
+
+	function self:setStepped (frameIndex)
+		self.curves[frameIndex * BEZIER_SIZE] = STEPPED
+	end
+
+	function self:getCurveType (frameIndex)
+		local index = frameIndex * BEZIER_SIZE
+		if index == zlen(self.curves) then return LINEAR end
+		local type = self.curves[index]
+		if type == LINEAR then return LINEAR end
+		if type == STEPPED then return STEPPED end
+		return BEZIER
+	end
+
+	function self:setCurve (frameIndex, cx1, cy1, cx2, cy2)
+			local tmpx = (-cx1 * 2 + cx2) * 0.03
+			local tmpy = (-cy1 * 2 + cy2) * 0.03
+			local dddfx = ((cx1 - cx2) * 3 + 1) * 0.006
+			local dddfy = ((cy1 - cy2) * 3 + 1) * 0.006
+			local ddfx = tmpx * 2 + dddfx
+			local ddfy = tmpy * 2 + dddfy
+			local dfx = cx1 * 0.3 + tmpx + dddfx * 0.16666667
+			local dfy = cy1 * 0.3 + tmpy + dddfy * 0.16666667
+
+			local i = frameIndex * BEZIER_SIZE
+			local curves = self.curves
+			curves[i] = BEZIER
+			i = i + 1
+
+			local x = dfx
+			local y = dfy
+			local n = i + BEZIER_SIZE - 1
+			while i < n do
+				curves[i] = x
+				curves[i + 1] = y
+				dfx = dfx + ddfx
+				dfy = dfy + ddfy
+				ddfx = ddfx + dddfx
+				ddfy = ddfy + dddfy
+				x = x + dfx
+				y = y + dfy
+				i = i + 2
+			end
+	end
+
+	function self:getCurvePercent (frameIndex, percent)
+		percent = utils.clamp(percent, 0, 1)
+		local curves = self.curves
+		local i = frameIndex * BEZIER_SIZE
+		local type = curves[i]
+		if type == LINEAR then return percent end
+		if type == STEPPED then return 0 end
+		i = i + 1
+		local x
+		local n = i + BEZIER_SIZE - 1
+		local start = i
+		while i < n do
+			x = curves[i]
+			if x >= percent then
+				local prevX, prevY
+				if i == start then
+					prevX = 0
+					prevY = 0
+				else
+					prevX = curves[i - 2]
+					prevY = curves[i - 1]
+				end
+				return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX)
+			end
+			i = i + 2
+		end
+		local y = curves[i - 1]
+		return y + (1 - y) * (percent - x) / (1 - x) -- Last point is 1,1.
+	end
+
+	return self
+end
+
+Animation.RotateTimeline = {}
+Animation.RotateTimeline.ENTRIES = 2
+Animation.RotateTimeline.PREV_TIME = -2
+Animation.RotateTimeline.PREV_ROTATION = -1
+Animation.RotateTimeline.ROTATION = 1
+function Animation.RotateTimeline.new (frameCount)
+	local ENTRIES = Animation.RotateTimeline.ENTRIES
+	local PREV_TIME = Animation.RotateTimeline.PREV_TIME
+	local PREV_ROTATION = Animation.RotateTimeline.PREV_ROTATION
+	local ROTATION = Animation.RotateTimeline.ROTATION
+
+	local self = Animation.CurveTimeline.new(frameCount)
+	self.boneIndex = -1
+	self.frames = utils.newNumberArrayZero(frameCount * 2)
+	self.type = TimelineType.rotate
+
+	function self:getPropertyId ()
+		return TimelineType.rotate * SHL_24 + self.boneIndex
+	end
+
+	function self:setFrame (frameIndex, time, degrees)
+		frameIndex = frameIndex * 2
+		self.frames[frameIndex] = time
+		self.frames[frameIndex + ROTATION] = degrees
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
+		local frames = self.frames
+
+		local bone = skeleton.bones[self.boneIndex]
+		if not bone.active then return end
+		if time < frames[0] then
+			if blend == MixBlend.setup then
+				bone.rotation = bone.data.rotation
+			elseif blend == MixBlend.first then
+				local r = bone.data.rotation - bone.rotation
+				bone.rotation = bone.rotation + (r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360) * alpha
+			end
+			return
+		end
+
+		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
+			local r = frames[zlen(frames) + PREV_ROTATION]
+			if blend == MixBlend.setup then
+				bone.rotation = bone.data.rotation + r * alpha
+			elseif blend == MixBlend.first or blend == MixBlend.replace then
+				r = r + bone.data.rotation - bone.rotation
+				r = r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360 -- Wrap within -180 and 180.
+				bone.rotation = bone.rotation + r * alpha;
+			elseif blend == MixBlend.add then
+				bone.rotation = bone.rotation + r * alpha;
+			end
+			return;
+		end
+
+		-- Interpolate between the last frame and the current frame.
+		local frame = binarySearch(frames, time, ENTRIES)
+		local prevRotation = frames[frame + PREV_ROTATION]
+		local frameTime = frames[frame]
+		local percent = self:getCurvePercent((math.floor(frame / 2)) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
+
+		local r = frames[frame + ROTATION] - prevRotation
+		r = prevRotation + (r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360) * percent
+		if blend == MixBlend.setup then
+			bone.rotation = bone.data.rotation + (r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360) * alpha
+		elseif blend == MixBlend.first or blend == MixBlend.replace then
+			r = r + bone.data.rotation - bone.rotation;
+			bone.rotation = bone.rotation + (r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360) * alpha
+		elseif blend == MixBlend.add then
+			bone.rotation = bone.rotation + (r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360) * alpha
+		end
+	end
+
+	return self
+end
+
+Animation.TranslateTimeline = {}
+Animation.TranslateTimeline.ENTRIES = 3
+function Animation.TranslateTimeline.new (frameCount)
+	local ENTRIES = Animation.TranslateTimeline.ENTRIES
+	local PREV_TIME = -3
+	local PREV_X = -2
+	local PREV_Y = -1
+	local X = 1
+	local Y = 2
+
+	local self = Animation.CurveTimeline.new(frameCount)
+	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
+	self.boneIndex = -1
+	self.type = TimelineType.translate
+
+	function self:getPropertyId ()
+		return TimelineType.translate * SHL_24 + self.boneIndex
+	end
+
+	function self:setFrame (frameIndex, time, x, y)
+		frameIndex = frameIndex * ENTRIES
+		self.frames[frameIndex] = time
+		self.frames[frameIndex + X] = x
+		self.frames[frameIndex + Y] = y
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
+		local frames = self.frames
+
+		local bone = skeleton.bones[self.boneIndex]
+		if not bone.active then return end
+
+		if time < frames[0] then
+			if blend == MixBlend.setup then
+				bone.x = bone.data.x
+				bone.y = bone.data.y
+			elseif blend == MixBlend.first then
+				bone.x = bone.x + (bone.data.x - bone.x) * alpha
+				bone.y = bone.y + (bone.data.y - bone.y) * alpha
+			end
+			return
+		end
+
+		local x = 0
+		local y = 0
+		if time >= frames[zlen(frames) - ENTRIES] then -- // Time is after last frame.
+			x = frames[zlen(frames) + PREV_X];
+			y = frames[zlen(frames) + PREV_Y];
+		else
+			-- Interpolate between the previous frame and the current frame.
+			local frame = binarySearch(frames, time, ENTRIES)
+			x = frames[frame + PREV_X]
+			y = frames[frame + PREV_Y]
+			local frameTime = frames[frame]
+			local percent = self:getCurvePercent(math_floor(frame / ENTRIES) - 1,
+				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+
+			x = x + (frames[frame + X] - x) * percent
+			y = y + (frames[frame + Y] - y) * percent
+		end
+		if blend == MixBlend.setup then
+			bone.x = bone.data.x + x * alpha
+			bone.y = bone.data.y + y * alpha
+		elseif blend == MixBlend.first or blend == MixBlend.replace then
+			bone.x = bone.x + (bone.data.x + x - bone.x) * alpha
+			bone.y = bone.y + (bone.data.y + y - bone.y) * alpha
+		elseif blend == MixBlend.add then
+			bone.x = bone.x + x * alpha
+			bone.y = bone.y + y * alpha
+		end
+	end
+
+	return self
+end
+
+Animation.ScaleTimeline = {}
+Animation.ScaleTimeline.ENTRIES = Animation.TranslateTimeline.ENTRIES
+function Animation.ScaleTimeline.new (frameCount)
+	local ENTRIES = Animation.ScaleTimeline.ENTRIES
+	local PREV_TIME = -3
+	local PREV_X = -2
+	local PREV_Y = -1
+	local X = 1
+	local Y = 2
+
+	local self = Animation.TranslateTimeline.new(frameCount)
+	self.type = TimelineType.scale
+
+	function self:getPropertyId ()
+		return TimelineType.scale * SHL_24 + self.boneIndex
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
+		local frames = self.frames
+
+		local bone = skeleton.bones[self.boneIndex]
+		if not bone.active then return end
+
+		if time < frames[0] then
+			if blend == MixBlend.setup then
+				bone.scaleX = bone.data.scaleX
+				bone.scaleY = bone.data.scaleY
+			elseif blend == MixBlend.first then
+				bone.scaleX = bone.scaleX + (bone.data.scaleX - bone.scaleX) * alpha
+				bone.scaleY = bone.scaleY + (bone.data.scaleY - bone.scaleY) * alpha
+			end
+			return
+		end
+
+		local x = 0
+		local y = 0
+		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
+			x = frames[zlen(frames) + PREV_X] * bone.data.scaleX
+			y = frames[zlen(frames) + PREV_Y] * bone.data.scaleY
+		else
+			-- Interpolate between the previous frame and the current frame.
+			local frame = binarySearch(frames, time, ENTRIES)
+			x = frames[frame + PREV_X]
+			y = frames[frame + PREV_Y]
+			local frameTime = frames[frame]
+			local percent = self:getCurvePercent(math_floor(frame / ENTRIES) - 1,
+				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
+
+			x = (x + (frames[frame + X] - x) * percent) * bone.data.scaleX
+			y = (y + (frames[frame + Y] - y) * percent) * bone.data.scaleY
+		end
+		if alpha == 1 then
+			if blend == MixBlend.add then
+				bone.scaleX = bone.scaleX + x - bone.data.scaleX
+				bone.scaleY = bone.scaleY + y - bone.data.scaleY
+			else
+				bone.scaleX = x
+				bone.scaleY = y
+			end
+		else
+			local bx = 0
+			local by = 0
+			if direction == MixDirection.out then
+				if blend == MixBlend.setup then
+					bx = bone.data.scaleX
+					by = bone.data.scaleY
+					bone.scaleX = bx + (math_abs(x) * math_signum(bx) - bx) * alpha
+					bone.scaleY = by + (math_abs(y) * math_signum(by) - by) * alpha
+				elseif blend == MixBlend.first or blend == MixBlend.replace then
+					bx = bone.scaleX
+					by = bone.scaleY
+					bone.scaleX = bx + (math_abs(x) * math_signum(bx) - bx) * alpha
+					bone.scaleY = by + (math_abs(y) * math_signum(by) - by) * alpha
+				elseif blend == MixBlend.add then
+					bx = bone.scaleX
+					by = bone.scaleY
+					bone.scaleX = bx + (math_abs(x) * math_signum(bx) - bone.data.scaleX) * alpha
+					bone.scaleY = by + (math_abs(y) * math_signum(by) - bone.data.scaleY) * alpha
+				end
+			else
+				if blend == MixBlend.setup then
+					bx = math_abs(bone.data.scaleX) * math_signum(x)
+					by = math_abs(bone.data.scaleY) * math_signum(y)
+					bone.scaleX = bx + (x - bx) * alpha
+					bone.scaleY = by + (y - by) * alpha
+				elseif blend == MixBlend.first or blend == MixBlend.replace then
+					bx = math_abs(bone.scaleX) * math_signum(x)
+					by = math_abs(bone.scaleY) * math_signum(y)
+					bone.scaleX = bx + (x - bx) * alpha
+					bone.scaleY = by + (y - by) * alpha
+				elseif blend == MixBlend.add then
+					bx = math_signum(x)
+					by = math_signum(y)
+					bone.scaleX = math_abs(bone.scaleX) * bx + (x - math_abs(bone.data.scaleX) * bx) * alpha
+					bone.scaleY = math_abs(bone.scaleY) * by + (y - math_abs(bone.data.scaleY) * by) * alpha
+				end
+			end
+		end
+	end
+
+	return self
+end
+
+Animation.ShearTimeline = {}
+Animation.ShearTimeline.ENTRIES = Animation.TranslateTimeline.ENTRIES
+function Animation.ShearTimeline.new (frameCount)
+	local ENTRIES = Animation.ShearTimeline.ENTRIES
+	local PREV_TIME = -3
+	local PREV_X = -2
+	local PREV_Y = -1
+	local X = 1
+	local Y = 2
+
+	local self = Animation.TranslateTimeline.new(frameCount)
+	self.type = TimelineType.shear
+
+	function self:getPropertyId ()
+		return TimelineType.shear * SHL_24 + self.boneIndex
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
+		local frames = self.frames
+
+		local bone = skeleton.bones[self.boneIndex]
+		if not bone.active then return end
+
+		if time < frames[0] then
+			if blend == MixBlend.setup then
+				bone.shearX = bone.data.shearX
+				bone.shearY = bone.data.shearY
+			elseif blend == MixBlend.first then
+				bone.shearX = bone.shearX + (bone.data.shearX - bone.shearX) * alpha
+				bone.shearY = bone.shearX + (bone.data.shearY - bone.shearY) * alpha
+			end
+			return
+		end
+
+		local x = 0
+		local y = 0
+		if time >= frames[zlen(frames) - ENTRIES] then -- // Time is after last frame.
+			x = frames[zlen(frames) + PREV_X]
+			y = frames[zlen(frames) + PREV_Y]
+		else
+			-- Interpolate between the previous frame and the current frame.
+			local frame = binarySearch(frames, time, ENTRIES)
+			x = frames[frame + PREV_X]
+			y = frames[frame + PREV_Y]
+			local frameTime = frames[frame]
+			local percent = self:getCurvePercent(math_floor(frame / ENTRIES) - 1,
+				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
+
+			x = x + (frames[frame + X] - x) * percent
+			y = y + (frames[frame + Y] - y) * percent
+		end
+		if blend == MixBlend.setup then
+			bone.shearX = bone.data.shearX + x * alpha
+			bone.shearY = bone.data.shearY + y * alpha
+		elseif blend == MixBlend.first or blend == MixBlend.replace then
+			bone.shearX = bone.shearX + (bone.data.shearX + x - bone.shearX) * alpha
+			bone.shearY = bone.shearY + (bone.data.shearY + y - bone.shearY) * alpha
+		elseif blend == MixBlend.add then
+			bone.shearX = bone.shearX + x * alpha
+			bone.shearY = bone.shearY + y * alpha
+		end
+	end
+
+	return self
+end
+
+Animation.ColorTimeline = {}
+Animation.ColorTimeline.ENTRIES = 5
+function Animation.ColorTimeline.new (frameCount)
+	local ENTRIES = Animation.ColorTimeline.ENTRIES
+	local PREV_TIME = -5
+	local PREV_R = -4
+	local PREV_G = -3
+	local PREV_B = -2
+	local PREV_A = -1
+	local R = 1
+	local G = 2
+	local B = 3
+	local A = 4
+
+	local self = Animation.CurveTimeline.new(frameCount)
+	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
+	self.slotIndex = -1
+	self.type = TimelineType.color
+
+	function self:getPropertyId ()
+		return TimelineType.color * SHL_24 + self.slotIndex
+	end
+
+	function self:setFrame (frameIndex, time, r, g, b, a)
+		frameIndex = frameIndex * ENTRIES
+		self.frames[frameIndex] = time
+		self.frames[frameIndex + R] = r
+		self.frames[frameIndex + G] = g
+		self.frames[frameIndex + B] = b
+		self.frames[frameIndex + A] = a
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
+		local frames = self.frames
+		local slot = skeleton.slots[self.slotIndex]
+		if not slot.bone.active then return end
+		if time < frames[0] then
+			if blend == MixBlend.setup then
+				slot.color:setFrom(slot.data.color)
+			elseif blend == MixBlend.first then
+				local color = slot.color
+				local setup = slot.data.color
+				color:add((setup.r - color.r) * alpha, (setup.g - color.g) * alpha, (setup.b - color.b) * alpha,
+						(setup.a - color.a) * alpha)
+			end
+			return
+		end
+
+		local r, g, b, a
+		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
+			local i = zlen(frames)
+			r = frames[i + PREV_R]
+			g = frames[i + PREV_G]
+			b = frames[i + PREV_B]
+			a = frames[i + PREV_A]
+		else
+			-- Interpolate between the last frame and the current frame.
+			local frame = binarySearch(frames, time, ENTRIES)
+			r = frames[frame + PREV_R]
+			g = frames[frame + PREV_G]
+			b = frames[frame + PREV_B]
+			a = frames[frame + PREV_A]
+			local frameTime = frames[frame]
+			local percent = self:getCurvePercent(math.floor(frame / ENTRIES) - 1,
+					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
+
+			r = r + (frames[frame + R] - r) * percent
+			g = g + (frames[frame + G] - g) * percent
+			b = b + (frames[frame + B] - b) * percent
+			a = a + (frames[frame + A] - a) * percent
+		end
+		if alpha == 1 then
+			slot.color:set(r, g, b, a)
+		else
+			local color = slot.color
+			if blend == MixBlend.setup then color:setFrom(slot.data.color) end
+			color:add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha)
+		end
+	end
+
+	return self
+end
+
+Animation.TwoColorTimeline = {}
+Animation.TwoColorTimeline.ENTRIES = 8
+function Animation.TwoColorTimeline.new (frameCount)
+	local ENTRIES = Animation.TwoColorTimeline.ENTRIES
+	local PREV_TIME = -8
+	local PREV_R = -7
+	local PREV_G = -6
+	local PREV_B = -5
+	local PREV_A = -4
+	local PREV_R2 = -3
+	local PREV_G2 = -2
+	local PREV_B2 = -1
+	local R = 1
+	local G = 2
+	local B = 3
+	local A = 4
+	local R2 = 5
+	local G2 = 6
+	local B2 = 7
+
+	local self = Animation.CurveTimeline.new(frameCount)
+	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
+	self.slotIndex = -1
+	self.type = TimelineType.twoColor
+
+	function self:getPropertyId ()
+		return TimelineType.twoColor * SHL_24 + self.slotIndex
+	end
+
+	function self:setFrame (frameIndex, time, r, g, b, a, r2, g2, b2)
+		frameIndex = frameIndex * ENTRIES
+		self.frames[frameIndex] = time
+		self.frames[frameIndex + R] = r
+		self.frames[frameIndex + G] = g
+		self.frames[frameIndex + B] = b
+		self.frames[frameIndex + A] = a
+		self.frames[frameIndex + R2] = r2
+		self.frames[frameIndex + G2] = g2
+		self.frames[frameIndex + B2] = b2
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
+		local frames = self.frames
+		local slot = skeleton.slots[self.slotIndex]
+		if not slot.bone.active then return end
+
+		if time < frames[0] then
+			if blend == MixBlend.setup then
+				slot.color:setFrom(slot.data.color)
+				slot.darkColor:setFrom(slot.data.darkColor)
+			elseif blend == MixBlend.first then
+				local light = slot.color
+				local dark = slot.darkColor
+				local setupLight = slot.data.color
+				local setupDark = slot.data.darkColor
+				light:add((setupLight.r - light.r) * alpha, (setupLight.g - light.g) * alpha, (setupLight.b - light.b) * alpha,
+					(setupLight.a - light.a) * alpha)
+				dark:add((setupDark.r - dark.r) * alpha, (setupDark.g - dark.g) * alpha, (setupDark.b - dark.b) * alpha, 0)
+			end
+			return
+		end
+
+		local r, g, b, a, r2, g2, b2
+		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
+			local i = zlen(frames)
+			r = frames[i + PREV_R]
+			g = frames[i + PREV_G]
+			b = frames[i + PREV_B]
+			a = frames[i + PREV_A]
+			r2 = frames[i + PREV_R2]
+			g2 = frames[i + PREV_G2]
+			b2 = frames[i + PREV_B2]
+		else
+			-- Interpolate between the last frame and the current frame.
+			local frame = binarySearch(frames, time, ENTRIES)
+			r = frames[frame + PREV_R]
+			g = frames[frame + PREV_G]
+			b = frames[frame + PREV_B]
+			a = frames[frame + PREV_A]
+			r2 = frames[frame + PREV_R2]
+			g2 = frames[frame + PREV_G2]
+			b2 = frames[frame + PREV_B2]
+			local frameTime = frames[frame]
+			local percent = self:getCurvePercent(math.floor(frame / ENTRIES) - 1,
+					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
+
+			r = r + (frames[frame + R] - r) * percent
+			g = g + (frames[frame + G] - g) * percent
+			b = b + (frames[frame + B] - b) * percent
+			a = a + (frames[frame + A] - a) * percent
+			r2 = r2 + (frames[frame + R2] - r2) * percent
+			g2 = g2 + (frames[frame + G2] - g2) * percent
+			b2 = b2 + (frames[frame + B2] - b2) * percent
+		end
+		if alpha == 1 then
+			slot.color:set(r, g, b, a)
+			slot.darkColor:set(r2, g2, b2, 1)
+		else
+			local light = slot.color
+			local dark = slot.darkColor
+			if blend == MixBlend.setup then
+				light:setFrom(slot.data.color)
+				dark:setFrom(slot.data.darkColor)
+			end
+			light:add((r - light.r) * alpha, (g - light.g) * alpha, (b - light.b) * alpha, (a - light.a) * alpha)
+			dark:add((r2 - dark.r) * alpha, (g2 - dark.g) * alpha, (b2 - dark.b) * alpha, 0)
+		end
+	end
+
+	return self
+end
+
+Animation.AttachmentTimeline = {}
+function Animation.AttachmentTimeline.new (frameCount)
+	local self = {
+		frames = utils.newNumberArrayZero(frameCount), -- time, ...
+		attachmentNames = {},
+		slotIndex = -1,
+		type = TimelineType.attachment
+	}
+
+	function self:getFrameCount ()
+		return zlen(self.frames)
+	end
+
+	function self:setFrame (frameIndex, time, attachmentName)
+		self.frames[frameIndex] = time
+		self.attachmentNames[frameIndex] = attachmentName
+	end
+
+	function self:getPropertyId ()
+		return TimelineType.attachment * SHL_24 + self.slotIndex
+	end
+
+	function self:setAttachment(skeleton, slot, attachmentName)
+		attachmentName = slot.data.attachmentName
+		if not attachmentName then
+			slot:setAttachment(nil)
+		else
+			slot:setAttachment(skeleton:getAttachmentByIndex(self.slotIndex, attachmentName))
+		end
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
+		local slot = skeleton.slots[self.slotIndex]
+		if not slot.bone.active then return end
+		local attachmentName
+		if direction == MixDirection.out then
+			if blend == MixBlend.setup then
+				self:setAttachment(skeleton, slot, slot.data.attachmentName)
+			end
+			return;
+		end
+
+		local frames = self.frames
+		if time < frames[0] then
+			if blend == MixBlend.setup or blend == MixBlend.first then
+				self:setAttachment(skeleton, slot, slot.data.attachmentName)
+			end
+			return
+		end
+
+		local frameIndex = 0
+		if time >= frames[zlen(frames) - 1] then
+			frameIndex = zlen(frames) - 1
+		else
+			frameIndex = binarySearch(frames, time, 1) - 1
+		end
+
+		attachmentName = self.attachmentNames[frameIndex]
+		if not attachmentName then
+			slot:setAttachment(nil)
+		else
+			slot:setAttachment(skeleton:getAttachmentByIndex(self.slotIndex, attachmentName))
+		end
+	end
+
+	return self
+end
+
+Animation.DeformTimeline = {}
+function Animation.DeformTimeline.new (frameCount)
+	local self = Animation.CurveTimeline.new(frameCount)
+	self.frames = utils.newNumberArrayZero(frameCount)
+	self.frameVertices = utils.newNumberArrayZero(frameCount)
+	self.slotIndex = -1
+	self.attachment = nil
+	self.type = TimelineType.deform
+
+	function self:getPropertyId ()
+		return TimelineType.deform * SHL_27 + self.attachment.id + self.slotIndex
+	end
+
+	function self:setFrame (frameIndex, time, vertices)
+		self.frames[frameIndex] = time
+		self.frameVertices[frameIndex] = vertices
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
+		local slot = skeleton.slots[self.slotIndex]
+		if not slot.bone.active then return end
+
+		local slotAttachment = slot.attachment
+		if not slotAttachment then return end
+		if not (slotAttachment.type == AttachmentType.mesh or slotAttachment.type == AttachmentType.linkedmesh or slotAttachment.type == AttachmentType.path or slotAttachment.type == AttachmentType.boundingbox) then return end
+		if slotAttachment.deformAttachment ~= self.attachment then return end
+
+		local frames = self.frames
+		local deformArray = slot.deform
+		if #(deformArray) == 0 then blend = MixBlend.setup end
+
+		local frameVertices = self.frameVertices
+		local vertexCount = #(frameVertices[0])
+
+		if time < frames[0] then
+			local vertexAttachment = slotAttachment;
+			if blend == MixBlend.setup then
+				slot.deform = {}
+				return;
+			elseif blend == MixBlend.first then
+				if (alpha == 1) then
+					slot.deform = {}
+					return;
+				end
+
+				local deform = utils.setArraySize(deformArray, vertexCount)
+				if (vertexAttachment.bones == nil) then
+					local setupVertices = vertexAttachment.vertices
+					local i = 1
+					while i <= vertexCount do
+						deform[i] = deform[i] + (setupVertices[i] - deform[i]) * alpha
+						i = i + 1
+					end
+				else
+					alpha = 1 - alpha
+					local i = 1
+					while i <= vertexCount do
+						deform[i] = deform[i] * alpha
+						i = i + 1
+					end
+				end
+			end
+			return
+		end
+
+		local deform = utils.setArraySize(deformArray, vertexCount)
+		if time >= frames[zlen(frames) - 1] then -- Time is after last frame.
+			local lastVertices = frameVertices[zlen(frames) - 1]
+			if alpha == 1 then
+				if blend == MixBlend.add then
+					local vertexAttachment = slotAttachment
+					if vertexAttachment.bones == nil then
+						-- Unweighted vertex positions, with alpha.
+						local setupVertices = vertexAttachment.vertices
+						local i = 1
+						while i <= vertexCount do
+							deform[i] = deform[i] + lastVertices[i] - setupVertices[i]
+							i = i + 1
+						end
+					else
+						-- Weighted deform offsets, with alpha.
+						local i = 1
+						while i <= vertexCount do
+							deform[i] = deform[i] + lastVertices[i]
+							i = i + 1
+						end
+					end
+				else
+					local i = 1
+					while i <= vertexCount do
+						deform[i] = lastVertices[i]
+						i = i + 1
+					end
+				end
+			else
+				if blend == MixBlend.setup then
+					local vertexAttachment = slotAttachment
+					if vertexAttachment.bones == nil then
+						-- Unweighted vertex positions, with alpha.
+						local setupVertices = vertexAttachment.vertices
+						local i = 1
+						while i <= vertexCount do
+							local setup = setupVertices[i]
+							deform[i] = setup + (lastVertices[i] - setup) * alpha
+							i = i + 1
+						end
+					else
+						-- Weighted deform offsets, with alpha.
+						local i = 1
+						while i <= vertexCount do
+							deform[i] = lastVertices[i] * alpha
+							i = i + 1
+						end
+					end
+				elseif blend == MixBlend.first or blend == MixBlend.replace then
+					local i = 1
+					while i <= vertexCount do
+						deform[i] = deform[i] + (lastVertices[i] - deform[i]) * alpha
+						i = i + 1
+					end
+					local vertexAttachment = slotAttachment
+					if vertexAttachment.bones == nil then
+						local setupVertices = vertexAttachment.vertices
+						local i = 1
+						while i <= vertexCount do
+							deform[i] = deform[i] + (lastVertices[i] - setupVertices[i]) * alpha
+							i = i + 1
+						end
+					else
+						-- Weighted deform offsets, with alpha.
+						local i = 1
+						while i <= vertexCount do
+							deform[i] = deform[i] + lastVertices[i] * alpha
+							i = i + 1
+						end
+					end
+				elseif blend == MixBlend.add then
+					local vertexAttachment = slotAttachment
+					if vertexAttachment.bones == nil then
+						local setupVertices = vertexAttachment.vertices
+						local i = 1
+						while i <= vertexCount do
+							deform[i] = deform[i] + (lastVertices[i] - setupVertices[i]) * alpha
+							i = i + 1
+						end
+					else
+						-- Weighted deform offsets, with alpha.
+						local i = 1
+						while i <= vertexCount do
+							deform[i] = deform[i] + lastVertices[i] * alpha
+							i = i + 1
+						end
+					end
+				end
+			end
+			return;
+		end
+
+		-- Interpolate between the previous frame and the current frame.
+		local frame = binarySearch(frames, time, 1)
+		local prevVertices = frameVertices[frame - 1]
+		local nextVertices = frameVertices[frame]
+		local frameTime = frames[frame]
+		local percent = self:getCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime))
+
+		if alpha == 1 then
+			if blend == MixBlend.add then
+				local vertexAttachment = slotAttachment
+				if vertexAttachment.bones == nil then
+					-- Unweighted vertex positions, with alpha.
+					local setupVertices = vertexAttachment.vertices
+					local i = 1
+					while i <= vertexCount do
+						local prev = prevVertices[i]
+						deform[i] = deform[i] + prev + (nextVertices[i] - prev) * precent - setupVertices[i]
+						i = i + 1
+					end
+				else
+					-- Weighted deform offsets, with alpha.
+					local i = 1
+					while i <= vertexCount do
+						local prev = prevVertices[i]
+						deform[i] = deform[i] + prev + (nextVertices[i] - prev) * percent
+						i = i + 1
+					end
+				end
+			else
+				local i = 1
+				while i <= vertexCount do
+					local prev = prevVertices[i]
+					deform[i] = prev + (nextVertices[i] - prev) * percent
+					i = i + 1
+				end
+			end
+		else
+			if blend == MixBlend.setup then
+				local vertexAttachment = slotAttachment
+				if vertexAttachment.bones == nil then
+					-- Unweighted vertex positions, with alpha.
+					local setupVertices = vertexAttachment.vertices
+					local i = 1
+					while i <= vertexCount do
+						local prev = prevVertices[i]
+						local setup = setupVertices[i]
+						deform[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha
+						i = i + 1
+					end
+				else
+					-- Weighted deform offsets, with alpha.
+					local i = 1
+					while i <= vertexCount do
+						local prev = prevVertices[i]
+						deform[i] = (prev + (nextVertices[i] - prev) * percent) * alpha
+						i = i + 1
+					end
+				end
+			elseif blend == MixBlend.first or blend == MixBlend.replace then
+				local i = 1
+				while i <= vertexCount do
+					local prev = prevVertices[i]
+					deform[i] = deform[i] + (prev + (nextVertices[i] - prev) * percent - deform[i]) * alpha
+					i = i + 1
+				end
+			elseif blend == MixBlend.add then
+				local vertexAttachment = slotAttachment
+				if vertexAttachment.bones == nil then
+					local setupVertices = vertexAttachment.vertices
+					local i = 1
+					while i <= vertexCount do
+						local prev = prevVertices[i]
+						deform[i] = deform[i] + (prev + (nextVertices[i] - prev) * percent - setupVertices[i]) * alpha
+						i = i + 1
+					end
+				else
+					-- Weighted deform offsets, with alpha.
+					local i = 1
+					while i <= vertexCount do
+						local prev = prevVertices[i]
+						deform[i] = deform[i] + (prev + (nextVertices[i] - prev) * percent) * alpha
+						i = i + 1
+					end
+				end
+			end
+		end
+	end
+
+	return self
+end
+
+Animation.EventTimeline = {}
+function Animation.EventTimeline.new (frameCount)
+	local self = {
+		frames = utils.newNumberArrayZero(frameCount),
+		events = {},
+		type = TimelineType.event
+	}
+
+	function self:getPropertyId ()
+		return TimelineType.event * SHL_24
+	end
+
+	function self:getFrameCount ()
+		return zlen(self.frames)
+	end
+
+	function self:setFrame (frameIndex, event)
+		self.frames[frameIndex] = event.time
+		self.events[frameIndex] = event
+	end
+
+	-- Fires events for frames > lastTime and <= time.
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
+		if not firedEvents then return end
+
+		local frames = self.frames
+		local frameCount = zlen(frames)
+
+		if lastTime > time then -- Fire events after last time for looped animations.
+			self:apply(skeleton, lastTime, 999999, firedEvents, alpha, blend, direction)
+			lastTime = -1
+		elseif lastTime >= frames[frameCount - 1] then -- Last time is after last frame.
+			return
+		end
+		if time < frames[0] then return end -- Time is before first frame.
+
+		local frame
+		if lastTime < frames[0] then
+			frame = 0
+		else
+			frame = binarySearch1(frames, lastTime)
+			local frame = frames[frame]
+			while frame > 0 do -- Fire multiple events with the same frame.
+				if frames[frame - 1] ~= frame then break end
+				frame = frame - 1
+			end
+		end
+		local events = self.events
+		while frame < frameCount and time >= frames[frame] do
+			table.insert(firedEvents, events[frame])
+			frame = frame + 1
+		end
+	end
+
+	return self
+end
+
+Animation.DrawOrderTimeline = {}
+function Animation.DrawOrderTimeline.new (frameCount)
+	local self = {
+		frames = utils.newNumberArrayZero(frameCount),
+		drawOrders = {},
+		type = TimelineType.drawOrder
+	}
+
+	function self:getPropertyId ()
+		return TimelineType.drawOrder * SHL_24
+	end
+
+	function self:getFrameCount ()
+		return zlen(self.frames)
+	end
+
+	function self:setFrame (frameIndex, time, drawOrder)
+		self.frames[frameIndex] = time
+		self.drawOrders[frameIndex] = drawOrder
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
+		local drawOrder = skeleton.drawOrder
+		local slots = skeleton.slots
+		if direction == MixDirection.out then
+			if blend == MixBlend.setup then
+				for i,slot in ipairs(slots) do
+					drawOrder[i] = slots[i]
+				end
+			end
+			return;
+		end
+
+		local frames = self.frames
+		if time < frames[0] then
+			if blend == MixBlend.setup or blend == MixBlend.first then
+				for i,slot in ipairs(slots) do
+					drawOrder[i] = slots[i]
+				end
+			end
+			return
+		end
+
+		local frame
+		if time >= frames[zlen(frames) - 1] then -- Time is after last frame.
+			frame = zlen(frames) - 1
+		else
+			frame = binarySearch1(frames, time) - 1
+		end
+
+		local drawOrderToSetupIndex = self.drawOrders[frame]
+		if not drawOrderToSetupIndex then
+			for i,slot in ipairs(slots) do
+				drawOrder[i] = slots[i]
+			end
+		else
+			for i,setupIndex in ipairs(drawOrderToSetupIndex) do
+				drawOrder[i] = skeleton.slots[setupIndex]
+			end
+		end
+	end
+
+	return self
+end
+
+Animation.IkConstraintTimeline = {}
+Animation.IkConstraintTimeline.ENTRIES = 6
+function Animation.IkConstraintTimeline.new (frameCount)
+	local ENTRIES = Animation.IkConstraintTimeline.ENTRIES
+	local PREV_TIME = -6
+	local PREV_MIX = -5
+	local PREV_SOFTNESS = -4
+	local PREV_BEND_DIRECTION = -3
+	local PREV_COMPRESS = -2
+	local PREV_STRETCH = -1
+	local MIX = 1
+	local SOFTNESS = 2
+	local BEND_DIRECTION = 3
+	local COMPRESS = 4
+	local STRETCH = 5
+
+	local self = Animation.CurveTimeline.new(frameCount)
+	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES) -- time, mix, softness, bendDirection, compress, stretch, ...
+	self.ikConstraintIndex = -1
+	self.type = TimelineType.ikConstraint
+
+	function self:getPropertyId ()
+		return TimelineType.ikConstraint * SHL_24 + self.ikConstraintIndex
+	end
+
+	function self:setFrame (frameIndex, time, mix, softness, bendDirection, compress, stretch)
+		frameIndex = frameIndex * ENTRIES
+		self.frames[frameIndex] = time
+		self.frames[frameIndex + MIX] = mix
+		self.frames[frameIndex + SOFTNESS] = softness
+		self.frames[frameIndex + BEND_DIRECTION] = bendDirection
+		if (compress) then
+			self.frames[frameIndex + COMPRESS] = 1
+		else
+			self.frames[frameIndex + COMPRESS] = 0
+		end
+		if (stretch) then
+			self.frames[frameIndex + STRETCH] = 1
+		else
+			self.frames[frameIndex + STRETCH] = 0
+		end
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
+		local frames = self.frames
+
+		local constraint = skeleton.ikConstraints[self.ikConstraintIndex]
+		if not constraint.active then return end
+		if time < frames[0] then
+			if blend == MixBlend.setup then
+				constraint.mix = constraint.data.mix
+				constraint.softness = constraint.data.softness
+				constraint.bendDirection = constraint.data.bendDirection
+				constraint.compress = constraint.data.compress
+				constraint.stretch = constraint.data.stretch
+			elseif blend == MixBlend.first then
+				constraint.mix = constraint.mix + (constraint.data.mix - constraint.mix) * alpha
+				constraint.softness = constraint.softness + (constraint.data.softness - constraint.softness) * alpha
+				constraint.bendDirection = constraint.data.bendDirection
+				constraint.compress = constraint.data.compress
+				constraint.stretch = constraint.data.stretch
+			end
+			return
+		end
+
+		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
+			if blend == MixBlend.setup then
+				constraint.mix = constraint.data.mix + (frames[zlen(frames) + PREV_MIX] - constraint.data.mix) * alpha
+				constraint.softness = constraint.data.softness + (frames[zlen(frames) + PREV_SOFTNESS] - constraint.data.softness) * alpha
+				if direction == MixDirection.out then
+					constraint.bendDirection = constraint.data.bendDirection
+					constraint.compress = constraint.data.compress
+					constraint.stretch = constraint.data.stretch
+				else
+					constraint.bendDirection = math_floor(frames[zlen(frames) + PREV_BEND_DIRECTION]);
+					if (math_floor(frames[zlen(frames) + PREV_COMPRESS]) == 1) then constraint.compress = true else constraint.compress = false end
+					if (math_floor(frames[zlen(frames) + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end
+				end
+			else
+				constraint.mix = constraint.mix + (frames[zlen(frames) + PREV_MIX] - constraint.mix) * alpha
+				constraint.softness = constraint.softness + (frames[zlen(frames) + PREV_SOFTNESS] - constraint.softness) * alpha
+				if direction == MixDirection._in then
+					constraint.bendDirection = math_floor(frames[zlen(frames) + PREV_BEND_DIRECTION])
+					if (math_floor(frames[zlen(frames) + PREV_COMPRESS]) == 1) then constraint.compress = true else constraint.compress = false end
+					if (math_floor(frames[zlen(frames) + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end
+				end
+			end
+			return
+		end
+
+		-- Interpolate between the previous frame and the current frame.
+		local frame = binarySearch(frames, time, ENTRIES)
+		local mix = frames[frame + PREV_MIX]
+		local softness = frames[frame + PREV_SOFTNESS]
+		local frameTime = frames[frame]
+		local percent = self:getCurvePercent(math.floor(frame / ENTRIES) - 1,
+				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
+
+		if blend == MixBlend.setup then
+			constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha
+			constraint.softness = constraint.data.softness + (softness + (frames[frame + SOFTNESS] - softness) * percent - constraint.data.softness) * alpha
+			if direction == MixDirection.out then
+				constraint.bendDirection = constraint.data.bendDirection
+				constraint.compress = constraint.data.compress
+				constraint.stretch = constraint.data.stretch
+			else
+				constraint.bendDirection = math_floor(frames[frame + PREV_BEND_DIRECTION])
+				if (math_floor(frames[frame + PREV_COMPRESS]) == 1) then constraint.compress = true else constraint.compress = false end
+				if (math_floor(frames[frame + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end
+			end
+		else
+			constraint.mix = constraint.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha
+			constraint.softness = constraint.softness + (softness + (frames[frame + SOFTNESS] - softness) * percent - constraint.softness) * alpha
+			if direction == MixDirection._in then
+				constraint.bendDirection = math_floor(frames[frame + PREV_BEND_DIRECTION])
+				if (math_floor(frames[frame + PREV_COMPRESS]) == 1) then constraint.compress = true else constraint.compress = false end
+				if (math_floor(frames[frame + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end
+			end
+		end
+	end
+
+	return self
+end
+
+Animation.TransformConstraintTimeline = {}
+Animation.TransformConstraintTimeline.ENTRIES = 5
+function Animation.TransformConstraintTimeline.new (frameCount)
+	local ENTRIES = Animation.TransformConstraintTimeline.ENTRIES
+	local PREV_TIME = -5
+	local PREV_ROTATE = -4
+	local PREV_TRANSLATE = -3
+	local PREV_SCALE = -2
+	local PREV_SHEAR = -1
+	local ROTATE = 1
+	local TRANSLATE = 2
+	local SCALE = 3
+	local SHEAR = 4
+
+	local self = Animation.CurveTimeline.new(frameCount)
+	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
+	self.transformConstraintIndex = -1
+	self.type = TimelineType.transformConstraint
+
+	function self:getPropertyId ()
+		return TimelineType.transformConstraint * SHL_24 + self.transformConstraintIndex
+	end
+
+	function self:setFrame (frameIndex, time, rotateMix, translateMix, scaleMix, shearMix)
+		frameIndex = frameIndex * ENTRIES
+		self.frames[frameIndex] = time
+		self.frames[frameIndex + ROTATE] = rotateMix
+		self.frames[frameIndex + TRANSLATE] = translateMix
+		self.frames[frameIndex + SCALE] = scaleMix
+		self.frames[frameIndex + SHEAR] = shearMix
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
+		local frames = self.frames
+
+		local constraint = skeleton.transformConstraints[self.transformConstraintIndex]
+		if not constraint.active then return end
+
+		if time < frames[0] then
+			local data = constraint.data
+			if blend == MixBlend.setup then
+				constraint.rotateMix = data.rotateMix
+				constraint.translateMix = data.translateMix
+				constraint.scaleMix = data.scaleMix
+				constraint.shearMix = data.shearMix
+			elseif blend == MixBlend.first then
+				constraint.rotateMix = constraint.rotateMix + (data.rotateMix - constraint.rotateMix) * alpha
+				constraint.translateMix = constraint.translateMix + (data.translateMix - constraint.translateMix) * alpha
+				constraint.scaleMix = constraint.scaleMix + (data.scaleMix - constraint.scaleMix) * alpha
+				constraint.shearMix = constraint.shearMix + (data.shearMix - constraint.shearMix) * alpha
+			end
+			return
+		end
+
+		local rotate = 0
+		local translate = 0
+		local scale = 0
+		local shear = 0
+		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
+			local i = zlen(frames)
+			rotate = frames[i + PREV_ROTATE]
+			translate = frames[i + PREV_TRANSLATE]
+			scale = frames[i + PREV_SCALE]
+			shear = frames[i + PREV_SHEAR]
+		else
+			-- Interpolate between the previous frame and the current frame.
+			local frame = binarySearch(frames, time, ENTRIES)
+			rotate = frames[frame + PREV_ROTATE]
+			translate = frames[frame + PREV_TRANSLATE]
+			scale = frames[frame + PREV_SCALE]
+			shear = frames[frame + PREV_SHEAR]
+			local frameTime = frames[frame]
+			local percent = self:getCurvePercent(math_floor(frame / ENTRIES) - 1,
+				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+
+			rotate = rotate + (frames[frame + ROTATE] - rotate) * percent
+			translate = translate + (frames[frame + TRANSLATE] - translate) * percent
+			scale = scale + (frames[frame + SCALE] - scale) * percent
+			shear = shear + (frames[frame + SHEAR] - shear) * percent
+		end
+		if blend == MixBlend.setup then
+			local data = constraint.data
+			constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha
+			constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha
+			constraint.scaleMix = data.scaleMix + (scale - data.scaleMix) * alpha
+			constraint.shearMix = data.shearMix + (shear - data.shearMix) * alpha
+		else
+			constraint.rotateMix = constraint.rotateMix + (rotate - constraint.rotateMix) * alpha
+			constraint.translateMix = constraint.translateMix + (translate - constraint.translateMix) * alpha
+			constraint.scaleMix = constraint.scaleMix + (scale - constraint.scaleMix) * alpha
+			constraint.shearMix = constraint.shearMix + (shear - constraint.shearMix) * alpha
+		end
+	end
+
+	return self
+end
+
+Animation.PathConstraintPositionTimeline = {}
+Animation.PathConstraintPositionTimeline.ENTRIES = 2
+function Animation.PathConstraintPositionTimeline.new (frameCount)
+	local ENTRIES = Animation.PathConstraintPositionTimeline.ENTRIES
+	local PREV_TIME = -2
+	local PREV_VALUE = -1
+	local VALUE = 1
+
+	local self = Animation.CurveTimeline.new(frameCount)
+	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
+	self.pathConstraintIndex = -1
+	self.type = TimelineType.pathConstraintPosition
+
+	function self:getPropertyId ()
+		return TimelineType.pathConstraintPosition * SHL_24 + self.pathConstraintIndex
+	end
+
+	function self:setFrame (frameIndex, time, value)
+		frameIndex = frameIndex * ENTRIES
+		self.frames[frameIndex] = time
+		self.frames[frameIndex + VALUE] = value
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
+		local frames = self.frames
+
+		local constraint = skeleton.pathConstraints[self.pathConstraintIndex]
+		if not constraint.active then return end
+
+		if (time < frames[0]) then
+			if blend == MixBlend.setup then
+				constraint.position = constraint.data.position
+			elseif blend == MixBlend.first then
+				constraint.position = constraint.position + (constraint.data.position - constraint.position) * alpha
+			end
+			return
+		end
+
+		local position = 0
+		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
+			position = frames[zlen(frames) + PREV_VALUE]
+		else
+			-- Interpolate between the previous frame and the current frame.
+			local frame = binarySearch(frames, time, ENTRIES)
+			position = frames[frame + PREV_VALUE]
+			local frameTime = frames[frame]
+			local percent = self:getCurvePercent(math_floor(frame / ENTRIES) - 1,
+				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
+
+			position = position + (frames[frame + VALUE] - position) * percent
+		end
+		if blend == MixBlend.setup then
+			constraint.position = constraint.data.position + (position - constraint.data.position) * alpha
+		else
+			constraint.position = constraint.position + (position - constraint.position) * alpha
+		end
+	end
+
+	return self
+end
+
+Animation.PathConstraintSpacingTimeline = {}
+Animation.PathConstraintSpacingTimeline.ENTRIES = 2
+function Animation.PathConstraintSpacingTimeline.new (frameCount)
+	local ENTRIES = Animation.PathConstraintSpacingTimeline.ENTRIES
+	local PREV_TIME = -2
+	local PREV_VALUE = -1
+	local VALUE = 1
+
+	local self = Animation.CurveTimeline.new(frameCount)
+	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
+	self.pathConstraintIndex = -1
+	self.type = TimelineType.pathConstraintSpacing
+
+	function self:getPropertyId ()
+		return TimelineType.pathConstraintSpacing * SHL_24 + self.pathConstraintIndex
+	end
+
+	function self:setFrame (frameIndex, time, value)
+		frameIndex = frameIndex * ENTRIES
+		self.frames[frameIndex] = time
+		self.frames[frameIndex + VALUE] = value
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
+		local frames = self.frames
+
+		local constraint = skeleton.pathConstraints[self.pathConstraintIndex]
+		if not constraint.active then return end
+
+		if (time < frames[0]) then
+			if blend == MixBlend.setup then
+				constraint.spacing = constraint.data.spacing
+			elseif blend == MixBlend.first then
+				constraint.spacing = constraint.spacing + (constraint.data.spacing - constraint.spacing) * alpha
+			end
+			return
+		end
+
+		local spacing = 0
+		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
+			spacing = frames[zlen(frames) + PREV_VALUE]
+		else
+			-- Interpolate between the previous frame and the current frame.
+			local frame = binarySearch(frames, time, ENTRIES)
+			spacing = frames[frame + PREV_VALUE]
+			local frameTime = frames[frame]
+			local percent = self:getCurvePercent(math_floor(frame / ENTRIES) - 1,
+				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
+
+			spacing = spacing + (frames[frame + VALUE] - spacing) * percent
+		end
+
+		if blend == MixBlend.setup then
+			constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha
+		else
+			constraint.spacing = constraint.spacing + (spacing - constraint.spacing) * alpha
+		end
+	end
+
+	return self
+end
+
+Animation.PathConstraintMixTimeline = {}
+Animation.PathConstraintMixTimeline.ENTRIES = 3
+function Animation.PathConstraintMixTimeline.new (frameCount)
+	local ENTRIES = Animation.PathConstraintMixTimeline.ENTRIES
+	local PREV_TIME = -3
+	local PREV_ROTATE = -2
+	local PREV_TRANSLATE = -1
+	local ROTATE = 1
+	local TRANSLATE = 2
+
+	local self = Animation.CurveTimeline.new(frameCount)
+	self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
+	self.pathConstraintIndex = -1
+	self.type = TimelineType.pathConstraintMix
+
+	function self:getPropertyId ()
+		return TimelineType.pathConstraintMix * SHL_24 + self.pathConstraintIndex
+	end
+
+	function self:setFrame (frameIndex, time, rotateMix, translateMix)
+		frameIndex = frameIndex * ENTRIES
+		self.frames[frameIndex] = time
+		self.frames[frameIndex + ROTATE] = rotateMix
+		self.frames[frameIndex + TRANSLATE] = translateMix
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction)
+		local frames = self.frames
+
+		local constraint = skeleton.pathConstraints[self.pathConstraintIndex]
+		if not constraint.active then return end
+
+		if (time < frames[0]) then
+			if blend == MixBlend.setup then
+				constraint.rotateMix = constraint.data.rotateMix
+				constraint.translateMix = constraint.data.translateMix
+			elseif blend == MixBlend.first then
+				constraint.rotateMix = constraint.rotateMix + (constraint.data.rotateMix - constraint.rotateMix) * alpha
+				constraint.translateMix = constraint.translateMix + (constraint.data.translateMix - constraint.translateMix) * alpha
+			end
+			return
+		end
+
+		local rotate = 0
+		local translate = 0
+		if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
+			rotate = frames[zlen(frames) + PREV_ROTATE]
+			translate = frames[zlen(frames) + PREV_TRANSLATE]
+		else
+			-- Interpolate between the previous frame and the current frame.
+			local frame = binarySearch(frames, time, ENTRIES)
+			rotate = frames[frame + PREV_ROTATE]
+			translate = frames[frame + PREV_TRANSLATE]
+			local frameTime = frames[frame]
+			local percent = self:getCurvePercent(math_floor(frame / ENTRIES) - 1,
+				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
+
+			rotate = rotate + (frames[frame + ROTATE] - rotate) * percent
+			translate = translate + (frames[frame + TRANSLATE] - translate) * percent
+		end
+
+		if blend == MixBlend.setup then
+			constraint.rotateMix = constraint.data.rotateMix + (rotate - constraint.data.rotateMix) * alpha
+			constraint.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha
+		else
+			constraint.rotateMix = constraint.rotateMix + (rotate - constraint.rotateMix) * alpha
+			constraint.translateMix = constraint.translateMix + (translate - constraint.translateMix) * alpha
+		end
+	end
+
+	return self
+end
+
+return Animation

+ 1007 - 1007
spine-lua/AnimationState.lua → spine-lua/spine-lua/AnimationState.lua

@@ -1,1007 +1,1007 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local setmetatable = setmetatable
-local table_insert = table.insert
-local utils = require "spine-lua.utils"
-local Animation = require "spine-lua.Animation"
-local MixBlend = Animation.MixBlend
-local MixDirection = Animation.MixDirection
-local AnimationStateData = require "spine-lua.AnimationStateData"
-local math_min = math.min
-local math_max = math.max
-local math_abs = math.abs
-local math_signum = utils.signum
-local math_floor = math.floor
-local math_ceil = math.ceil
-local math_mod = utils.mod
-local testBit = utils.testBit
-local setBit = utils.setBit
-local clearBit = utils.clearBit
-
-local function zlen(array)
-	return #array + 1
-end
-
-local EMPTY_ANIMATION = Animation.new("<empty>", {}, 0)
-local SUBSEQUENT = 0
-local FIRST = 1
-local HOLD_SUBSEQUENT = 2
-local HOLD_FIRST = 3
-local HOLD_MIX = 4
-
-local SETUP = 1
-local CURRENT = 2
-
-local EventType = {
-	start = 0,
-	interrupt = 1,
-	_end = 2,
-	dispose = 3,
-	complete = 4,
-	event = 5
-}
-
-local EventQueue = {}
-EventQueue.__index = EventQueue
-
-function EventQueue.new (animationState)
-	local self = {
-		objects = {},
-		animationState = animationState,
-		drainDisabled = false
-	}
-	setmetatable(self, EventQueue)
-	return self
-end
-
-function EventQueue:start (entry)
-	local objects = self.objects
-	table_insert(objects, EventType.start)
-	table_insert(objects, entry)
-	self.animationState.animationsChanged = true
-end
-
-function EventQueue:interrupt (entry)
-	local objects = self.objects
-	table_insert(objects, EventType.interrupt)
-	table_insert(objects, entry)
-end
-
-function EventQueue:_end (entry)
-	local objects = self.objects
-	table_insert(objects, EventType._end)
-	table_insert(objects, entry)
-	self.animationState.animationsChanged = true
-end
-
-function EventQueue:dispose (entry)
-	local objects = self.objects
-	table_insert(objects, EventType.dispose)
-	table_insert(objects, entry)
-end
-
-function EventQueue:complete (entry)
-	local objects = self.objects
-	table_insert(objects, EventType.complete)
-	table_insert(objects, entry)
-end
-
-function EventQueue:event (entry, event)
-	local objects = self.objects
-	table_insert(objects, EventType.event)
-	table_insert(objects, entry)
-	table_insert(objects, event)
-end
-
-function EventQueue:drain ()
-	if self.drainDisabled then return end -- Not reentrant.
-	self.drainDisabled = true
-
-	local objects = self.objects
-	local as = self.animationState
-	local i = 1
-	local n = #objects
-	while i <= n do
-		local _type = objects[i]
-		local entry = objects[i + 1]
-		if _type == EventType.start then
-			if entry.onStart then entry.onStart(entry) end
-			if as.onStart then as.onStart(entry) end
-		elseif _type == EventType.interrupt then
-			if entry.onInterrupt then entry.onInterrupt(entry) end
-			if as.onInterrupt then as.onInterrupt(entry) end
-		elseif _type == EventType._end then
-			if entry.onEnd then entry.onEnd(entry) end
-			if as.onEnd then as.onEnd(entry) end
-			-- fall through in ref impl
-			if entry.onDispose then entry.onDispose(entry) end
-			if as.onDispose then as.onDispose(entry) end
-		elseif _type == EventType._dispose then
-			if entry.onDispose then entry.onDispose(entry) end
-			if as.onDispose then as.onDispose(entry) end
-		elseif _type == EventType.complete then
-			if entry.onComplete then entry.onComplete(entry) end
-			if as.onComplete then as.onComplete(entry) end
-		elseif _type == EventType.event then
-			local event = objects[i + 2]
-			if entry.onEvent then entry.onEvent(entry, event) end
-			if as.onEvent then as.onEvent(entry, event) end
-			i = i + 1
-		end
-		i = i + 2
-	end
-	self:clear()
-
-	self.drainDisabled = false;
-end
-
-function EventQueue:clear ()
-	self.objects = {}
-end
-
-local TrackEntry = {}
-TrackEntry.__index = TrackEntry
-
-function TrackEntry.new ()
-	local self = {
-		animation = nil,
-		next = nil, mixingFrom = nil, mixingTo = nil,
-		onStart = nil, onInterrupt = nil, onEnd = nil, onDispose = nil, onComplete = nil, onEvent = nil,
-		trackIndex = 0,
-		loop = false, holdPrevious = false,
-		eventThreshold = 0, attachmentThreshold = 0, drawOrderThreshold = 0,
-		animationStart = 0, animationEnd = 0, animationLast = 0, nextAnimationLast = 0,
-		delay = 0, trackTime = 0, trackLast = 0, nextTrackLast = 0, trackEnd = 0, timeScale = 0,
-		alpha = 0, mixTime = 0, mixDuration = 0, interruptAlpha = 0, totalAlpha = 0,
-		mixBlend = MixBlend.replace,
-		timelineMode = {},
-		timelineHoldMix = {},
-		timelinesRotation = {}
-	}
-	setmetatable(self, TrackEntry)
-	return self
-end
-
-function TrackEntry:getAnimationTime ()
-	if self.loop then
-		local duration = self.animationEnd - self.animationStart
-		if duration == 0 then return self.animationStart end
-		return (self.trackTime % duration) + self.animationStart
-	end
-	return math_min(self.trackTime + self.animationStart, self.animationEnd)
-end
-
-function TrackEntry:resetRotationDirections ()
-	self.timelinesRotation = {}
-end
-
-local AnimationState = {}
-AnimationState.__index = AnimationState
-
-function AnimationState.new (data)
-	if not data then error("data cannot be nil", 2) end
-
-	local self = {
-		data = data,
-		tracks = {},
-		events = {},
-		onStart = nil, onInterrupt = nil, onEnd = nil, onDispose = nil, onComplete = nil, onEvent = nil,
-		queue = nil,
-		propertyIDs = {},
-		animationsChanged = false,
-		timeScale = 1,
-		mixingTo = {},
-		unkeyedState = 0
-	}
-	self.queue = EventQueue.new(self)
-	setmetatable(self, AnimationState)
-	return self
-end
-
-AnimationState.TrackEntry = TrackEntry
-
-function AnimationState:update (delta)
-	delta = delta * self.timeScale
-	local tracks = self.tracks
-	local queue = self.queue
-	local numTracks = getNumTracks(tracks)
-	local i = 0
-	while i <= numTracks do
-		current = tracks[i]
-		if current then
-			current.animationLast = current.nextAnimationLast
-			current.trackLast = current.nextTrackLast
-
-			local currentDelta = delta * current.timeScale
-
-			local skip = false
-			if current.delay > 0 then
-				current.delay = current.delay - currentDelta
-				if current.delay <= 0 then
-					skip = true
-					currentDelta = -current.delay
-					current.delay = 0
-				end
-			end
-
-			if not skip then
-				local _next = current.next
-				if _next then
-					-- When the next entry's delay is passed, change to the next entry, preserving leftover time.
-					local nextTime = current.trackLast - _next.delay
-					if nextTime >= 0 then
-						_next.delay = 0
-						if current.timeScale == 0 then
-							_next.trackTime = _next.trackTime + 0
-						else
-							_next.trackTime = _next.trackTime + (nextTime / current.timeScale + delta) * _next.timeScale
-						end
-						current.trackTime = current.trackTime + currentDelta
-						self:setCurrent(i, _next, true)
-						while _next.mixingFrom do
-							_next.mixTime = _next.mixTime + delta
-							_next = _next.mixingFrom
-						end
-						skip = true
-					end
-				else
-					-- Clear the track when there is no next entry, the track end time is reached, and there is no mixingFrom.
-					if current.trackLast >= current.trackEnd and current.mixingFrom == nil then
-						tracks[i] = nil
-						queue:_end(current)
-						self:disposeNext(current)
-						skip = true
-					end
-				end
-
-				if not skip then
-					if current.mixingFrom and self:updateMixingFrom(current, delta) then
-						-- End mixing from entries once all have completed.
-						local from = current.mixingFrom
-						current.mixingFrom = nil
-						if from then from.mixingTo = nil end
-						while from do
-							queue:_end(from)
-							from = from.mixingFrom
-						end
-					end
-
-					current.trackTime = current.trackTime + currentDelta
-				end
-			end
-		end
-		i = i + 1
-	end
-
-	queue:drain()
-end
-
-function AnimationState:updateMixingFrom (to, delta)
-	local from = to.mixingFrom
-	if from == nil then return true end
-
-	local finished = self:updateMixingFrom(from, delta)
-
-	from.animationLast = from.nextAnimationLast
-	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
-		-- 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
-			to.mixingFrom = from.mixingFrom
-			if from.mixingFrom then from.mixingFrom.mixingTo = to end
-			to.interruptAlpha = from.interruptAlpha
-			self.queue:_end(from)
-		end
-		return finished
-	end
-
-	from.trackTime = from.trackTime + delta * from.timeScale
-	to.mixTime = to.mixTime + delta
-	return false;
-end
-
-function AnimationState:apply (skeleton)
-	if skeleton == nil then error("skeleton cannot be null.", 2) end
-	if self.animationsChanged then self:_animationsChanged() end
-
-	local tracks = self.tracks
-	local queue = self.queue
-	local applied = false
-
-	local numTracks = getNumTracks(tracks)
-	local i = 0
-	while i <= numTracks do
-		current = tracks[i]
-		if current then
-			if not (current == nil or current.delay > 0) then
-				applied = true
-
-				local blend = current.mixBlend
-				if i == 0 then blend = MixBlend.first end
-
-				-- Apply mixing from entries first.
-				local mix = current.alpha
-				if current.mixingFrom then
-					mix = mix * self:applyMixingFrom(current, skeleton, blend)
-				elseif current.trackTime >= current.trackEnd and current.next == nil then
-					mix = 0
-				end
-
-				-- Apply current entry.
-				local animationLast = current.animationLast
-				local animationTime = current:getAnimationTime()
-				local timelines = current.animation.timelines
-				if (i == 0 and mix == 1) or blend == MixBlend.add then
-					for i,timeline in ipairs(timelines) do
-						if timeline.type == Animation.TimelineType.attachment then
-							self:applyAttachmentTimeline(timeline, skeleton, animationTime, blend, true)
-						else
-							timeline:apply(skeleton, animationLast, animationTime, self.events, mix, blend, MixDirection._in)
-						end
-					end
-				else
-					local timelineMode = current.timelineMode
-					local firstFrame = #current.timelinesRotation == 0
-					local timelinesRotation = current.timelinesRotation
-
-					for ii,timeline in ipairs(timelines) do
-						local timelineBlend = MixBlend.setup
-						if timelineMode[ii] == SUBSEQUENT then timelineBlend = blend end
-
-						if timeline.type == Animation.TimelineType.rotate then
-							self:applyRotateTimeline(timeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, ii * 2,
-									firstFrame)
-						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)
-						end
-					end
-				end
-				self:queueEvents(current, animationTime)
-				self.events = {};
-				current.nextAnimationLast = animationTime
-				current.nextTrackLast = current.trackTime
-			end
-		end
-		i = i + 1
-	end
-
-	-- Set slots attachments to the setup pose, if needed. This occurs if an animation that is mixing out sets attachments so
-	-- subsequent timelines see any deform, but the subsequent timelines don't set an attachment (eg they are also mixing out or
-	-- the time is before the first key).
-	local setupState = self.unkeyedState + SETUP
-	local slots = skeleton.slots;
-	for _, slot in ipairs(slots) do
-		if slot.attachmentState == setupState then
-			local attachmentName = slot.data.attachmentName
-			if attachmentName == nil then
-				slot.attachment = nil
-			else
-				slot.attachment = skeleton:getAttachmentByIndex(slot.data.index, attachmentName)
-			end
-		end
-	end
-	self.unkeyedState = self.unkeyedState + 2; -- Increasing after each use avoids the need to reset attachmentState for every slot.
-
-
-	queue:drain()
-	return applied
-end
-
-function AnimationState:applyMixingFrom (to, skeleton, blend)
-	local from = to.mixingFrom
-	if from.mixingFrom then self:applyMixingFrom(from, skeleton, blend) end
-
-	local mix = 0
-	if to.mixDuration == 0 then -- Single frame mix to undo mixingFrom changes.
-		mix = 1
-		if blend == MixBlend.first then blend = MixBlend.setup end
-	else
-		mix = to.mixTime / to.mixDuration
-		if mix > 1 then mix = 1 end
-		if blend ~= MixBlend.first then blend = from.mixBlend end
-	end
-
-	local events = nil
-	if mix < from.eventThreshold then events = self.events end
-	local attachments = mix < from.attachmentThreshold
-	local drawOrder = mix < from.drawOrderThreshold
-	local animationLast = from.animationLast
-	local animationTime = from:getAnimationTime()
-	local timelines = from.animation.timelines
-	local alphaHold = from.alpha * to.interruptAlpha
-	local alphaMix = alphaHold * (1 - mix)
-
-	if blend == MixBlend.add then
-		for i,timeline in ipairs(timelines) do
-			timeline:apply(skeleton, animationLast, animationTime, events, alphaMix, blend, MixDirection.out)
-		end
-	else
-		local timelineMode = from.timelineMode
-		local timelineHoldMix = from.timelineHoldMix
-
-		local firstFrame = #from.timelinesRotation == 0
-		local timelinesRotation = from.timelinesRotation
-
-		from.totalAlpha = 0;
-
-		for i,timeline in ipairs(timelines) do
-			local skipSubsequent = false;
-			local direction = MixDirection.out;
-			local timelineBlend = MixBlend.setup
-			local alpha = 0
-			if timelineMode[i] == SUBSEQUENT then
-				if not drawOrder and timeline.type == Animation.TimelineType.drawOrder then skipSubsequent = true end
-				timelineBlend = blend
-				alpha = alphaMix
-			elseif timelineMode[i] == FIRST then
-				timelineBlend = MixBlend.setup
-				alpha = alphaMix
-			elseif timelineMode[i] == HOLD_SUBSEQUENT then
-				timelineBlend = blend
-				alpha = alphaHold
-			elseif timelineMode[i] == HOLD_FIRST then
-				timelineBlend = MixBlend.setup
-				alpha = alphaHold
-			else
-				timelineBlend = MixBlend.setup
-				local holdMix = timelineHoldMix[i]
-				alpha = alphaHold * math_max(0, 1 - holdMix.mixtime / holdMix.mixDuration)
-			end
-
-			if not skipSubsequent then
-				from.totalAlpha = from.totalAlpha + alpha
-				if timeline.type == Animation.TimelineType.rotate then
-					self:applyRotateTimeline(timeline, skeleton, animationTime, alpha, timelineBlend, timelinesRotation, i * 2, firstFrame)
-				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
-					end
-					timeline:apply(skeleton, animationLast, animationTime, self.events, alpha, timelineBlend, direction)
-				end
-			end
-		end
-	end
-
-	if (to.mixDuration > 0) then
-		self:queueEvents(from, animationTime)
-	end
-	self.events = {};
-	from.nextAnimationLast = animationTime
-	from.nextTrackLast = from.trackTime
-
-	return mix
-end
-
-function AnimationState:applyAttachmentTimeline(timeline, skeleton, time, blend, attachments)
-	local slot = skeleton.slots[timeline.slotIndex];
-	if slot.bone.active == false then return end
-
-	local frames = timeline.frames
-	if time < frames[0] then -- Time is before first frame.
-		if blend == MixBlend.setup or blend == MixBlend.first then
-			self:setAttachment(skeleton, slot, slot.data.attachmentName, attachments);
-		end
-	else
-		local frameIndex = 0
-		if (time >= frames[zlen(frames) - 1]) then -- Time is after last frame.
-			frameIndex = zlen(frames) - 1;
-		else
-			frameIndex = Animation.binarySearch(frames, time, 1) - 1;
-		end
-		self:setAttachment(skeleton, slot, timeline.attachmentNames[frameIndex], attachments)
-	end
-
-	-- If an attachment wasn't set (ie before the first frame or attachments is false), set the setup attachment later.
-	if slot.attachmentState <= self.unkeyedState then slot.attachmentState = self.unkeyedState + SETUP end
-end
-
-function AnimationState:setAttachment(skeleton, slot, attachmentName, attachments)
-	if (attachmentName == nil) then
-		slot.attachment = nil
-	else
-		slot.attachment = skeleton:getAttachmentByIndex(slot.data.index, attachmentName)
-	end
-	if attachments then slot.attachmentState = self.unkeyedState + CURRENT end
-end
-
-function AnimationState:applyRotateTimeline (timeline, skeleton, time, alpha, blend, timelinesRotation, i, firstFrame)
-	if firstFrame then
-		timelinesRotation[i] = 0
-		timelinesRotation[i+1] = 0
-	end
-
-	if alpha == 1 then
-		timeline:apply(skeleton, 0, time, nil, 1, blend, MixDirection._in)
-		return
-	end
-
-	local rotateTimeline = timeline
-	local frames = rotateTimeline.frames
-	local bone = skeleton.bones[rotateTimeline.boneIndex]
-	if not bone.active then return end
-	local r1 = 0
-	local r2 = 0
-	if time < frames[0] then
-		if blend == MixBlend.setup then
-			bone.rotation = bone.data.rotation
-			return
-		elseif blend == MixBlend.first then
-			r1 = bone.rotation
-			r2 = bone.data.rotation
-		else
-			return
-		end
-	else
-		if blend == MixBlend.setup then
-			r1 = bone.data.rotation
-		else
-			r1 = bone.rotation
-		end
-		if time >= frames[zlen(frames) - Animation.RotateTimeline.ENTRIES] then -- Time is after last frame.
-			r2 = bone.data.rotation + frames[zlen(frames) + Animation.RotateTimeline.PREV_ROTATION]
-		else
-			-- Interpolate between the previous frame and the current frame.
-			local frame = Animation.binarySearch(frames, time, Animation.RotateTimeline.ENTRIES)
-			local prevRotation = frames[frame + Animation.RotateTimeline.PREV_ROTATION]
-			local frameTime = frames[frame]
-			local percent = rotateTimeline:getCurvePercent(math_floor(frame / 2) - 1,
-				1 - (time - frameTime) / (frames[frame + Animation.RotateTimeline.PREV_TIME] - frameTime))
-
-			r2 = frames[frame + Animation.RotateTimeline.ROTATION] - prevRotation
-			r2 = r2 - (16384 - math_floor(16384.499999999996 - r2 / 360)) * 360
-			r2 = prevRotation + r2 * percent + bone.data.rotation
-			r2 = r2 - (16384 - math_floor(16384.499999999996 - r2 / 360)) * 360
-		end
-	end
-
-	-- Mix between rotations using the direction of the shortest route on the first frame while detecting crosses.
-	local total = 0
-	local diff = r2 - r1
-	diff = diff - (16384 - math_floor(16384.499999999996 - diff / 360)) * 360
-	if diff == 0 then
-		total = timelinesRotation[i]
-	else
-		local lastTotal = 0
-		local lastDiff = 0
-		if firstFrame then
-			lastTotal = 0
-			lastDiff = diff
-		else
-			lastTotal = timelinesRotation[i] -- Angle and direction of mix, including loops.
-			lastDiff = timelinesRotation[i + 1] -- Difference between bones.
-		end
-		local current = diff > 0
-		local dir = lastTotal >= 0
-		-- Detect cross at 0 (not 180).
-		if math_signum(lastDiff) ~= math_signum(diff) and math_abs(lastDiff) <= 90 then
-			-- A cross after a 360 rotation is a loop.
-			if math_abs(lastTotal) > 180 then lastTotal = lastTotal + 360 * math_signum(lastTotal) end
-			dir = current
-		end
-		total = diff + lastTotal - math_mod(lastTotal, 360) -- FIXME used to be %360, store loops as part of lastTotal.
-		if dir ~= current then total = total + 360 * math_signum(lastTotal) end
-		timelinesRotation[i] = total
-	end
-	timelinesRotation[i + 1] = diff
-	r1 = r1 + total * alpha
-	bone.rotation = r1 - (16384 - math_floor(16384.499999999996 - r1 / 360)) * 360
-end
-
-function AnimationState:queueEvents (entry, animationTime)
-	local animationStart = entry.animationStart
-	local animationEnd = entry.animationEnd
-	local duration = animationEnd - animationStart
-	local trackLastWrapped = entry.trackLast % duration
-
-	-- Queue events before complete.
-	local events = self.events
-	local queue = self.queue
-	local i = 1
-	local n = #events
-	while i <= n do
-		local event = events[i]
-		if event.time < trackLastWrapped then break end
-		if not (event.time > animationEnd) then -- Discard events outside animation start/end.
-			queue:event(entry, event)
-		end
-		i = i + 1
-	end
-
-	-- Queue complete if completed a loop iteration or the animation.
-	local queueComplete = false
-	if entry.loop then
-		queueComplete = duration == 0 or (trackLastWrapped > entry.trackTime % duration)
-	else
-		queueComplete = (animationTime >= animationEnd and entry.animationLast < animationEnd)
-	end
-	if queueComplete then
-		queue:complete(entry)
-	end
-
-	-- Queue events after complete.
-	while i <= n do
-		local event = events[i]
-		if not (event.time < animationStart) then --// Discard events outside animation start/end.
-			queue:event(entry, event)
-		end
-		i = i + 1
-	end
-end
-
-function AnimationState:clearTracks ()
-	local queue = self.queue
-	local tracks = self.tracks
-	local oldDrainDisabled = queue.drainDisabled
-	queue.drainDisabled = true;
-	local numTracks = getNumTracks(tracks)
-	local i = 0
-	while i <= numTracks do
-		self:clearTrack(i)
-	end
-	tracks = {}
-	queue.drainDisabled = oldDrainDisabled
-	queue:drain();
-end
-
-function AnimationState:clearTrack (trackIndex)
-	local tracks = self.tracks
-	local queue = self.queue
-	local current = tracks[trackIndex]
-	if current == nil then return end
-
-	queue:_end(current)
-
-	self:disposeNext(current)
-
-	local entry = current;
-	while (true) do
-		local from = entry.mixingFrom
-		if from == nil then break end
-		queue:_end(from)
-		entry.mixingFrom = nil
-		entry.mixingTo = nil
-		entry = from
-	end
-
-	tracks[current.trackIndex] = nil
-
-	queue:drain()
-end
-
-function AnimationState:setCurrent (index, current, interrupt)
-	local from = self:expandToIndex(index)
-	local tracks = self.tracks
-	local queue = self.queue
-	tracks[index] = current
-
-	if from then
-		if interrupt then queue:interrupt(from) end
-		current.mixingFrom = from
-		from.mixingTo = current
-		current.mixTime = 0
-
-		if from.mixingFrom and from.mixDuration > 0 then
-			current.interruptAlpha = current.interruptAlpha * math_min(1, from.mixTime / from.mixDuration)
-		end
-
-		from.timelinesRotation = {};
-	end
-
-	queue:start(current)
-end
-
-function AnimationState:setAnimationByName (trackIndex, animationName, loop)
-		local animation = self.data.skeletonData:findAnimation(animationName)
-		if not animation then error("Animation not found: " .. animationName, 2) end
-		return self:setAnimation(trackIndex, animation, loop)
-end
-
-function AnimationState:setAnimation (trackIndex, animation, loop)
-	if not animation then error("animation cannot be null.") end
-	local interrupt = true;
-	local current = self:expandToIndex(trackIndex)
-	local queue = self.queue
-	local tracks = self.tracks
-	if current then
-		if current.nextTrackLast == -1 then
-			-- Don't mix from an entry that was never applied.
-			tracks[trackIndex] = current.mixingFrom
-			queue:interrupt(current)
-			queue:_end(current)
-			self:disposeNext(current)
-			current = current.mixingFrom
-			interrupt = false;
-		else
-			self:disposeNext(current)
-		end
-	end
-	local entry = self:trackEntry(trackIndex, animation, loop, current)
-	self:setCurrent(trackIndex, entry, interrupt)
-	queue:drain()
-	return entry
-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
-	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
-
-	local last = self:expandToIndex(trackIndex)
-	if last then
-		while last.next do
-			last = last.next
-		end
-	end
-
-	local entry = self:trackEntry(trackIndex, animation, loop, last)
-	local queue = self.queue
-	local data = self.data
-
-	if not last then
-		self:setCurrent(trackIndex, entry, true)
-		queue:drain()
-	else
-		last.next = entry
-		if delay <= 0 then
-			local duration = last.animationEnd - last.animationStart
-			if duration ~= 0 then
-				if last.loop then
-					delay = delay + duration * (1 + math_floor(last.trackTime / duration))
-				else
-					delay = delay + math_max(duration, last.trackTime)
-				end
-				delay = delay - data:getMix(last.animation, animation)
-			else
-				delay = last.trackTime
-			end
-		end
-	end
-
-	entry.delay = delay
-	return entry
-end
-
-function AnimationState:setEmptyAnimation (trackIndex, mixDuration)
-	local entry = self:setAnimation(trackIndex, EMPTY_ANIMATION, false)
-	entry.mixDuration = mixDuration
-	entry.trackEnd = mixDuration
-	return entry
-end
-
-function AnimationState:addEmptyAnimation (trackIndex, mixDuration, delay)
-	if delay <= 0 then delay = delay - mixDuration end
-	local entry = self:addAnimation(trackIndex, EMPTY_ANIMATION, false, delay)
-	entry.mixDuration = mixDuration
-	entry.trackEnd = mixDuration
-	return entry
-end
-
-function AnimationState:setEmptyAnimations (mixDuration)
-	local queue = self.queue
-	local oldDrainDisabled = queue.drainDisabled
-	queue.drainDisabled = true
-	local tracks = self.tracks
-	local numTracks = getNumTracks(tracks)
-	local i = 0
-	while i <= numTracks do
-		current = tracks[i]
-		if current then self:setEmptyAnimation(current.trackIndex, mixDuration) end
-		i = i + 1
-	end
-	queue.drainDisabled = oldDrainDisabled
-	queue:drain()
-end
-
-function AnimationState:expandToIndex (index)
-	return self.tracks[index]
-end
-
-function AnimationState:trackEntry (trackIndex, animation, loop, last)
-	local data = self.data
-	local entry = TrackEntry.new()
-	entry.trackIndex = trackIndex
-	entry.animation = animation
-	entry.loop = loop
-	entry.holdPrevious = false
-
-	entry.eventThreshold = 0
-	entry.attachmentThreshold = 0
-	entry.drawOrderThreshold = 0
-
-	entry.animationStart = 0
-	entry.animationEnd = animation.duration
-	entry.animationLast = -1
-	entry.nextAnimationLast = -1
-
-	entry.delay = 0
-	entry.trackTime = 0
-	entry.trackLast = -1
-	entry.nextTrackLast = -1
-	entry.trackEnd = 999999999
-	entry.timeScale = 1
-
-	entry.alpha = 1
-	entry.interruptAlpha = 1
-	entry.mixTime = 0
-	if not last then
-		entry.mixDuration = 0
-	else
-		entry.mixDuration = data:getMix(last.animation.name, animation.name)
-	end
-	entry.mixBlend = MixBlend.replace
-	return entry
-end
-
-function AnimationState:disposeNext (entry)
-	local _next = entry.next
-	local queue = self.queue
-	while _next do
-		queue:dispose(_next)
-		_next = _next.next
-	end
-	entry.next = nil
-end
-
-function getNumTracks(tracks)
-	local numTracks = 0
-	if tracks then
-		for i, track in pairs(tracks) do
-			if i > numTracks then
-				numTracks = i
-			end
-		end
-	end
-	return numTracks
-end
-
-function AnimationState:_animationsChanged ()
-	self.animationsChanged = false
-
-	self.propertyIDs = {}
-
-	local highestIndex = -1
-	local tracks = self.tracks
-	local numTracks = getNumTracks(tracks)
-	local i = 0
-	while i <= numTracks do
-		entry = tracks[i]
-		if entry then
-			if i > highestIndex then highestIndex = i end
-
-			if entry then
-				while entry.mixingFrom do
-					entry = entry.mixingFrom
-				end
-
-				repeat
-					if (entry.mixingTo == nil or entry.mixBlend ~= MixBlend.add) then
-						self:computeHold(entry)
-					end
-					entry = entry.mixingTo
-				until (entry == nil)
-			end
-		end
-		i = i + 1
-	end
-end
-
-function AnimationState:computeHold(entry)
-	local to = entry.mixingTo
-	local timelines = entry.animation.timelines
-	local timelinesCount = #entry.animation.timelines
-	local timelineMode = entry.timelineMode
-	local timelineHoldMix = entry.timelineHoldMix
-	local propertyIDs = self.propertyIDs
-
-	if (to and to.holdPrevious) then
-		local i = 1
-		while i <= timelinesCount do
-			local id = "" .. timelines[i]:getPropertyId()
-			if propertyIDs[id] == nil then
-				propertyIDs[id] = id
-				timelineMode[i] = HOLD_FIRST
-			else
-				timelineMode[i] = HOLD_SUBSEQUENT
-			end
-		end
-		return
-	end
-
-	local i = 1
-	local skip
-	while i <= timelinesCount do
-		local id = "" .. timelines[i]:getPropertyId()
-		if propertyIDs[id] then
-			timelineMode[i] = SUBSEQUENT
-		else
-			propertyIDs[id] = id
-			local timeline = timelines[i]
-			if to == nil or timeline.type == Animation.TimelineType.attachment
-				or timeline.type == Animation.TimelineType.drawOrder
-				or timeline.type == Animation.TimelineType.event
-				or not to.animation:hasTimeline(id) then
-				timelineMode[i] = FIRST
-			else
-				local next = to.mixingTo
-				skip = false
-				while next do
-					if not next.animation:hasTimeline(id) then
-						if entry.mixDuration > 0 then
-							timelineMode[i] = HOLD_MIX
-							timelineHoldMix[i] = next
-							skip = true
-							break
-						end
-					end
-					next = next.mixingTo
-				end
-				if not skip then 	timelineMode[i] = HOLD_FIRST end
-			end
-		end
-		i = i + 1
-	end
-end
-
-function AnimationState:getCurrent (trackIndex)
-	return self.tracks[trackIndex]
-end
-
-function AnimationState:getLast (trackIndex)
-	local lastEntry = self.tracks[trackIndex]
-	while lastEntry.next do
-		lastEntry = lastEntry.next
-	end
-	return lastEntry
-end
-
-function AnimationState:clearListeners ()
-	self.onStart = nil
-	self.onInterrupt = nil
-	self.onEnd = nil
-	self.onComplete = nil
-	self.onDispose = nil
-	self.onEvent = nil
-end
-
-function AnimationState:clearListenerNotificatin ()
-	self.queue:clear()
-end
-
-return AnimationState
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+local table_insert = table.insert
+local utils = require "spine-lua.utils"
+local Animation = require "spine-lua.Animation"
+local MixBlend = Animation.MixBlend
+local MixDirection = Animation.MixDirection
+local AnimationStateData = require "spine-lua.AnimationStateData"
+local math_min = math.min
+local math_max = math.max
+local math_abs = math.abs
+local math_signum = utils.signum
+local math_floor = math.floor
+local math_ceil = math.ceil
+local math_mod = utils.mod
+local testBit = utils.testBit
+local setBit = utils.setBit
+local clearBit = utils.clearBit
+
+local function zlen(array)
+	return #array + 1
+end
+
+local EMPTY_ANIMATION = Animation.new("<empty>", {}, 0)
+local SUBSEQUENT = 0
+local FIRST = 1
+local HOLD_SUBSEQUENT = 2
+local HOLD_FIRST = 3
+local HOLD_MIX = 4
+
+local SETUP = 1
+local CURRENT = 2
+
+local EventType = {
+	start = 0,
+	interrupt = 1,
+	_end = 2,
+	dispose = 3,
+	complete = 4,
+	event = 5
+}
+
+local EventQueue = {}
+EventQueue.__index = EventQueue
+
+function EventQueue.new (animationState)
+	local self = {
+		objects = {},
+		animationState = animationState,
+		drainDisabled = false
+	}
+	setmetatable(self, EventQueue)
+	return self
+end
+
+function EventQueue:start (entry)
+	local objects = self.objects
+	table_insert(objects, EventType.start)
+	table_insert(objects, entry)
+	self.animationState.animationsChanged = true
+end
+
+function EventQueue:interrupt (entry)
+	local objects = self.objects
+	table_insert(objects, EventType.interrupt)
+	table_insert(objects, entry)
+end
+
+function EventQueue:_end (entry)
+	local objects = self.objects
+	table_insert(objects, EventType._end)
+	table_insert(objects, entry)
+	self.animationState.animationsChanged = true
+end
+
+function EventQueue:dispose (entry)
+	local objects = self.objects
+	table_insert(objects, EventType.dispose)
+	table_insert(objects, entry)
+end
+
+function EventQueue:complete (entry)
+	local objects = self.objects
+	table_insert(objects, EventType.complete)
+	table_insert(objects, entry)
+end
+
+function EventQueue:event (entry, event)
+	local objects = self.objects
+	table_insert(objects, EventType.event)
+	table_insert(objects, entry)
+	table_insert(objects, event)
+end
+
+function EventQueue:drain ()
+	if self.drainDisabled then return end -- Not reentrant.
+	self.drainDisabled = true
+
+	local objects = self.objects
+	local as = self.animationState
+	local i = 1
+	local n = #objects
+	while i <= n do
+		local _type = objects[i]
+		local entry = objects[i + 1]
+		if _type == EventType.start then
+			if entry.onStart then entry.onStart(entry) end
+			if as.onStart then as.onStart(entry) end
+		elseif _type == EventType.interrupt then
+			if entry.onInterrupt then entry.onInterrupt(entry) end
+			if as.onInterrupt then as.onInterrupt(entry) end
+		elseif _type == EventType._end then
+			if entry.onEnd then entry.onEnd(entry) end
+			if as.onEnd then as.onEnd(entry) end
+			-- fall through in ref impl
+			if entry.onDispose then entry.onDispose(entry) end
+			if as.onDispose then as.onDispose(entry) end
+		elseif _type == EventType._dispose then
+			if entry.onDispose then entry.onDispose(entry) end
+			if as.onDispose then as.onDispose(entry) end
+		elseif _type == EventType.complete then
+			if entry.onComplete then entry.onComplete(entry) end
+			if as.onComplete then as.onComplete(entry) end
+		elseif _type == EventType.event then
+			local event = objects[i + 2]
+			if entry.onEvent then entry.onEvent(entry, event) end
+			if as.onEvent then as.onEvent(entry, event) end
+			i = i + 1
+		end
+		i = i + 2
+	end
+	self:clear()
+
+	self.drainDisabled = false;
+end
+
+function EventQueue:clear ()
+	self.objects = {}
+end
+
+local TrackEntry = {}
+TrackEntry.__index = TrackEntry
+
+function TrackEntry.new ()
+	local self = {
+		animation = nil,
+		next = nil, mixingFrom = nil, mixingTo = nil,
+		onStart = nil, onInterrupt = nil, onEnd = nil, onDispose = nil, onComplete = nil, onEvent = nil,
+		trackIndex = 0,
+		loop = false, holdPrevious = false,
+		eventThreshold = 0, attachmentThreshold = 0, drawOrderThreshold = 0,
+		animationStart = 0, animationEnd = 0, animationLast = 0, nextAnimationLast = 0,
+		delay = 0, trackTime = 0, trackLast = 0, nextTrackLast = 0, trackEnd = 0, timeScale = 0,
+		alpha = 0, mixTime = 0, mixDuration = 0, interruptAlpha = 0, totalAlpha = 0,
+		mixBlend = MixBlend.replace,
+		timelineMode = {},
+		timelineHoldMix = {},
+		timelinesRotation = {}
+	}
+	setmetatable(self, TrackEntry)
+	return self
+end
+
+function TrackEntry:getAnimationTime ()
+	if self.loop then
+		local duration = self.animationEnd - self.animationStart
+		if duration == 0 then return self.animationStart end
+		return (self.trackTime % duration) + self.animationStart
+	end
+	return math_min(self.trackTime + self.animationStart, self.animationEnd)
+end
+
+function TrackEntry:resetRotationDirections ()
+	self.timelinesRotation = {}
+end
+
+local AnimationState = {}
+AnimationState.__index = AnimationState
+
+function AnimationState.new (data)
+	if not data then error("data cannot be nil", 2) end
+
+	local self = {
+		data = data,
+		tracks = {},
+		events = {},
+		onStart = nil, onInterrupt = nil, onEnd = nil, onDispose = nil, onComplete = nil, onEvent = nil,
+		queue = nil,
+		propertyIDs = {},
+		animationsChanged = false,
+		timeScale = 1,
+		mixingTo = {},
+		unkeyedState = 0
+	}
+	self.queue = EventQueue.new(self)
+	setmetatable(self, AnimationState)
+	return self
+end
+
+AnimationState.TrackEntry = TrackEntry
+
+function AnimationState:update (delta)
+	delta = delta * self.timeScale
+	local tracks = self.tracks
+	local queue = self.queue
+	local numTracks = getNumTracks(tracks)
+	local i = 0
+	while i <= numTracks do
+		current = tracks[i]
+		if current then
+			current.animationLast = current.nextAnimationLast
+			current.trackLast = current.nextTrackLast
+
+			local currentDelta = delta * current.timeScale
+
+			local skip = false
+			if current.delay > 0 then
+				current.delay = current.delay - currentDelta
+				if current.delay <= 0 then
+					skip = true
+					currentDelta = -current.delay
+					current.delay = 0
+				end
+			end
+
+			if not skip then
+				local _next = current.next
+				if _next then
+					-- When the next entry's delay is passed, change to the next entry, preserving leftover time.
+					local nextTime = current.trackLast - _next.delay
+					if nextTime >= 0 then
+						_next.delay = 0
+						if current.timeScale == 0 then
+							_next.trackTime = _next.trackTime + 0
+						else
+							_next.trackTime = _next.trackTime + (nextTime / current.timeScale + delta) * _next.timeScale
+						end
+						current.trackTime = current.trackTime + currentDelta
+						self:setCurrent(i, _next, true)
+						while _next.mixingFrom do
+							_next.mixTime = _next.mixTime + delta
+							_next = _next.mixingFrom
+						end
+						skip = true
+					end
+				else
+					-- Clear the track when there is no next entry, the track end time is reached, and there is no mixingFrom.
+					if current.trackLast >= current.trackEnd and current.mixingFrom == nil then
+						tracks[i] = nil
+						queue:_end(current)
+						self:disposeNext(current)
+						skip = true
+					end
+				end
+
+				if not skip then
+					if current.mixingFrom and self:updateMixingFrom(current, delta) then
+						-- End mixing from entries once all have completed.
+						local from = current.mixingFrom
+						current.mixingFrom = nil
+						if from then from.mixingTo = nil end
+						while from do
+							queue:_end(from)
+							from = from.mixingFrom
+						end
+					end
+
+					current.trackTime = current.trackTime + currentDelta
+				end
+			end
+		end
+		i = i + 1
+	end
+
+	queue:drain()
+end
+
+function AnimationState:updateMixingFrom (to, delta)
+	local from = to.mixingFrom
+	if from == nil then return true end
+
+	local finished = self:updateMixingFrom(from, delta)
+
+	from.animationLast = from.nextAnimationLast
+	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
+		-- 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
+			to.mixingFrom = from.mixingFrom
+			if from.mixingFrom then from.mixingFrom.mixingTo = to end
+			to.interruptAlpha = from.interruptAlpha
+			self.queue:_end(from)
+		end
+		return finished
+	end
+
+	from.trackTime = from.trackTime + delta * from.timeScale
+	to.mixTime = to.mixTime + delta
+	return false;
+end
+
+function AnimationState:apply (skeleton)
+	if skeleton == nil then error("skeleton cannot be null.", 2) end
+	if self.animationsChanged then self:_animationsChanged() end
+
+	local tracks = self.tracks
+	local queue = self.queue
+	local applied = false
+
+	local numTracks = getNumTracks(tracks)
+	local i = 0
+	while i <= numTracks do
+		current = tracks[i]
+		if current then
+			if not (current == nil or current.delay > 0) then
+				applied = true
+
+				local blend = current.mixBlend
+				if i == 0 then blend = MixBlend.first end
+
+				-- Apply mixing from entries first.
+				local mix = current.alpha
+				if current.mixingFrom then
+					mix = mix * self:applyMixingFrom(current, skeleton, blend)
+				elseif current.trackTime >= current.trackEnd and current.next == nil then
+					mix = 0
+				end
+
+				-- Apply current entry.
+				local animationLast = current.animationLast
+				local animationTime = current:getAnimationTime()
+				local timelines = current.animation.timelines
+				if (i == 0 and mix == 1) or blend == MixBlend.add then
+					for i,timeline in ipairs(timelines) do
+						if timeline.type == Animation.TimelineType.attachment then
+							self:applyAttachmentTimeline(timeline, skeleton, animationTime, blend, true)
+						else
+							timeline:apply(skeleton, animationLast, animationTime, self.events, mix, blend, MixDirection._in)
+						end
+					end
+				else
+					local timelineMode = current.timelineMode
+					local firstFrame = #current.timelinesRotation == 0
+					local timelinesRotation = current.timelinesRotation
+
+					for ii,timeline in ipairs(timelines) do
+						local timelineBlend = MixBlend.setup
+						if timelineMode[ii] == SUBSEQUENT then timelineBlend = blend end
+
+						if timeline.type == Animation.TimelineType.rotate then
+							self:applyRotateTimeline(timeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, ii * 2,
+									firstFrame)
+						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)
+						end
+					end
+				end
+				self:queueEvents(current, animationTime)
+				self.events = {};
+				current.nextAnimationLast = animationTime
+				current.nextTrackLast = current.trackTime
+			end
+		end
+		i = i + 1
+	end
+
+	-- Set slots attachments to the setup pose, if needed. This occurs if an animation that is mixing out sets attachments so
+	-- subsequent timelines see any deform, but the subsequent timelines don't set an attachment (eg they are also mixing out or
+	-- the time is before the first key).
+	local setupState = self.unkeyedState + SETUP
+	local slots = skeleton.slots;
+	for _, slot in ipairs(slots) do
+		if slot.attachmentState == setupState then
+			local attachmentName = slot.data.attachmentName
+			if attachmentName == nil then
+				slot.attachment = nil
+			else
+				slot.attachment = skeleton:getAttachmentByIndex(slot.data.index, attachmentName)
+			end
+		end
+	end
+	self.unkeyedState = self.unkeyedState + 2; -- Increasing after each use avoids the need to reset attachmentState for every slot.
+
+
+	queue:drain()
+	return applied
+end
+
+function AnimationState:applyMixingFrom (to, skeleton, blend)
+	local from = to.mixingFrom
+	if from.mixingFrom then self:applyMixingFrom(from, skeleton, blend) end
+
+	local mix = 0
+	if to.mixDuration == 0 then -- Single frame mix to undo mixingFrom changes.
+		mix = 1
+		if blend == MixBlend.first then blend = MixBlend.setup end
+	else
+		mix = to.mixTime / to.mixDuration
+		if mix > 1 then mix = 1 end
+		if blend ~= MixBlend.first then blend = from.mixBlend end
+	end
+
+	local events = nil
+	if mix < from.eventThreshold then events = self.events end
+	local attachments = mix < from.attachmentThreshold
+	local drawOrder = mix < from.drawOrderThreshold
+	local animationLast = from.animationLast
+	local animationTime = from:getAnimationTime()
+	local timelines = from.animation.timelines
+	local alphaHold = from.alpha * to.interruptAlpha
+	local alphaMix = alphaHold * (1 - mix)
+
+	if blend == MixBlend.add then
+		for i,timeline in ipairs(timelines) do
+			timeline:apply(skeleton, animationLast, animationTime, events, alphaMix, blend, MixDirection.out)
+		end
+	else
+		local timelineMode = from.timelineMode
+		local timelineHoldMix = from.timelineHoldMix
+
+		local firstFrame = #from.timelinesRotation == 0
+		local timelinesRotation = from.timelinesRotation
+
+		from.totalAlpha = 0;
+
+		for i,timeline in ipairs(timelines) do
+			local skipSubsequent = false;
+			local direction = MixDirection.out;
+			local timelineBlend = MixBlend.setup
+			local alpha = 0
+			if timelineMode[i] == SUBSEQUENT then
+				if not drawOrder and timeline.type == Animation.TimelineType.drawOrder then skipSubsequent = true end
+				timelineBlend = blend
+				alpha = alphaMix
+			elseif timelineMode[i] == FIRST then
+				timelineBlend = MixBlend.setup
+				alpha = alphaMix
+			elseif timelineMode[i] == HOLD_SUBSEQUENT then
+				timelineBlend = blend
+				alpha = alphaHold
+			elseif timelineMode[i] == HOLD_FIRST then
+				timelineBlend = MixBlend.setup
+				alpha = alphaHold
+			else
+				timelineBlend = MixBlend.setup
+				local holdMix = timelineHoldMix[i]
+				alpha = alphaHold * math_max(0, 1 - holdMix.mixtime / holdMix.mixDuration)
+			end
+
+			if not skipSubsequent then
+				from.totalAlpha = from.totalAlpha + alpha
+				if timeline.type == Animation.TimelineType.rotate then
+					self:applyRotateTimeline(timeline, skeleton, animationTime, alpha, timelineBlend, timelinesRotation, i * 2, firstFrame)
+				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
+					end
+					timeline:apply(skeleton, animationLast, animationTime, self.events, alpha, timelineBlend, direction)
+				end
+			end
+		end
+	end
+
+	if (to.mixDuration > 0) then
+		self:queueEvents(from, animationTime)
+	end
+	self.events = {};
+	from.nextAnimationLast = animationTime
+	from.nextTrackLast = from.trackTime
+
+	return mix
+end
+
+function AnimationState:applyAttachmentTimeline(timeline, skeleton, time, blend, attachments)
+	local slot = skeleton.slots[timeline.slotIndex];
+	if slot.bone.active == false then return end
+
+	local frames = timeline.frames
+	if time < frames[0] then -- Time is before first frame.
+		if blend == MixBlend.setup or blend == MixBlend.first then
+			self:setAttachment(skeleton, slot, slot.data.attachmentName, attachments);
+		end
+	else
+		local frameIndex = 0
+		if (time >= frames[zlen(frames) - 1]) then -- Time is after last frame.
+			frameIndex = zlen(frames) - 1;
+		else
+			frameIndex = Animation.binarySearch(frames, time, 1) - 1;
+		end
+		self:setAttachment(skeleton, slot, timeline.attachmentNames[frameIndex], attachments)
+	end
+
+	-- If an attachment wasn't set (ie before the first frame or attachments is false), set the setup attachment later.
+	if slot.attachmentState <= self.unkeyedState then slot.attachmentState = self.unkeyedState + SETUP end
+end
+
+function AnimationState:setAttachment(skeleton, slot, attachmentName, attachments)
+	if (attachmentName == nil) then
+		slot.attachment = nil
+	else
+		slot.attachment = skeleton:getAttachmentByIndex(slot.data.index, attachmentName)
+	end
+	if attachments then slot.attachmentState = self.unkeyedState + CURRENT end
+end
+
+function AnimationState:applyRotateTimeline (timeline, skeleton, time, alpha, blend, timelinesRotation, i, firstFrame)
+	if firstFrame then
+		timelinesRotation[i] = 0
+		timelinesRotation[i+1] = 0
+	end
+
+	if alpha == 1 then
+		timeline:apply(skeleton, 0, time, nil, 1, blend, MixDirection._in)
+		return
+	end
+
+	local rotateTimeline = timeline
+	local frames = rotateTimeline.frames
+	local bone = skeleton.bones[rotateTimeline.boneIndex]
+	if not bone.active then return end
+	local r1 = 0
+	local r2 = 0
+	if time < frames[0] then
+		if blend == MixBlend.setup then
+			bone.rotation = bone.data.rotation
+			return
+		elseif blend == MixBlend.first then
+			r1 = bone.rotation
+			r2 = bone.data.rotation
+		else
+			return
+		end
+	else
+		if blend == MixBlend.setup then
+			r1 = bone.data.rotation
+		else
+			r1 = bone.rotation
+		end
+		if time >= frames[zlen(frames) - Animation.RotateTimeline.ENTRIES] then -- Time is after last frame.
+			r2 = bone.data.rotation + frames[zlen(frames) + Animation.RotateTimeline.PREV_ROTATION]
+		else
+			-- Interpolate between the previous frame and the current frame.
+			local frame = Animation.binarySearch(frames, time, Animation.RotateTimeline.ENTRIES)
+			local prevRotation = frames[frame + Animation.RotateTimeline.PREV_ROTATION]
+			local frameTime = frames[frame]
+			local percent = rotateTimeline:getCurvePercent(math_floor(frame / 2) - 1,
+				1 - (time - frameTime) / (frames[frame + Animation.RotateTimeline.PREV_TIME] - frameTime))
+
+			r2 = frames[frame + Animation.RotateTimeline.ROTATION] - prevRotation
+			r2 = r2 - (16384 - math_floor(16384.499999999996 - r2 / 360)) * 360
+			r2 = prevRotation + r2 * percent + bone.data.rotation
+			r2 = r2 - (16384 - math_floor(16384.499999999996 - r2 / 360)) * 360
+		end
+	end
+
+	-- Mix between rotations using the direction of the shortest route on the first frame while detecting crosses.
+	local total = 0
+	local diff = r2 - r1
+	diff = diff - (16384 - math_floor(16384.499999999996 - diff / 360)) * 360
+	if diff == 0 then
+		total = timelinesRotation[i]
+	else
+		local lastTotal = 0
+		local lastDiff = 0
+		if firstFrame then
+			lastTotal = 0
+			lastDiff = diff
+		else
+			lastTotal = timelinesRotation[i] -- Angle and direction of mix, including loops.
+			lastDiff = timelinesRotation[i + 1] -- Difference between bones.
+		end
+		local current = diff > 0
+		local dir = lastTotal >= 0
+		-- Detect cross at 0 (not 180).
+		if math_signum(lastDiff) ~= math_signum(diff) and math_abs(lastDiff) <= 90 then
+			-- A cross after a 360 rotation is a loop.
+			if math_abs(lastTotal) > 180 then lastTotal = lastTotal + 360 * math_signum(lastTotal) end
+			dir = current
+		end
+		total = diff + lastTotal - math_mod(lastTotal, 360) -- FIXME used to be %360, store loops as part of lastTotal.
+		if dir ~= current then total = total + 360 * math_signum(lastTotal) end
+		timelinesRotation[i] = total
+	end
+	timelinesRotation[i + 1] = diff
+	r1 = r1 + total * alpha
+	bone.rotation = r1 - (16384 - math_floor(16384.499999999996 - r1 / 360)) * 360
+end
+
+function AnimationState:queueEvents (entry, animationTime)
+	local animationStart = entry.animationStart
+	local animationEnd = entry.animationEnd
+	local duration = animationEnd - animationStart
+	local trackLastWrapped = entry.trackLast % duration
+
+	-- Queue events before complete.
+	local events = self.events
+	local queue = self.queue
+	local i = 1
+	local n = #events
+	while i <= n do
+		local event = events[i]
+		if event.time < trackLastWrapped then break end
+		if not (event.time > animationEnd) then -- Discard events outside animation start/end.
+			queue:event(entry, event)
+		end
+		i = i + 1
+	end
+
+	-- Queue complete if completed a loop iteration or the animation.
+	local queueComplete = false
+	if entry.loop then
+		queueComplete = duration == 0 or (trackLastWrapped > entry.trackTime % duration)
+	else
+		queueComplete = (animationTime >= animationEnd and entry.animationLast < animationEnd)
+	end
+	if queueComplete then
+		queue:complete(entry)
+	end
+
+	-- Queue events after complete.
+	while i <= n do
+		local event = events[i]
+		if not (event.time < animationStart) then --// Discard events outside animation start/end.
+			queue:event(entry, event)
+		end
+		i = i + 1
+	end
+end
+
+function AnimationState:clearTracks ()
+	local queue = self.queue
+	local tracks = self.tracks
+	local oldDrainDisabled = queue.drainDisabled
+	queue.drainDisabled = true;
+	local numTracks = getNumTracks(tracks)
+	local i = 0
+	while i <= numTracks do
+		self:clearTrack(i)
+	end
+	tracks = {}
+	queue.drainDisabled = oldDrainDisabled
+	queue:drain();
+end
+
+function AnimationState:clearTrack (trackIndex)
+	local tracks = self.tracks
+	local queue = self.queue
+	local current = tracks[trackIndex]
+	if current == nil then return end
+
+	queue:_end(current)
+
+	self:disposeNext(current)
+
+	local entry = current;
+	while (true) do
+		local from = entry.mixingFrom
+		if from == nil then break end
+		queue:_end(from)
+		entry.mixingFrom = nil
+		entry.mixingTo = nil
+		entry = from
+	end
+
+	tracks[current.trackIndex] = nil
+
+	queue:drain()
+end
+
+function AnimationState:setCurrent (index, current, interrupt)
+	local from = self:expandToIndex(index)
+	local tracks = self.tracks
+	local queue = self.queue
+	tracks[index] = current
+
+	if from then
+		if interrupt then queue:interrupt(from) end
+		current.mixingFrom = from
+		from.mixingTo = current
+		current.mixTime = 0
+
+		if from.mixingFrom and from.mixDuration > 0 then
+			current.interruptAlpha = current.interruptAlpha * math_min(1, from.mixTime / from.mixDuration)
+		end
+
+		from.timelinesRotation = {};
+	end
+
+	queue:start(current)
+end
+
+function AnimationState:setAnimationByName (trackIndex, animationName, loop)
+		local animation = self.data.skeletonData:findAnimation(animationName)
+		if not animation then error("Animation not found: " .. animationName, 2) end
+		return self:setAnimation(trackIndex, animation, loop)
+end
+
+function AnimationState:setAnimation (trackIndex, animation, loop)
+	if not animation then error("animation cannot be null.") end
+	local interrupt = true;
+	local current = self:expandToIndex(trackIndex)
+	local queue = self.queue
+	local tracks = self.tracks
+	if current then
+		if current.nextTrackLast == -1 then
+			-- Don't mix from an entry that was never applied.
+			tracks[trackIndex] = current.mixingFrom
+			queue:interrupt(current)
+			queue:_end(current)
+			self:disposeNext(current)
+			current = current.mixingFrom
+			interrupt = false;
+		else
+			self:disposeNext(current)
+		end
+	end
+	local entry = self:trackEntry(trackIndex, animation, loop, current)
+	self:setCurrent(trackIndex, entry, interrupt)
+	queue:drain()
+	return entry
+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
+	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
+
+	local last = self:expandToIndex(trackIndex)
+	if last then
+		while last.next do
+			last = last.next
+		end
+	end
+
+	local entry = self:trackEntry(trackIndex, animation, loop, last)
+	local queue = self.queue
+	local data = self.data
+
+	if not last then
+		self:setCurrent(trackIndex, entry, true)
+		queue:drain()
+	else
+		last.next = entry
+		if delay <= 0 then
+			local duration = last.animationEnd - last.animationStart
+			if duration ~= 0 then
+				if last.loop then
+					delay = delay + duration * (1 + math_floor(last.trackTime / duration))
+				else
+					delay = delay + math_max(duration, last.trackTime)
+				end
+				delay = delay - data:getMix(last.animation, animation)
+			else
+				delay = last.trackTime
+			end
+		end
+	end
+
+	entry.delay = delay
+	return entry
+end
+
+function AnimationState:setEmptyAnimation (trackIndex, mixDuration)
+	local entry = self:setAnimation(trackIndex, EMPTY_ANIMATION, false)
+	entry.mixDuration = mixDuration
+	entry.trackEnd = mixDuration
+	return entry
+end
+
+function AnimationState:addEmptyAnimation (trackIndex, mixDuration, delay)
+	if delay <= 0 then delay = delay - mixDuration end
+	local entry = self:addAnimation(trackIndex, EMPTY_ANIMATION, false, delay)
+	entry.mixDuration = mixDuration
+	entry.trackEnd = mixDuration
+	return entry
+end
+
+function AnimationState:setEmptyAnimations (mixDuration)
+	local queue = self.queue
+	local oldDrainDisabled = queue.drainDisabled
+	queue.drainDisabled = true
+	local tracks = self.tracks
+	local numTracks = getNumTracks(tracks)
+	local i = 0
+	while i <= numTracks do
+		current = tracks[i]
+		if current then self:setEmptyAnimation(current.trackIndex, mixDuration) end
+		i = i + 1
+	end
+	queue.drainDisabled = oldDrainDisabled
+	queue:drain()
+end
+
+function AnimationState:expandToIndex (index)
+	return self.tracks[index]
+end
+
+function AnimationState:trackEntry (trackIndex, animation, loop, last)
+	local data = self.data
+	local entry = TrackEntry.new()
+	entry.trackIndex = trackIndex
+	entry.animation = animation
+	entry.loop = loop
+	entry.holdPrevious = false
+
+	entry.eventThreshold = 0
+	entry.attachmentThreshold = 0
+	entry.drawOrderThreshold = 0
+
+	entry.animationStart = 0
+	entry.animationEnd = animation.duration
+	entry.animationLast = -1
+	entry.nextAnimationLast = -1
+
+	entry.delay = 0
+	entry.trackTime = 0
+	entry.trackLast = -1
+	entry.nextTrackLast = -1
+	entry.trackEnd = 999999999
+	entry.timeScale = 1
+
+	entry.alpha = 1
+	entry.interruptAlpha = 1
+	entry.mixTime = 0
+	if not last then
+		entry.mixDuration = 0
+	else
+		entry.mixDuration = data:getMix(last.animation.name, animation.name)
+	end
+	entry.mixBlend = MixBlend.replace
+	return entry
+end
+
+function AnimationState:disposeNext (entry)
+	local _next = entry.next
+	local queue = self.queue
+	while _next do
+		queue:dispose(_next)
+		_next = _next.next
+	end
+	entry.next = nil
+end
+
+function getNumTracks(tracks)
+	local numTracks = 0
+	if tracks then
+		for i, track in pairs(tracks) do
+			if i > numTracks then
+				numTracks = i
+			end
+		end
+	end
+	return numTracks
+end
+
+function AnimationState:_animationsChanged ()
+	self.animationsChanged = false
+
+	self.propertyIDs = {}
+
+	local highestIndex = -1
+	local tracks = self.tracks
+	local numTracks = getNumTracks(tracks)
+	local i = 0
+	while i <= numTracks do
+		entry = tracks[i]
+		if entry then
+			if i > highestIndex then highestIndex = i end
+
+			if entry then
+				while entry.mixingFrom do
+					entry = entry.mixingFrom
+				end
+
+				repeat
+					if (entry.mixingTo == nil or entry.mixBlend ~= MixBlend.add) then
+						self:computeHold(entry)
+					end
+					entry = entry.mixingTo
+				until (entry == nil)
+			end
+		end
+		i = i + 1
+	end
+end
+
+function AnimationState:computeHold(entry)
+	local to = entry.mixingTo
+	local timelines = entry.animation.timelines
+	local timelinesCount = #entry.animation.timelines
+	local timelineMode = entry.timelineMode
+	local timelineHoldMix = entry.timelineHoldMix
+	local propertyIDs = self.propertyIDs
+
+	if (to and to.holdPrevious) then
+		local i = 1
+		while i <= timelinesCount do
+			local id = "" .. timelines[i]:getPropertyId()
+			if propertyIDs[id] == nil then
+				propertyIDs[id] = id
+				timelineMode[i] = HOLD_FIRST
+			else
+				timelineMode[i] = HOLD_SUBSEQUENT
+			end
+		end
+		return
+	end
+
+	local i = 1
+	local skip
+	while i <= timelinesCount do
+		local id = "" .. timelines[i]:getPropertyId()
+		if propertyIDs[id] then
+			timelineMode[i] = SUBSEQUENT
+		else
+			propertyIDs[id] = id
+			local timeline = timelines[i]
+			if to == nil or timeline.type == Animation.TimelineType.attachment
+				or timeline.type == Animation.TimelineType.drawOrder
+				or timeline.type == Animation.TimelineType.event
+				or not to.animation:hasTimeline(id) then
+				timelineMode[i] = FIRST
+			else
+				local next = to.mixingTo
+				skip = false
+				while next do
+					if not next.animation:hasTimeline(id) then
+						if entry.mixDuration > 0 then
+							timelineMode[i] = HOLD_MIX
+							timelineHoldMix[i] = next
+							skip = true
+							break
+						end
+					end
+					next = next.mixingTo
+				end
+				if not skip then 	timelineMode[i] = HOLD_FIRST end
+			end
+		end
+		i = i + 1
+	end
+end
+
+function AnimationState:getCurrent (trackIndex)
+	return self.tracks[trackIndex]
+end
+
+function AnimationState:getLast (trackIndex)
+	local lastEntry = self.tracks[trackIndex]
+	while lastEntry.next do
+		lastEntry = lastEntry.next
+	end
+	return lastEntry
+end
+
+function AnimationState:clearListeners ()
+	self.onStart = nil
+	self.onInterrupt = nil
+	self.onEnd = nil
+	self.onComplete = nil
+	self.onDispose = nil
+	self.onEvent = nil
+end
+
+function AnimationState:clearListenerNotificatin ()
+	self.queue:clear()
+end
+
+return AnimationState

+ 58 - 58
spine-lua/AnimationStateData.lua → spine-lua/spine-lua/AnimationStateData.lua

@@ -1,58 +1,58 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local AnimationStateData = {}
-
-function AnimationStateData.new (skeletonData)
-	if not skeletonData then error("skeletonData cannot be nil", 2) end
-
-	local self = {
-		skeletonData = skeletonData,
-		animationToMixTime = {},
-		defaultMix = 0
-	}
-
-	function self:setMix (fromName, toName, duration)
-		if not self.animationToMixTime[fromName] then
-			self.animationToMixTime[fromName] = {}
-		end
-		self.animationToMixTime[fromName][toName] = duration
-	end
-
-	function self:getMix (fromName, toName)
-		local first = self.animationToMixTime[fromName]
-		if not first then return self.defaultMix end
-		local duration = first[toName]
-		if not duration then return self.defaultMix end
-		return duration
-	end
-
-	return self
-end
-return AnimationStateData
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local AnimationStateData = {}
+
+function AnimationStateData.new (skeletonData)
+	if not skeletonData then error("skeletonData cannot be nil", 2) end
+
+	local self = {
+		skeletonData = skeletonData,
+		animationToMixTime = {},
+		defaultMix = 0
+	}
+
+	function self:setMix (fromName, toName, duration)
+		if not self.animationToMixTime[fromName] then
+			self.animationToMixTime[fromName] = {}
+		end
+		self.animationToMixTime[fromName][toName] = duration
+	end
+
+	function self:getMix (fromName, toName)
+		local first = self.animationToMixTime[fromName]
+		if not first then return self.defaultMix end
+		local duration = first[toName]
+		if not duration then return self.defaultMix end
+		return duration
+	end
+
+	return self
+end
+return AnimationStateData

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

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

+ 0 - 0
spine-lua/AtlasAttachmentLoader.lua → spine-lua/spine-lua/AtlasAttachmentLoader.lua


+ 68 - 68
spine-lua/AttachmentLoader.lua → spine-lua/spine-lua/AttachmentLoader.lua

@@ -1,68 +1,68 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local AttachmentType = require "spine-lua.attachments.AttachmentType"
-local RegionAttachment = require "spine-lua.attachments.RegionAttachment"
-local BoundingBoxAttachment = require "spine-lua.attachments.BoundingBoxAttachment"
-local MeshAttachment = require "spine-lua.attachments.MeshAttachment"
-local PathAttachment = require "spine-lua.attachments.PathAttachment"
-local PointAttachment = require "spine-lua.attachments.PointAttachment"
-local ClippingAttachment = require "spine-lua.attachments.ClippingAttachment"
-
-local AttachmentLoader = {}
-function AttachmentLoader.new ()
-	local self = {}
-
-	function self:newRegionAttachment (skin, name, path)
-		return RegionAttachment.new(name)
-	end
-
-	function self:newMeshAttachment (skin, name, path)
-		return MeshAttachment.new(name)
-	end
-
-	function self:newBoundingBoxAttachment (skin, name)
-		return BoundingBoxAttachment.new(name)
-	end
-
-	function self:newPathAttachment(skin, name)
-		return PathAttachment.new(name)
-	end
-
-	function self:newPointAttachment(skin, name)
-		return PointAttachment.new(name)
-	end
-
-	function self:newClippingAttachment(skin, name)
-		return ClippingAttachment.new(name)
-	end
-
-	return self
-end
-return AttachmentLoader
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local AttachmentType = require "spine-lua.attachments.AttachmentType"
+local RegionAttachment = require "spine-lua.attachments.RegionAttachment"
+local BoundingBoxAttachment = require "spine-lua.attachments.BoundingBoxAttachment"
+local MeshAttachment = require "spine-lua.attachments.MeshAttachment"
+local PathAttachment = require "spine-lua.attachments.PathAttachment"
+local PointAttachment = require "spine-lua.attachments.PointAttachment"
+local ClippingAttachment = require "spine-lua.attachments.ClippingAttachment"
+
+local AttachmentLoader = {}
+function AttachmentLoader.new ()
+	local self = {}
+
+	function self:newRegionAttachment (skin, name, path)
+		return RegionAttachment.new(name)
+	end
+
+	function self:newMeshAttachment (skin, name, path)
+		return MeshAttachment.new(name)
+	end
+
+	function self:newBoundingBoxAttachment (skin, name)
+		return BoundingBoxAttachment.new(name)
+	end
+
+	function self:newPathAttachment(skin, name)
+		return PathAttachment.new(name)
+	end
+
+	function self:newPointAttachment(skin, name)
+		return PointAttachment.new(name)
+	end
+
+	function self:newClippingAttachment(skin, name)
+		return ClippingAttachment.new(name)
+	end
+
+	return self
+end
+return AttachmentLoader

+ 36 - 36
spine-lua/AttachmentType.lua → spine-lua/spine-lua/AttachmentType.lua

@@ -1,36 +1,36 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local AttachmentType = {
-	region = 0,
-	boundingbox = 1,
-	mesh = 2,
-	skinnedmesh = 3
-}
-return AttachmentType
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local AttachmentType = {
+	region = 0,
+	boundingbox = 1,
+	mesh = 2,
+	skinnedmesh = 3
+}
+return AttachmentType

+ 36 - 36
spine-lua/BlendMode.lua → spine-lua/spine-lua/BlendMode.lua

@@ -1,36 +1,36 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local BlendMode = {
-	normal = 0,
-	additive = 1,
-	multiply = 2,
-	screen = 3
-}
-return BlendMode
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local BlendMode = {
+	normal = 0,
+	additive = 1,
+	multiply = 2,
+	screen = 3
+}
+return BlendMode

+ 320 - 320
spine-lua/Bone.lua → spine-lua/spine-lua/Bone.lua

@@ -1,320 +1,320 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local setmetatable = setmetatable
-local math_rad = math.rad
-local math_deg = math.deg
-local math_sin = math.sin
-local math_cos = math.cos
-local math_atan2 = math.atan2
-local math_sqrt = math.sqrt
-local math_abs = math.abs
-local math_pi = math.pi
-
-local TransformMode = require "spine-lua.TransformMode"
-
-function math.sign(x)
-	if x<0 then
-		return -1
-	elseif x>0 then
-		return 1
-	else
-		return 0
-	end
-end
-
-local math_sign = math.sign
-
-local Bone = {}
-Bone.__index = Bone
-
-function Bone.new (data, skeleton, parent)
-	if not data then error("data cannot be nil", 2) end
-	if not skeleton then error("skeleton cannot be nil", 2) end
-
-	local self = {
-		data = data,
-		skeleton = skeleton,
-		parent = parent,
-		children = { },
-		x = 0, y = 0, rotation = 0, scaleX = 1, scaleY = 1, shearX = 0, shearY = 0,
-		ax = 0, ay = 0, arotation = 0, ascaleX = 0, ascaleY = 0, ashearX = 0, ashearY = 0,
-		appliedValid = false,
-
-		a = 0, b = 0, worldX = 0, -- a b x
-		c = 0, d = 0, worldY = 0, -- c d y
-		sorted = false,
-		active = false
-	}
-	setmetatable(self, Bone)
-
-	self:setToSetupPose()
-	return self
-end
-
-function Bone:update ()
-	self:updateWorldTransformWith(self.x, self.y, self.rotation, self.scaleX, self.scaleY, self.shearX, self.shearY)
-end
-
-function Bone:updateWorldTransform ()
-	self:updateWorldTransformWith(self.x, self.y, self.rotation, self.scaleX, self.scaleY, self.shearX, self.shearY)
-end
-
-function Bone:updateWorldTransformWith (x, y, rotation, scaleX, scaleY, shearX, shearY)
-	self.ax = x
-	self.ay = y
-	self.arotation = rotation
-	self.ascaleX = scaleX
-	self.ascaleY = scaleY
-	self.ashearX = shearX
-	self.ashearY = shearY
-	self.appliedValid = true
-
-	local sx = self.skeleton.scaleX;
-	local sy = self.skeleton.scaleY;
-
-	local parent = self.parent
-	if parent == nil then
-		local rotationY = rotation + 90 + shearY
-		local rotationRad = math_rad(rotation + shearX)
-		local rotationYRad = math_rad(rotationY)
-		local skeleton = self.skeleton
-		self.a = math_cos(rotationRad) * scaleX * sx
-		self.b = math_cos(rotationYRad) * scaleY * sx
-		self.c = math_sin(rotationRad) * scaleX * sy
-		self.d = math_sin(rotationYRad) * scaleY * sy
-		self.worldX = x * sx + skeleton.x
-		self.worldY = y * sy + skeleton.y
-		return
-	end
-
-	local pa = parent.a
-	local pb = parent.b
-	local pc = parent.c
-	local pd = parent.d
-	self.worldX = pa * x + pb * y + parent.worldX
-	self.worldY = pc * x + pd * y + parent.worldY
-
-	local transformMode = self.data.transformMode
-	if transformMode == TransformMode.normal then
-		local rotationY = rotation + 90 + shearY
-		local la = math_cos(math_rad(rotation + shearX)) * scaleX
-		local lb = math_cos(math_rad(rotationY)) * scaleY
-		local lc = math_sin(math_rad(rotation + shearX)) * scaleX
-		local ld = math_sin(math_rad(rotationY)) * scaleY
-		self.a = pa * la + pb * lc
-		self.b = pa * lb + pb * ld
-		self.c = pc * la + pd * lc
-		self.d = pc * lb + pd * ld
-		return;
-	elseif transformMode == TransformMode.onlyTranslation then
-		local rotationY = rotation + 90 + shearY
-		self.a = math_cos(math_rad(rotation + shearX)) * scaleX
-		self.b = math_cos(math_rad(rotationY)) * scaleY
-		self.c = math_sin(math_rad(rotation + shearX)) * scaleX
-		self.d = math_sin(math_rad(rotationY)) * scaleY
-	elseif transformMode == TransformMode.noRotationOrReflection then
-		local s = pa * pa + pc * pc
-		local prx = 0
-		if s > 0.0001 then
-			s = math_abs(pa * pd - pb * pc) / s
-			pa = pa / self.skeleton.scaleX
-			pc = pc / self.skeleton.scaleY
-			pb = pc * s
-			pd = pa * s
-			prx = math_deg(math_atan2(pc, pa));
-		else
-			pa = 0;
-			pc = 0;
-			prx = 90 - math_deg(math_atan2(pd, pb));
-		end
-		local rx = rotation + shearX - prx
-		local ry = rotation + shearY - prx + 90
-		local la = math_cos(math_rad(rx)) * scaleX
-		local lb = math_cos(math_rad(ry)) * scaleY
-		local lc = math_sin(math_rad(rx)) * scaleX
-		local ld = math_sin(math_rad(ry)) * scaleY
-		self.a = pa * la - pb * lc
-		self.b = pa * lb - pb * ld
-		self.c = pc * la + pd * lc
-		self.d = pc * lb + pd * ld
-
-	elseif transformMode == TransformMode.noScale or transformMode == TransformMode.noScaleOrReflection then
-		local cos = math_cos(math_rad(rotation))
-		local sin = math_sin(math_rad(rotation))
-		local za = (pa * cos + pb * sin) / sx
-		local zc = (pc * cos + pd * sin) / sy
-		local s = math_sqrt(za * za + zc * zc)
-		if s > 0.00001 then s = 1 / s end
-		za = za * s
-		zc = zc * s
-		s = math_sqrt(za * za + zc * zc)
-		if transformMode == TransformMode.noScale and pa * pd - pb * pc < 0 ~= (sx < 0) ~= (sy < 0) then
-			s = -s
-		end
-		local r = math_pi / 2 + math_atan2(zc, za)
-		local zb = math_cos(r) * s
-		local zd = math_sin(r) * s
-		local la = math_cos(math_rad(shearX)) * scaleX;
-		local lb = math_cos(math_rad(90 + shearY)) * scaleY;
-		local lc = math_sin(math_rad(shearX)) * scaleX;
-		local ld = math_sin(math_rad(90 + shearY)) * scaleY;
-		self.a = za * la + zb * lc
-		self.b = za * lb + zb * ld
-		self.c = zc * la + zd * lc
-		self.d = zc * lb + zd * ld
-	end
-
-	self.a = self.a * sx
-	self.b = self.b * sx
-	self.c = self.c * sy
-	self.d = self.d * sy
-end
-
-function Bone:setToSetupPose ()
-	local data = self.data
-	self.x = data.x
-	self.y = data.y
-	self.rotation = data.rotation
-	self.scaleX = data.scaleX
-	self.scaleY = data.scaleY
-	self.shearX = data.shearX
-	self.shearY = data.shearY
-end
-
-function Bone:getWorldRotationX ()
-	return math_deg(math_atan2(self.c, self.a))
-end
-
-function Bone:getWorldRotationY ()
-	return math_deg(math_atan2(self.d, self.b))
-end
-
-function Bone:getWorldScaleX ()
-	return math_sqrt(self.a * self.a + self.c * self.c)
-end
-
-function Bone:getWorldScaleY ()
-	return math_sqrt(self.b * self.b + self.d * self.d)
-end
-
-function Bone:updateAppliedTransform ()
-	local parent = self.parent
-	if parent == nil then
-		self.ax = self.worldX
-		self.ay = self.worldY
-		self.arotation = math_deg(math_atan2(self.c, self.a))
-		self.ascaleX = math_sqrt(self.a * self.a + self.c * self.c)
-		self.ascaleY = math_sqrt(self.b * self.b + self.d * self.d)
-		self.ashearX = 0
-		self.ashearY = math_deg(math_atan2(self.a * self.b + self.c * self.d, self.a * self.d - self.b * self.c))
-		return
-	end
-	local pa = parent.a
-	local pb = parent.b
-	local pc = parent.c
-	local pd = parent.d
-	local pid = 1 / (pa * pd - pb * pc)
-	local dx = self.worldX - parent.worldX
-	local dy = self.worldY - parent.worldY
-	self.ax = (dx * pd * pid - dy * pb * pid)
-	self.ay = (dy * pa * pid - dx * pc * pid)
-	local ia = pid * pd
-	local id = pid * pa
-	local ib = pid * pb
-	local ic = pid * pc
-	local ra = ia * self.a - ib * self.c
-	local rb = ia * self.b - ib * self.d
-	local rc = id * self.c - ic * self.a
-	local rd = id * self.d - ic * self.b
-	self.ashearX = 0
-	self.ascaleX = math_sqrt(ra * ra + rc * rc)
-	if self.ascaleX > 0.0001 then
-		local det = ra * rd - rb * rc
-		self.ascaleY = det / self.ascaleX
-		self.ashearY = math_deg(math_atan2(ra * rb + rc * rd, det))
-		self.arotation = math_deg(math_atan2(rc, ra))
-	else
-		self.ascaleX = 0
-		self.ascaleY = math_sqrt(rb * rb + rd * rd)
-		self.ashearY = 0
-		self.arotation = 90 - math_deg(math_atan2(rd, rb))
-	end
-end
-
-function Bone:worldToLocal (world)
-	local a = self.a
-	local b = self.b
-	local c = self.c
-	local d = self.d
-	local invDet = 1 / (a * d - b * c)
-	local x = world[1] - self.worldX
-	local y = world[2] - self.worldY
-	world[1] = (x * d * invDet - y * b * invDet)
-	world[2] = (y * a * invDet - x * c * invDet)
-	return world
-end
-
-function Bone:localToWorld (localCoords)
-	local x = localCoords[1]
-	local y = localCoords[2]
-	localCoords[1] = x * self.a + y * self.b + self.worldX
-	localCoords[2] = x * self.c + y * self.d + self.worldY
-	return localCoords
-end
-
-function Bone:worldToLocalRotation (worldRotation)
-	local sin = math_sin(math_rad(worldRotation))
-	local cos = math_cos(math_rad(worldRotation))
-	return math_deg(math_atan2(self.a * sin - self.c * cos, self.d * cos - self.b * sin)) + self.rotation - self.shearX
-end
-
-function Bone:localToWorldRotation (localRotation)
-	localRotation = localRotation - (self.rotation - self.shearX)
-	local sin = math_sin(math_rad(localRotation))
-	local cos = math_cos(math_rad(localRotation))
-	return math_deg(math_atan2(cos * self.c + sin * self.d, cos * self.a + sin * self.b))
-end
-
-function Bone:rotateWorld (degrees)
-	local a = self.a
-	local b = self.b
-	local c = self.c
-	local d = self.d
-	local degreesRad = math_rad(degrees)
-	local cos = math_cos(degreesRad)
-	local sin = math_sin(degreesRad)
-	self.a = cos * a - sin * c
-	self.b = cos * b - sin * d
-	self.c = sin * a + cos * c
-	self.d = sin * b + cos * d
-	self.appliedValid = false
-end
-
-return Bone
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+local math_rad = math.rad
+local math_deg = math.deg
+local math_sin = math.sin
+local math_cos = math.cos
+local math_atan2 = math.atan2
+local math_sqrt = math.sqrt
+local math_abs = math.abs
+local math_pi = math.pi
+
+local TransformMode = require "spine-lua.TransformMode"
+
+function math.sign(x)
+	if x<0 then
+		return -1
+	elseif x>0 then
+		return 1
+	else
+		return 0
+	end
+end
+
+local math_sign = math.sign
+
+local Bone = {}
+Bone.__index = Bone
+
+function Bone.new (data, skeleton, parent)
+	if not data then error("data cannot be nil", 2) end
+	if not skeleton then error("skeleton cannot be nil", 2) end
+
+	local self = {
+		data = data,
+		skeleton = skeleton,
+		parent = parent,
+		children = { },
+		x = 0, y = 0, rotation = 0, scaleX = 1, scaleY = 1, shearX = 0, shearY = 0,
+		ax = 0, ay = 0, arotation = 0, ascaleX = 0, ascaleY = 0, ashearX = 0, ashearY = 0,
+		appliedValid = false,
+
+		a = 0, b = 0, worldX = 0, -- a b x
+		c = 0, d = 0, worldY = 0, -- c d y
+		sorted = false,
+		active = false
+	}
+	setmetatable(self, Bone)
+
+	self:setToSetupPose()
+	return self
+end
+
+function Bone:update ()
+	self:updateWorldTransformWith(self.x, self.y, self.rotation, self.scaleX, self.scaleY, self.shearX, self.shearY)
+end
+
+function Bone:updateWorldTransform ()
+	self:updateWorldTransformWith(self.x, self.y, self.rotation, self.scaleX, self.scaleY, self.shearX, self.shearY)
+end
+
+function Bone:updateWorldTransformWith (x, y, rotation, scaleX, scaleY, shearX, shearY)
+	self.ax = x
+	self.ay = y
+	self.arotation = rotation
+	self.ascaleX = scaleX
+	self.ascaleY = scaleY
+	self.ashearX = shearX
+	self.ashearY = shearY
+	self.appliedValid = true
+
+	local sx = self.skeleton.scaleX;
+	local sy = self.skeleton.scaleY;
+
+	local parent = self.parent
+	if parent == nil then
+		local rotationY = rotation + 90 + shearY
+		local rotationRad = math_rad(rotation + shearX)
+		local rotationYRad = math_rad(rotationY)
+		local skeleton = self.skeleton
+		self.a = math_cos(rotationRad) * scaleX * sx
+		self.b = math_cos(rotationYRad) * scaleY * sx
+		self.c = math_sin(rotationRad) * scaleX * sy
+		self.d = math_sin(rotationYRad) * scaleY * sy
+		self.worldX = x * sx + skeleton.x
+		self.worldY = y * sy + skeleton.y
+		return
+	end
+
+	local pa = parent.a
+	local pb = parent.b
+	local pc = parent.c
+	local pd = parent.d
+	self.worldX = pa * x + pb * y + parent.worldX
+	self.worldY = pc * x + pd * y + parent.worldY
+
+	local transformMode = self.data.transformMode
+	if transformMode == TransformMode.normal then
+		local rotationY = rotation + 90 + shearY
+		local la = math_cos(math_rad(rotation + shearX)) * scaleX
+		local lb = math_cos(math_rad(rotationY)) * scaleY
+		local lc = math_sin(math_rad(rotation + shearX)) * scaleX
+		local ld = math_sin(math_rad(rotationY)) * scaleY
+		self.a = pa * la + pb * lc
+		self.b = pa * lb + pb * ld
+		self.c = pc * la + pd * lc
+		self.d = pc * lb + pd * ld
+		return;
+	elseif transformMode == TransformMode.onlyTranslation then
+		local rotationY = rotation + 90 + shearY
+		self.a = math_cos(math_rad(rotation + shearX)) * scaleX
+		self.b = math_cos(math_rad(rotationY)) * scaleY
+		self.c = math_sin(math_rad(rotation + shearX)) * scaleX
+		self.d = math_sin(math_rad(rotationY)) * scaleY
+	elseif transformMode == TransformMode.noRotationOrReflection then
+		local s = pa * pa + pc * pc
+		local prx = 0
+		if s > 0.0001 then
+			s = math_abs(pa * pd - pb * pc) / s
+			pa = pa / self.skeleton.scaleX
+			pc = pc / self.skeleton.scaleY
+			pb = pc * s
+			pd = pa * s
+			prx = math_deg(math_atan2(pc, pa));
+		else
+			pa = 0;
+			pc = 0;
+			prx = 90 - math_deg(math_atan2(pd, pb));
+		end
+		local rx = rotation + shearX - prx
+		local ry = rotation + shearY - prx + 90
+		local la = math_cos(math_rad(rx)) * scaleX
+		local lb = math_cos(math_rad(ry)) * scaleY
+		local lc = math_sin(math_rad(rx)) * scaleX
+		local ld = math_sin(math_rad(ry)) * scaleY
+		self.a = pa * la - pb * lc
+		self.b = pa * lb - pb * ld
+		self.c = pc * la + pd * lc
+		self.d = pc * lb + pd * ld
+
+	elseif transformMode == TransformMode.noScale or transformMode == TransformMode.noScaleOrReflection then
+		local cos = math_cos(math_rad(rotation))
+		local sin = math_sin(math_rad(rotation))
+		local za = (pa * cos + pb * sin) / sx
+		local zc = (pc * cos + pd * sin) / sy
+		local s = math_sqrt(za * za + zc * zc)
+		if s > 0.00001 then s = 1 / s end
+		za = za * s
+		zc = zc * s
+		s = math_sqrt(za * za + zc * zc)
+		if transformMode == TransformMode.noScale and pa * pd - pb * pc < 0 ~= (sx < 0) ~= (sy < 0) then
+			s = -s
+		end
+		local r = math_pi / 2 + math_atan2(zc, za)
+		local zb = math_cos(r) * s
+		local zd = math_sin(r) * s
+		local la = math_cos(math_rad(shearX)) * scaleX;
+		local lb = math_cos(math_rad(90 + shearY)) * scaleY;
+		local lc = math_sin(math_rad(shearX)) * scaleX;
+		local ld = math_sin(math_rad(90 + shearY)) * scaleY;
+		self.a = za * la + zb * lc
+		self.b = za * lb + zb * ld
+		self.c = zc * la + zd * lc
+		self.d = zc * lb + zd * ld
+	end
+
+	self.a = self.a * sx
+	self.b = self.b * sx
+	self.c = self.c * sy
+	self.d = self.d * sy
+end
+
+function Bone:setToSetupPose ()
+	local data = self.data
+	self.x = data.x
+	self.y = data.y
+	self.rotation = data.rotation
+	self.scaleX = data.scaleX
+	self.scaleY = data.scaleY
+	self.shearX = data.shearX
+	self.shearY = data.shearY
+end
+
+function Bone:getWorldRotationX ()
+	return math_deg(math_atan2(self.c, self.a))
+end
+
+function Bone:getWorldRotationY ()
+	return math_deg(math_atan2(self.d, self.b))
+end
+
+function Bone:getWorldScaleX ()
+	return math_sqrt(self.a * self.a + self.c * self.c)
+end
+
+function Bone:getWorldScaleY ()
+	return math_sqrt(self.b * self.b + self.d * self.d)
+end
+
+function Bone:updateAppliedTransform ()
+	local parent = self.parent
+	if parent == nil then
+		self.ax = self.worldX
+		self.ay = self.worldY
+		self.arotation = math_deg(math_atan2(self.c, self.a))
+		self.ascaleX = math_sqrt(self.a * self.a + self.c * self.c)
+		self.ascaleY = math_sqrt(self.b * self.b + self.d * self.d)
+		self.ashearX = 0
+		self.ashearY = math_deg(math_atan2(self.a * self.b + self.c * self.d, self.a * self.d - self.b * self.c))
+		return
+	end
+	local pa = parent.a
+	local pb = parent.b
+	local pc = parent.c
+	local pd = parent.d
+	local pid = 1 / (pa * pd - pb * pc)
+	local dx = self.worldX - parent.worldX
+	local dy = self.worldY - parent.worldY
+	self.ax = (dx * pd * pid - dy * pb * pid)
+	self.ay = (dy * pa * pid - dx * pc * pid)
+	local ia = pid * pd
+	local id = pid * pa
+	local ib = pid * pb
+	local ic = pid * pc
+	local ra = ia * self.a - ib * self.c
+	local rb = ia * self.b - ib * self.d
+	local rc = id * self.c - ic * self.a
+	local rd = id * self.d - ic * self.b
+	self.ashearX = 0
+	self.ascaleX = math_sqrt(ra * ra + rc * rc)
+	if self.ascaleX > 0.0001 then
+		local det = ra * rd - rb * rc
+		self.ascaleY = det / self.ascaleX
+		self.ashearY = math_deg(math_atan2(ra * rb + rc * rd, det))
+		self.arotation = math_deg(math_atan2(rc, ra))
+	else
+		self.ascaleX = 0
+		self.ascaleY = math_sqrt(rb * rb + rd * rd)
+		self.ashearY = 0
+		self.arotation = 90 - math_deg(math_atan2(rd, rb))
+	end
+end
+
+function Bone:worldToLocal (world)
+	local a = self.a
+	local b = self.b
+	local c = self.c
+	local d = self.d
+	local invDet = 1 / (a * d - b * c)
+	local x = world[1] - self.worldX
+	local y = world[2] - self.worldY
+	world[1] = (x * d * invDet - y * b * invDet)
+	world[2] = (y * a * invDet - x * c * invDet)
+	return world
+end
+
+function Bone:localToWorld (localCoords)
+	local x = localCoords[1]
+	local y = localCoords[2]
+	localCoords[1] = x * self.a + y * self.b + self.worldX
+	localCoords[2] = x * self.c + y * self.d + self.worldY
+	return localCoords
+end
+
+function Bone:worldToLocalRotation (worldRotation)
+	local sin = math_sin(math_rad(worldRotation))
+	local cos = math_cos(math_rad(worldRotation))
+	return math_deg(math_atan2(self.a * sin - self.c * cos, self.d * cos - self.b * sin)) + self.rotation - self.shearX
+end
+
+function Bone:localToWorldRotation (localRotation)
+	localRotation = localRotation - (self.rotation - self.shearX)
+	local sin = math_sin(math_rad(localRotation))
+	local cos = math_cos(math_rad(localRotation))
+	return math_deg(math_atan2(cos * self.c + sin * self.d, cos * self.a + sin * self.b))
+end
+
+function Bone:rotateWorld (degrees)
+	local a = self.a
+	local b = self.b
+	local c = self.c
+	local d = self.d
+	local degreesRad = math_rad(degrees)
+	local cos = math_cos(degreesRad)
+	local sin = math_sin(degreesRad)
+	self.a = cos * a - sin * c
+	self.b = cos * b - sin * d
+	self.c = sin * a + cos * c
+	self.d = sin * b + cos * d
+	self.appliedValid = false
+end
+
+return Bone

+ 55 - 55
spine-lua/BoneData.lua → spine-lua/spine-lua/BoneData.lua

@@ -1,55 +1,55 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local setmetatable = setmetatable
-local TransformMode = require "spine-lua.TransformMode"
-
-local BoneData = {}
-function BoneData.new (index, name, parent)
-	if index < 0 then error("index must be >= 0", 2) end
-	if not name then error("name cannot be nil", 2) end
-
-	local self = {
-		index = index,
-		name = name,
-		transformMode = TransformMode.normal,
-		parent = parent,
-		length = 0,
-		x = 0, y = 0,
-		rotation = 0,
-		scaleX = 1, scaleY = 1,
-		shearX = 0, shearY = 0,
-		inheritRotation = true,
-		inheritScale = true,
-		skinRequired = false
-	}
-
-	return self
-end
-return BoneData
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+local TransformMode = require "spine-lua.TransformMode"
+
+local BoneData = {}
+function BoneData.new (index, name, parent)
+	if index < 0 then error("index must be >= 0", 2) end
+	if not name then error("name cannot be nil", 2) end
+
+	local self = {
+		index = index,
+		name = name,
+		transformMode = TransformMode.normal,
+		parent = parent,
+		length = 0,
+		x = 0, y = 0,
+		rotation = 0,
+		scaleX = 1, scaleY = 1,
+		shearX = 0, shearY = 0,
+		inheritRotation = true,
+		inheritScale = true,
+		skinRequired = false
+	}
+
+	return self
+end
+return BoneData

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

@@ -1,61 +1,61 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local AttachmentType = require "spine-lua.AttachmentType"
-
-local BoundingBoxAttachment = {}
-function BoundingBoxAttachment.new (name)
-	if not name then error("name cannot be nil", 2) end
-
-	local self = {
-		name = name,
-		type = AttachmentType.boundingbox,
-		vertices = {}
-	}
-
-	function self:computeWorldVertices (x, y, bone, worldVertices)
-		x = x + bone.worldX
-		y = y + bone.worldY
-		local m00 = bone.m00
-		local m01 = bone.m01
-		local m10 = bone.m10
-		local m11 = bone.m11
-		local vertices = self.vertices
-		local count = #vertices
-		for i = 1, count, 2 do
-			local px = vertices[i]
-			local py = vertices[i + 1]
-			worldVertices[i] = px * m00 + py * m01 + x
-			worldVertices[i + 1] = px * m10 + py * m11 + y
-		end
-	end
-
-	return self
-end
-return BoundingBoxAttachment
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local AttachmentType = require "spine-lua.AttachmentType"
+
+local BoundingBoxAttachment = {}
+function BoundingBoxAttachment.new (name)
+	if not name then error("name cannot be nil", 2) end
+
+	local self = {
+		name = name,
+		type = AttachmentType.boundingbox,
+		vertices = {}
+	}
+
+	function self:computeWorldVertices (x, y, bone, worldVertices)
+		x = x + bone.worldX
+		y = y + bone.worldY
+		local m00 = bone.m00
+		local m01 = bone.m01
+		local m10 = bone.m10
+		local m11 = bone.m11
+		local vertices = self.vertices
+		local count = #vertices
+		for i = 1, count, 2 do
+			local px = vertices[i]
+			local py = vertices[i + 1]
+			worldVertices[i] = px * m00 + py * m01 + x
+			worldVertices[i + 1] = px * m10 + py * m11 + y
+		end
+	end
+
+	return self
+end
+return BoundingBoxAttachment

+ 83 - 83
spine-lua/Color.lua → spine-lua/spine-lua/Color.lua

@@ -1,83 +1,83 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local setmetatable = setmetatable
-local utils = require "spine-lua.utils"
-
-local Color = {}
-Color.__index = Color
-
-function Color.new ()
-	local self = {
-		r = 0, g = 0, b = 0, a = 0
-	}
-	setmetatable(self, Color)
-
-	return self
-end
-
-function Color.newWith (r, g, b, a)
-	local self = {
-		r = r, g = g, b = b, a = a
-	}
-	setmetatable(self, Color)
-
-	return self
-end
-
-function Color:set(r, g, b, a)
-	self.r = r
-	self.g = g
-	self.b = b
-	self.a = a
-end
-
-function Color:setFrom(color)
-	self.r = color.r
-	self.g = color.g
-	self.b = color.b
-	self.a = color.a
-end
-
-function Color:add(r, g, b, a)
-	self.r = self.r + r
-	self.g = self.g + g
-	self.b = self.b + b
-	self.a = self.a + a
-	self:clamp()
-end
-
-function Color:clamp()
-	self.r = utils.clamp(self.r, 0, 1)
-	self.g = utils.clamp(self.g, 0, 1)
-	self.b = utils.clamp(self.b, 0, 1)
-	self.a = utils.clamp(self.a, 0, 1)
-end
-
-return Color
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+local utils = require "spine-lua.utils"
+
+local Color = {}
+Color.__index = Color
+
+function Color.new ()
+	local self = {
+		r = 0, g = 0, b = 0, a = 0
+	}
+	setmetatable(self, Color)
+
+	return self
+end
+
+function Color.newWith (r, g, b, a)
+	local self = {
+		r = r, g = g, b = b, a = a
+	}
+	setmetatable(self, Color)
+
+	return self
+end
+
+function Color:set(r, g, b, a)
+	self.r = r
+	self.g = g
+	self.b = b
+	self.a = a
+end
+
+function Color:setFrom(color)
+	self.r = color.r
+	self.g = color.g
+	self.b = color.b
+	self.a = color.a
+end
+
+function Color:add(r, g, b, a)
+	self.r = self.r + r
+	self.g = self.g + g
+	self.b = self.b + b
+	self.a = self.a + a
+	self:clamp()
+end
+
+function Color:clamp()
+	self.r = utils.clamp(self.r, 0, 1)
+	self.g = utils.clamp(self.g, 0, 1)
+	self.b = utils.clamp(self.b, 0, 1)
+	self.a = utils.clamp(self.a, 0, 1)
+end
+
+return Color

+ 46 - 46
spine-lua/Event.lua → spine-lua/spine-lua/Event.lua

@@ -1,46 +1,46 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local Event = {}
-function Event.new (time, data)
-	if not data then error("data cannot be nil", 2) end
-
-	local self = {
-		data = data,
-		intValue = 0,
-		floatValue = 0,
-		stringValue = nil,
-		time = time,
-		volume = 1,
-		balance = 0
-	}
-
-	return self
-end
-return Event
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local Event = {}
+function Event.new (time, data)
+	if not data then error("data cannot be nil", 2) end
+
+	local self = {
+		data = data,
+		intValue = 0,
+		floatValue = 0,
+		stringValue = nil,
+		time = time,
+		volume = 1,
+		balance = 0
+	}
+
+	return self
+end
+return Event

+ 46 - 46
spine-lua/EventData.lua → spine-lua/spine-lua/EventData.lua

@@ -1,46 +1,46 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local EventData = {}
-function EventData.new (name)
-	if not name then error("name cannot be nil", 2) end
-
-	local self = {
-		name = name,
-		intValue = 0,
-		floatValue = 0,
-		stringValue = nil,
-		audioPath = nil,
-		volume = 1,
-		balance = 0
-	}
-
-	return self
-end
-return EventData
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local EventData = {}
+function EventData.new (name)
+	if not name then error("name cannot be nil", 2) end
+
+	local self = {
+		name = name,
+		intValue = 0,
+		floatValue = 0,
+		stringValue = nil,
+		audioPath = nil,
+		volume = 1,
+		balance = 0
+	}
+
+	return self
+end
+return EventData

+ 334 - 334
spine-lua/IkConstraint.lua → spine-lua/spine-lua/IkConstraint.lua

@@ -1,334 +1,334 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local setmetatable = setmetatable
-local math_pi = math.pi
-local math_atan2 = math.atan2
-local math_sqrt = math.sqrt
-local math_acos = math.acos
-local math_sin = math.sin
-local math_cos = math.cos
-local math_min = math.min
-local table_insert = table.insert
-local math_deg = math.deg
-local math_rad = math.rad
-local math_abs = math.abs
-
-local TransformMode = require "spine-lua.TransformMode"
-
-local IkConstraint = {}
-IkConstraint.__index = IkConstraint
-
-function IkConstraint.new (data, skeleton)
-	if not data then error("data cannot be nil", 2) end
-	if not skeleton then error("skeleton cannot be nil", 2) end
-
-	local self = {
-		data = data,
-		bones = {},
-		target = nil,
-		mix = data.mix,
-		softness = data.softness,
-		compress = data.compress,
-		stretch = data.stretch,
-		bendDirection = data.bendDirection,
-		active = false
-	}
-	setmetatable(self, IkConstraint)
-
-	local self_bones = self.bones
-	for _,boneData in ipairs(data.bones) do
-		table_insert(self_bones, skeleton:findBone(boneData.name))
-	end
-	self.target = skeleton:findBone(data.target.name)
-
-	return self
-end
-
-function IkConstraint:apply ()
-	self:update()
-end
-
-function IkConstraint:update ()
-	local target = self.target
-	local bones = self.bones
-	local boneCount = #bones
-	if boneCount == 1 then
-		self:apply1(bones[1], target.worldX, target.worldY, self.compress, self.stretch, self.data.uniform, self.mix)
-	elseif boneCount == 2 then
-		self:apply2(bones[1], bones[2], target.worldX, target.worldY, self.bendDirection, self.stretch, self.softness, self.mix)
-	end
-end
-
-function IkConstraint:apply1 (bone, targetX, targetY, compress, stretch, uniform, alpha)
-	if not bone.appliedValid then bone:updateAppliedTransform() end
-	local p = bone.parent
-
-	local pa = p.a
-	local pb = p.b
-	local pc = p.c
-	local pd = p.d
-	local rotationIK = -bone.ashearX - bone.arotation
-	local tx = 0
-	local ty = 0
-
-	if bone.data.transformMode == TransformMode.onlyTranslation then
-		tx = targetX - bone.worldX
-		ty = targetY - bone.worldY
-	elseif bone.data.transformMode == TransformMode.noRotationOrReflection then
-		local s = math_abs(pa * pd - pb * pc) / (pa * pa + pc * pc);
-		local sa = pa / bone.skeleton.scaleX;
-		local sc = pc / bone.skeleton.scaleY;
-		pb = -sc * s * bone.skeleton.scaleX;
-		pd = sa * s * bone.skeleton.scaleY;
-		rotationIK = rotationIK + math_deg(math_atan2(sc, sa));
-
-
-		local x = targetX - p.worldX
-		local y = targetY - p.worldY
-		local d = pa * pd - pb * pc
-		tx = (x * pd - y * pb) / d - bone.ax
-		ty = (y * pa - x * pc) / d - bone.ay
-	else
-		local x = targetX - p.worldX
-		local y = targetY - p.worldY
-		local d = pa * pd - pb * pc
-		tx = (x * pd - y * pb) / d - bone.ax
-		ty = (y * pa - x * pc) / d - bone.ay
-	end
-	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
-	end
-	local sx = bone.ascaleX
-	local sy = bone.ascaleY
-	if compress or stretch then
-		if bone.data.transformMode == TransformMode.noScale or bone.data.transformMode == TransformMode.noScaleOrReflection then
-			tx = targetX - bone.worldX
-			ty = targetY - bone.worldY
-		end
-		local b = bone.data.length * sx
-		local dd = math_sqrt(tx * tx + ty * ty)
-		if (compress and dd < b) or (stretch and dd > b) and b > 0.0001 then
-			local s = (dd / b - 1) * alpha + 1
-			sx = sx * s
-			if uniform then sy = sy * s end
-		end
-	end
-	bone:updateWorldTransformWith(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, sx, sy, bone.ashearX, bone.ashearY)
-	end
-
-function IkConstraint:apply2 (parent, child, targetX, targetY, bendDir, stretch, softness, alpha)
-	if alpha == 0 then
-		child:updateWorldTransform()
-		return
-	end
-	if not parent.appliedValid then parent:updateAppliedTransform() end
-	if not child.appliedValid then child:updateAppliedTransform() end
-	local px = parent.ax
-	local py = parent.ay
-	local psx = parent.ascaleX
-	local sx = psx
-	local psy = parent.ascaleY
-	local csx = child.ascaleX
-	local os1 = 0
-	local os2 = 0
-	local s2 = 0
-	if psx < 0 then
-		psx = -psx
-		os1 = 180
-		s2 = -1
-	else
-		os1 = 0
-		s2 = 1
-	end
-	if psy < 0 then
-		psy = -psy
-		s2 = -s2
-	end
-	if csx < 0 then
-		csx = -csx
-		os2 = 180
-	else
-		os2 = 0
-	end
-	local cx = child.ax
-	local cy = 0
-	local cwx = 0
-	local cwy = 0
-	local a = parent.a
-	local b = parent.b
-	local c = parent.c
-	local d = parent.d
-	local u = math_abs(psx - psy) <= 0.0001
-	if not u then
-		cy = 0
-		cwx = a * cx + parent.worldX
-		cwy = c * cx + parent.worldY
-	else
-		cy = child.ay
-		cwx = a * cx + b * cy + parent.worldX
-		cwy = c * cx + d * cy + parent.worldY
-	end
-	local pp = parent.parent
-	a = pp.a
-	b = pp.b
-	c = pp.c
-	d = pp.d
-	local id = 1 / (a * d - b * c)
-	local x = cwx - pp.worldX
-	local y = cwy - pp.worldY
-	local dx = (x * d - y * b) * id - px
-	local dy = (y * a - x * c) * id - py
-	local l1 = math_sqrt(dx * dx + dy * dy)
-	local l2 = child.data.length * csx
-	local a1 = 0
-	local a2 = 0
-	if l1 < 0.0001 then
-		self:apply1(parent, targetX, targetY, false, stretch, false, alpha)
-		child:updateWorldTransformWith(cx, cy, 0, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY)
-		return
-	end
-	x = targetX - pp.worldX
-	y = targetY - pp.worldY
-	local tx = (x * d - y * b) * id - px
-	local ty = (y * a - x * c) * id - py
-	local dd = tx * tx + ty * ty
-	if softness ~= 0 then
-		softness = softness * (psx * (csx + 1) / 2)
-		local td = math_sqrt(dd)
-		local sd = td - l1 - l2 * psx + softness
-		if sd > 0 then
-			local p = math_min(1, sd / (softness * 2)) - 1
-			p = (sd - softness * (1 - p * p)) / td
-			tx = tx - p * tx
-			ty = ty - p * ty
-			dd = tx * tx + ty * ty
-		end
-	end
-
-	if u then
-		l2 = l2 * psx
-		local cos = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2)
-		if cos < -1 then
-			cos = -1
-		elseif cos > 1 then
-			cos = 1
-			if stretch then sx = sx * ((math_sqrt(dd) / (l1 + l2) - 1) * alpha + 1) end
-		end
-		a2 = math_acos(cos) * bendDir
-		a = l1 + l2 * cos
-		b = l2 * math_sin(a2)
-		a1 = math_atan2(ty * a - tx * b, tx * a + ty * b)
-	else
-		local skip = false
-		a = psx * l2
-		b = psy * l2
-		local aa = a * a
-		local bb = b * b
-		local ta = math_atan2(ty, tx);
-		c = bb * l1 * l1 + aa * dd - aa * bb
-		local c1 = -2 * bb * l1
-		local c2 = bb - aa
-		d = c1 * c1 - 4 * c2 * c
-		if d >= 0 then
-			local q = math_sqrt(d);
-			if (c1 < 0) then q = -q end
-			q = -(c1 + q) / 2
-			local r0 = q / c2
-			local r1 = c / q
-			local r = r1
-			if math_abs(r0) < math_abs(r1) then r = r0 end
-			if r * r <= dd then
-				y = math_sqrt(dd - r * r) * bendDir
-				a1 = ta - math_atan2(y, r)
-				a2 = math_atan2(y / psy, (r - l1) / psx)
-				skip = true
-			end
-		end
-		if not skip then
-			local minAngle = math_pi
-			local minX = l1 - a
-			local minDist = minX * minX
-			local minY = 0;
-			local maxAngle = 0
-			local maxX = l1 + a
-			local maxDist = maxX * maxX
-			local maxY = 0
-			c = -a * l1 / (aa - bb)
-			if (c >= -1 and c <= 1) then
-				c = math_acos(c)
-				x = a * math_cos(c) + l1
-				y = b * math_sin(c)
-				d = x * x + y * y
-				if d < minDist then
-					minAngle = c
-					minDist = d
-					minX = x
-					minY = y
-				end
-				if d > maxDist then
-					maxAngle = c
-					maxDist = d
-					maxX = x
-					maxY = y
-				end
-			end
-			if dd <= (minDist + maxDist) / 2 then
-				a1 = ta - math_atan2(minY * bendDir, minX)
-				a2 = minAngle * bendDir
-			else
-				a1 = ta - math_atan2(maxY * bendDir, maxX)
-				a2 = maxAngle * bendDir
-			end
-		end
-	end
-	local os = math_atan2(cy, cx) * s2
-	local rotation = parent.arotation
-	a1 = math_deg(a1 - os) + os1 - rotation
-	if a1 > 180 then
-		a1 = a1 - 360
-	elseif a1 < -180 then
-		a1 = a1 + 360
-	end
-	parent:updateWorldTransformWith(px, py, rotation + a1 * alpha, sx, parent.ascaleY, 0, 0)
-	rotation = child.rotation
-	a2 = (math_deg(a2 + os) - child.ashearX) * s2 + os2 - rotation
-	if a2 > 180 then
-		a2 = a2 - 360
-	elseif a2 < -180 then
-		a2 = a2 + 360
-	end
-	child:updateWorldTransformWith(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY);
-end
-
-return IkConstraint
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+local math_pi = math.pi
+local math_atan2 = math.atan2
+local math_sqrt = math.sqrt
+local math_acos = math.acos
+local math_sin = math.sin
+local math_cos = math.cos
+local math_min = math.min
+local table_insert = table.insert
+local math_deg = math.deg
+local math_rad = math.rad
+local math_abs = math.abs
+
+local TransformMode = require "spine-lua.TransformMode"
+
+local IkConstraint = {}
+IkConstraint.__index = IkConstraint
+
+function IkConstraint.new (data, skeleton)
+	if not data then error("data cannot be nil", 2) end
+	if not skeleton then error("skeleton cannot be nil", 2) end
+
+	local self = {
+		data = data,
+		bones = {},
+		target = nil,
+		mix = data.mix,
+		softness = data.softness,
+		compress = data.compress,
+		stretch = data.stretch,
+		bendDirection = data.bendDirection,
+		active = false
+	}
+	setmetatable(self, IkConstraint)
+
+	local self_bones = self.bones
+	for _,boneData in ipairs(data.bones) do
+		table_insert(self_bones, skeleton:findBone(boneData.name))
+	end
+	self.target = skeleton:findBone(data.target.name)
+
+	return self
+end
+
+function IkConstraint:apply ()
+	self:update()
+end
+
+function IkConstraint:update ()
+	local target = self.target
+	local bones = self.bones
+	local boneCount = #bones
+	if boneCount == 1 then
+		self:apply1(bones[1], target.worldX, target.worldY, self.compress, self.stretch, self.data.uniform, self.mix)
+	elseif boneCount == 2 then
+		self:apply2(bones[1], bones[2], target.worldX, target.worldY, self.bendDirection, self.stretch, self.softness, self.mix)
+	end
+end
+
+function IkConstraint:apply1 (bone, targetX, targetY, compress, stretch, uniform, alpha)
+	if not bone.appliedValid then bone:updateAppliedTransform() end
+	local p = bone.parent
+
+	local pa = p.a
+	local pb = p.b
+	local pc = p.c
+	local pd = p.d
+	local rotationIK = -bone.ashearX - bone.arotation
+	local tx = 0
+	local ty = 0
+
+	if bone.data.transformMode == TransformMode.onlyTranslation then
+		tx = targetX - bone.worldX
+		ty = targetY - bone.worldY
+	elseif bone.data.transformMode == TransformMode.noRotationOrReflection then
+		local s = math_abs(pa * pd - pb * pc) / (pa * pa + pc * pc);
+		local sa = pa / bone.skeleton.scaleX;
+		local sc = pc / bone.skeleton.scaleY;
+		pb = -sc * s * bone.skeleton.scaleX;
+		pd = sa * s * bone.skeleton.scaleY;
+		rotationIK = rotationIK + math_deg(math_atan2(sc, sa));
+
+
+		local x = targetX - p.worldX
+		local y = targetY - p.worldY
+		local d = pa * pd - pb * pc
+		tx = (x * pd - y * pb) / d - bone.ax
+		ty = (y * pa - x * pc) / d - bone.ay
+	else
+		local x = targetX - p.worldX
+		local y = targetY - p.worldY
+		local d = pa * pd - pb * pc
+		tx = (x * pd - y * pb) / d - bone.ax
+		ty = (y * pa - x * pc) / d - bone.ay
+	end
+	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
+	end
+	local sx = bone.ascaleX
+	local sy = bone.ascaleY
+	if compress or stretch then
+		if bone.data.transformMode == TransformMode.noScale or bone.data.transformMode == TransformMode.noScaleOrReflection then
+			tx = targetX - bone.worldX
+			ty = targetY - bone.worldY
+		end
+		local b = bone.data.length * sx
+		local dd = math_sqrt(tx * tx + ty * ty)
+		if (compress and dd < b) or (stretch and dd > b) and b > 0.0001 then
+			local s = (dd / b - 1) * alpha + 1
+			sx = sx * s
+			if uniform then sy = sy * s end
+		end
+	end
+	bone:updateWorldTransformWith(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, sx, sy, bone.ashearX, bone.ashearY)
+	end
+
+function IkConstraint:apply2 (parent, child, targetX, targetY, bendDir, stretch, softness, alpha)
+	if alpha == 0 then
+		child:updateWorldTransform()
+		return
+	end
+	if not parent.appliedValid then parent:updateAppliedTransform() end
+	if not child.appliedValid then child:updateAppliedTransform() end
+	local px = parent.ax
+	local py = parent.ay
+	local psx = parent.ascaleX
+	local sx = psx
+	local psy = parent.ascaleY
+	local csx = child.ascaleX
+	local os1 = 0
+	local os2 = 0
+	local s2 = 0
+	if psx < 0 then
+		psx = -psx
+		os1 = 180
+		s2 = -1
+	else
+		os1 = 0
+		s2 = 1
+	end
+	if psy < 0 then
+		psy = -psy
+		s2 = -s2
+	end
+	if csx < 0 then
+		csx = -csx
+		os2 = 180
+	else
+		os2 = 0
+	end
+	local cx = child.ax
+	local cy = 0
+	local cwx = 0
+	local cwy = 0
+	local a = parent.a
+	local b = parent.b
+	local c = parent.c
+	local d = parent.d
+	local u = math_abs(psx - psy) <= 0.0001
+	if not u then
+		cy = 0
+		cwx = a * cx + parent.worldX
+		cwy = c * cx + parent.worldY
+	else
+		cy = child.ay
+		cwx = a * cx + b * cy + parent.worldX
+		cwy = c * cx + d * cy + parent.worldY
+	end
+	local pp = parent.parent
+	a = pp.a
+	b = pp.b
+	c = pp.c
+	d = pp.d
+	local id = 1 / (a * d - b * c)
+	local x = cwx - pp.worldX
+	local y = cwy - pp.worldY
+	local dx = (x * d - y * b) * id - px
+	local dy = (y * a - x * c) * id - py
+	local l1 = math_sqrt(dx * dx + dy * dy)
+	local l2 = child.data.length * csx
+	local a1 = 0
+	local a2 = 0
+	if l1 < 0.0001 then
+		self:apply1(parent, targetX, targetY, false, stretch, false, alpha)
+		child:updateWorldTransformWith(cx, cy, 0, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY)
+		return
+	end
+	x = targetX - pp.worldX
+	y = targetY - pp.worldY
+	local tx = (x * d - y * b) * id - px
+	local ty = (y * a - x * c) * id - py
+	local dd = tx * tx + ty * ty
+	if softness ~= 0 then
+		softness = softness * (psx * (csx + 1) / 2)
+		local td = math_sqrt(dd)
+		local sd = td - l1 - l2 * psx + softness
+		if sd > 0 then
+			local p = math_min(1, sd / (softness * 2)) - 1
+			p = (sd - softness * (1 - p * p)) / td
+			tx = tx - p * tx
+			ty = ty - p * ty
+			dd = tx * tx + ty * ty
+		end
+	end
+
+	if u then
+		l2 = l2 * psx
+		local cos = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2)
+		if cos < -1 then
+			cos = -1
+		elseif cos > 1 then
+			cos = 1
+			if stretch then sx = sx * ((math_sqrt(dd) / (l1 + l2) - 1) * alpha + 1) end
+		end
+		a2 = math_acos(cos) * bendDir
+		a = l1 + l2 * cos
+		b = l2 * math_sin(a2)
+		a1 = math_atan2(ty * a - tx * b, tx * a + ty * b)
+	else
+		local skip = false
+		a = psx * l2
+		b = psy * l2
+		local aa = a * a
+		local bb = b * b
+		local ta = math_atan2(ty, tx);
+		c = bb * l1 * l1 + aa * dd - aa * bb
+		local c1 = -2 * bb * l1
+		local c2 = bb - aa
+		d = c1 * c1 - 4 * c2 * c
+		if d >= 0 then
+			local q = math_sqrt(d);
+			if (c1 < 0) then q = -q end
+			q = -(c1 + q) / 2
+			local r0 = q / c2
+			local r1 = c / q
+			local r = r1
+			if math_abs(r0) < math_abs(r1) then r = r0 end
+			if r * r <= dd then
+				y = math_sqrt(dd - r * r) * bendDir
+				a1 = ta - math_atan2(y, r)
+				a2 = math_atan2(y / psy, (r - l1) / psx)
+				skip = true
+			end
+		end
+		if not skip then
+			local minAngle = math_pi
+			local minX = l1 - a
+			local minDist = minX * minX
+			local minY = 0;
+			local maxAngle = 0
+			local maxX = l1 + a
+			local maxDist = maxX * maxX
+			local maxY = 0
+			c = -a * l1 / (aa - bb)
+			if (c >= -1 and c <= 1) then
+				c = math_acos(c)
+				x = a * math_cos(c) + l1
+				y = b * math_sin(c)
+				d = x * x + y * y
+				if d < minDist then
+					minAngle = c
+					minDist = d
+					minX = x
+					minY = y
+				end
+				if d > maxDist then
+					maxAngle = c
+					maxDist = d
+					maxX = x
+					maxY = y
+				end
+			end
+			if dd <= (minDist + maxDist) / 2 then
+				a1 = ta - math_atan2(minY * bendDir, minX)
+				a2 = minAngle * bendDir
+			else
+				a1 = ta - math_atan2(maxY * bendDir, maxX)
+				a2 = maxAngle * bendDir
+			end
+		end
+	end
+	local os = math_atan2(cy, cx) * s2
+	local rotation = parent.arotation
+	a1 = math_deg(a1 - os) + os1 - rotation
+	if a1 > 180 then
+		a1 = a1 - 360
+	elseif a1 < -180 then
+		a1 = a1 + 360
+	end
+	parent:updateWorldTransformWith(px, py, rotation + a1 * alpha, sx, parent.ascaleY, 0, 0)
+	rotation = child.rotation
+	a2 = (math_deg(a2 + os) - child.ashearX) * s2 + os2 - rotation
+	if a2 > 180 then
+		a2 = a2 - 360
+	elseif a2 < -180 then
+		a2 = a2 + 360
+	end
+	child:updateWorldTransformWith(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY);
+end
+
+return IkConstraint

+ 50 - 50
spine-lua/IkConstraintData.lua → spine-lua/spine-lua/IkConstraintData.lua

@@ -1,50 +1,50 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local IkConstraintData = {}
-function IkConstraintData.new (name)
-	if not name then error("name cannot be nil", 2) end
-
-	local self = {
-		name = name,
-		order = 0,
-		skinRequired = false,
-		bones = {},
-		target = nil,
-		bendDirection = 1,
-		compress = false,
-		stretch = false,
-		uniform = false,
-		mix = 1,
-		softness = 0
-	}
-
-	return self
-end
-return IkConstraintData
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local IkConstraintData = {}
+function IkConstraintData.new (name)
+	if not name then error("name cannot be nil", 2) end
+
+	local self = {
+		name = name,
+		order = 0,
+		skinRequired = false,
+		bones = {},
+		target = nil,
+		bendDirection = 1,
+		compress = false,
+		stretch = false,
+		uniform = false,
+		mix = 1,
+		softness = 0
+	}
+
+	return self
+end
+return IkConstraintData

+ 0 - 0
spine-lua/Interpolation.lua → spine-lua/spine-lua/Interpolation.lua


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

@@ -1,93 +1,93 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local AttachmentType = require "spine-lua.AttachmentType"
-
-local MeshAttachment = {}
-function MeshAttachment.new (name)
-	if not name then error("name cannot be nil", 2) end
-
-	local self = {
-		name = name,
-		type = AttachmentType.mesh,
-		vertices = nil,
-		uvs = nil,
-		regionUVs = nil,
-		triangles = nil,
-		hullLength = 0,
-		r = 1, g = 1, b = 1, a = 1,
-		path = nil,
-		rendererObject = nil,
-		regionU = 0, regionV = 0, regionU2 = 1, regionV2 = 1, regionRotate = false,
-		regionOffsetX = 0, regionOffsetY = 0,
-		regionWidth = 0, regionHeight = 0,
-		regionOriginalWidth = 0, regionOriginalHeight = 0,
-		edges = nil,
-		width = 0, height = 0
-	}
-
-	function self:updateUVs ()
-		local width, height = self.regionU2 - self.regionU, self.regionV2 - self.regionV
-		local n = #self.regionUVs
-		if not self.uvs or #self.uvs ~= n then
-			self.uvs = {}
-		end
-		if self.regionRotate then
-			for i = 1, n, 2 do
-				self.uvs[i] = self.regionU + self.regionUVs[i + 1] * width
-				self.uvs[i + 1] = self.regionV + height - self.regionUVs[i] * height
-			end
-		else
-			for i = 1, n, 2 do
-				self.uvs[i] = self.regionU + self.regionUVs[i] * width
-				self.uvs[i + 1] = self.regionV + self.regionUVs[i + 1] * height
-			end
-		end
-	end
-
-	function self:computeWorldVertices (x, y, slot, worldVertices)
-		local bone = slot.bone
-x,y=slot.bone.skeleton.x,slot.bone.skeleton.y
-		x = x + bone.worldX
-		y = y + bone.worldY
-		local m00, m01, m10, m11 = bone.m00, bone.m01, bone.m10, bone.m11
-		local vertices = self.vertices
-		local verticesCount = #vertices
-		if slot.deform and #slot.deform == verticesCount then vertices = slot.deform end
-		for i = 1, verticesCount, 2 do
-			local vx = vertices[i]
-			local vy = vertices[i + 1]
-			worldVertices[i] = vx * m00 + vy * m01 + x
-			worldVertices[i + 1] = vx * m10 + vy * m11 + y
-		end
-	end
-
-	return self
-end
-return MeshAttachment
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local AttachmentType = require "spine-lua.AttachmentType"
+
+local MeshAttachment = {}
+function MeshAttachment.new (name)
+	if not name then error("name cannot be nil", 2) end
+
+	local self = {
+		name = name,
+		type = AttachmentType.mesh,
+		vertices = nil,
+		uvs = nil,
+		regionUVs = nil,
+		triangles = nil,
+		hullLength = 0,
+		r = 1, g = 1, b = 1, a = 1,
+		path = nil,
+		rendererObject = nil,
+		regionU = 0, regionV = 0, regionU2 = 1, regionV2 = 1, regionRotate = false,
+		regionOffsetX = 0, regionOffsetY = 0,
+		regionWidth = 0, regionHeight = 0,
+		regionOriginalWidth = 0, regionOriginalHeight = 0,
+		edges = nil,
+		width = 0, height = 0
+	}
+
+	function self:updateUVs ()
+		local width, height = self.regionU2 - self.regionU, self.regionV2 - self.regionV
+		local n = #self.regionUVs
+		if not self.uvs or #self.uvs ~= n then
+			self.uvs = {}
+		end
+		if self.regionRotate then
+			for i = 1, n, 2 do
+				self.uvs[i] = self.regionU + self.regionUVs[i + 1] * width
+				self.uvs[i + 1] = self.regionV + height - self.regionUVs[i] * height
+			end
+		else
+			for i = 1, n, 2 do
+				self.uvs[i] = self.regionU + self.regionUVs[i] * width
+				self.uvs[i + 1] = self.regionV + self.regionUVs[i + 1] * height
+			end
+		end
+	end
+
+	function self:computeWorldVertices (x, y, slot, worldVertices)
+		local bone = slot.bone
+x,y=slot.bone.skeleton.x,slot.bone.skeleton.y
+		x = x + bone.worldX
+		y = y + bone.worldY
+		local m00, m01, m10, m11 = bone.m00, bone.m01, bone.m10, bone.m11
+		local vertices = self.vertices
+		local verticesCount = #vertices
+		if slot.deform and #slot.deform == verticesCount then vertices = slot.deform end
+		for i = 1, verticesCount, 2 do
+			local vx = vertices[i]
+			local vy = vertices[i + 1]
+			worldVertices[i] = vx * m00 + vy * m01 + x
+			worldVertices[i + 1] = vx * m10 + vy * m11 + y
+		end
+	end
+
+	return self
+end
+return MeshAttachment

+ 563 - 563
spine-lua/PathConstraint.lua → spine-lua/spine-lua/PathConstraint.lua

@@ -1,563 +1,563 @@
--------------------------------------------------------------------------------
--- 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 the logic in this file uses 0-based indexing. Each array
--- access adds 1 to the calculated index. We should switch the logic
--- to 1-based indexing eventually.
-
-local setmetatable = setmetatable
-local AttachmentType = require "spine-lua.attachments.AttachmentType"
-local PathConstraintData = require "spine-lua.PathConstraintData"
-local utils = require "spine-lua.utils"
-local math_pi = math.pi
-local math_pi2 = math.pi * 2
-local math_atan2 = math.atan2
-local math_sqrt = math.sqrt
-local math_acos = math.acos
-local math_sin = math.sin
-local math_cos = math.cos
-local table_insert = table.insert
-local math_deg = math.deg
-local math_rad = math.rad
-local math_abs = math.abs
-local math_max = math.max
-
-local PathConstraint = {}
-PathConstraint.__index = PathConstraint
-
-PathConstraint.NONE = -1
-PathConstraint.BEFORE = -2
-PathConstraint.AFTER = -3
-PathConstraint.epsilon = 0.00001
-
-function PathConstraint.new (data, skeleton)
-	if not data then error("data cannot be nil", 2) end
-	if not skeleton then error("skeleton cannot be nil", 2) end
-
-	local self = {
-		data = data,
-		bones = {},
-		target = skeleton:findSlot(data.target.name),
-		position = data.position,
-		spacing = data.spacing,
-		rotateMix = data.rotateMix,
-		translateMix = data.translateMix,
-		spaces = {},
-		positions = {},
-		world = {},
-		curves = {},
-		lengths = {},
-		segments = {},
-		active = false
-	}
-	setmetatable(self, PathConstraint)
-
-	for _,boneData in ipairs(data.bones) do
-		table_insert(self.bones, skeleton:findBone(boneData.name))
-	end
-
-	return self
-end
-
-function PathConstraint:apply ()
-	self:update()
-end
-
-function PathConstraint:update ()
-	local attachment = self.target.attachment
-	if not attachment or not (attachment.type == AttachmentType.path) then return end
-
-	local rotateMix = self.rotateMix
-	local translateMix = self.translateMix
-	local translate = translateMix > 0
-	local rotate = rotateMix > 0
-	if not translate and not rotate then return end
-
-	local data = self.data;
-	local percentSpacing = data.spacingMode == PathConstraintData.SpacingMode.percent
-	local rotateMode = data.rotateMode
-	local tangents = rotateMode == PathConstraintData.RotateMode.tangent
-	local scale = rotateMode == PathConstraintData.RotateMode.chainscale
-	local bones = self.bones
-	local boneCount = #bones
-	local spacesCount = boneCount + 1
-	if tangents then spacesCount = boneCount end
-	local spaces = utils.setArraySize(self.spaces, spacesCount)
-	local lengths = nil
-	local spacing = self.spacing
-	if scale or not percentSpacing then
-		if scale then lengths = utils.setArraySize(self.lengths, boneCount) end
-		local lengthSpacing = data.spacingMode == PathConstraintData.SpacingMode.length
-		local i = 0
-		local n = spacesCount - 1
-		while i < n do
-			local bone = bones[i + 1];
-			local setupLength = bone.data.length
-			if setupLength < PathConstraint.epsilon then
-				if scale then lengths[i + 1] = 0 end
-				i = i + 1
-				spaces[i + 1] = 0
-			elseif percentSpacing then
-				if scale then
-					local x = setupLength * bone.a
-					local y = setupLength * bone.c
-					local length = math_sqrt(x * x + y * y)
-					lengths[i + 1] = length
-				end
-				i = i + 1
-				spaces[i + 1] = spacing
-			else
-	 			local x = setupLength * bone.a
-				local y = setupLength * bone.c
-				local length = math_sqrt(x * x + y * y)
-				if scale then lengths[i + 1] = length end
-				i = i + 1
-				if lengthSpacing then
-					spaces[i + 1] = (setupLength + spacing) * length / setupLength
-				else
-					spaces[i + 1] = spacing * length / setupLength
-				end
-			end
-		end
-	else
-		local i = 1
-		while i < spacesCount do
-			spaces[i + 1] = spacing
-			i = i + 1
-		end
-	end
-
-	local positions = self:computeWorldPositions(attachment, spacesCount, tangents, data.positionMode == PathConstraintData.PositionMode.percent, percentSpacing)
-	local boneX = positions[1]
-	local boneY = positions[2]
-	local offsetRotation = data.offsetRotation
-	local tip = false;
-	if offsetRotation == 0 then
-			tip = rotateMode == PathConstraintData.RotateMode.chain
-	else
-		tip = false;
-		local p = self.target.bone;
-		if p.a * p.d - p.b * p.c > 0 then
-			offsetRotation = offsetRotation * utils.degRad
-		else
-			offsetRotation = offsetRotation * -utils.degRad
-		end
-	end
-
-	local i = 0
-	local p = 3
-	while i < boneCount do
-		local bone = bones[i + 1]
-		bone.worldX = bone.worldX + (boneX - bone.worldX) * translateMix
-		bone.worldY = bone.worldY + (boneY - bone.worldY) * translateMix
-		local x = positions[p + 1]
-		local y = positions[p + 2]
-		local dx = x - boneX
-		local dy = y - boneY
-		if scale then
-			local length = lengths[i + 1]
-			if length ~= 0 then
-				local s = (math_sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1
-				bone.a = bone.a * s
-				bone.c = bone.c * s
-			end
-		end
-		boneX = x
-		boneY = y
-		if rotate then
-			local a = bone.a
-			local b = bone.b
-			local c = bone.c
-			local d = bone.d
-			local r = 0
-			local cos = 0
-			local sin = 0
-			if tangents then
-				r = positions[p - 1 + 1]
-			elseif spaces[i + 1 + 1] == 0 then
-				r = positions[p + 2 + 1]
-			else
-				r = math_atan2(dy, dx)
-			end
-			r = r - math_atan2(c, a)
-			if tip then
-				cos = math_cos(r)
-				sin = math_sin(r)
-				local length = bone.data.length
-				boneX = boneX + (length * (cos * a - sin * c) - dx) * rotateMix;
-				boneY = boneY + (length * (sin * a + cos * c) - dy) * rotateMix;
-			else
-				r = r + offsetRotation
-			end
-			if r > math_pi then
-				r = r - math_pi2
-			elseif r < -math_pi then
-				r = r + math_pi2
-			end
-			r = r * rotateMix
-			cos = math_cos(r)
-			sin = math.sin(r)
-			bone.a = cos * a - sin * c
-			bone.b = cos * b - sin * d
-			bone.c = sin * a + cos * c
-			bone.d = sin * b + cos * d
-		end
-		bone.appliedValid = false
-		i = i + 1
-		p = p + 3
-	end
-end
-
-function PathConstraint:computeWorldPositions (path, spacesCount, tangents, percentPosition, percentSpacing)
-	local target = self.target
-	local position = self.position
-	local spaces = self.spaces
-	local out = utils.setArraySize(self.positions, spacesCount * 3 + 2)
-	local world = nil
-	local closed = path.closed
-	local verticesLength = path.worldVerticesLength
-	local curveCount = verticesLength / 6
-	local prevCurve = PathConstraint.NONE
-	local i = 0
-
-	if not path.constantSpeed then
-		local lengths = path.lengths
-		if closed then curveCount = curveCount - 1 else curveCount = curveCount - 2 end
-		local pathLength = lengths[curveCount + 1];
-		if percentPosition then position = position * pathLength end
-		if percentSpacing then
-			i = 1
-			while i < spacesCount do
-				spaces[i + 1] = spaces[i + 1] * pathLength
-				i = i + 1
-			end
-		end
-		world = utils.setArraySize(self.world, 8);
-		i = 0
-		local o = 0
-		local curve = 0
-		while i < spacesCount do
-			local space = spaces[i + 1];
-			position = position + space
-			local p = position
-
-			local skip = false
-			if closed then
-				p = p % pathLength
-				if p < 0 then p = p + pathLength end
-				curve = 0
-			elseif p < 0 then
-				if prevCurve ~= PathConstraint.BEFORE then
-					prevCurve = PathConstraint.BEFORE
-					path:computeWorldVertices(target, 2, 4, world, 0, 2)
-				end
-				self:addBeforePosition(p, world, 0, out, o)
-				skip = true
-			elseif p > pathLength then
-				if prevCurve ~= PathConstraint.AFTER then
-					prevCurve = PathConstraint.AFTER
-					path:computeWorldVertices(target, verticesLength - 6, 4, world, 0, 2)
-				end
-				self:addAfterPosition(p - pathLength, world, 0, out, o)
-				skip = true
-			end
-
-			if not skip then
-				-- Determine curve containing position.
-				while true do
-					local length = lengths[curve + 1]
-					if p <= length then
-						if curve == 0 then
-							p = p / length
-						else
-							local prev = lengths[curve - 1 + 1]
-							p = (p - prev) / (length - prev)
-						end
-						break
-					end
-					curve = curve + 1
-				end
-				if curve ~= prevCurve then
-					prevCurve = curve
-					if closed and curve == curveCount then
-						path:computeWorldVertices(target, verticesLength - 4, 4, world, 0, 2)
-						path:computeWorldVertices(target, 0, 4, world, 4, 2)
-					else
-						path:computeWorldVertices(target, curve * 6 + 2, 8, world, 0, 2)
-					end
-				end
-				self:addCurvePosition(p, world[1], world[2], world[3], world[4], world[5], world[6], world[7], world[8], out, o, tangents or (i > 0 and space == 0))
-			end
-
-			i = i + 1
-			o = o + 3
-		end
-		return out
-	end
-
-	-- World vertices.
-	if closed then
-		verticesLength = verticesLength + 2
-		world = utils.setArraySize(self.world, verticesLength)
-		path:computeWorldVertices(target, 2, verticesLength - 4, world, 0, 2)
-		path:computeWorldVertices(target, 0, 2, world, verticesLength - 4, 2)
-		world[verticesLength - 2 + 1] = world[0 + 1]
-		world[verticesLength - 1 + 1] = world[1 + 1]
-	else
-		curveCount = curveCount - 1
-		verticesLength = verticesLength - 4;
-		world = utils.setArraySize(self.world, verticesLength)
-		path:computeWorldVertices(target, 2, verticesLength, world, 0, 2)
-	end
-
-	-- Curve lengths.
-	local curves = utils.setArraySize(self.curves, curveCount)
-	local pathLength = 0;
-	local x1 = world[0 + 1]
-	local y1 = world[1 + 1]
-	local cx1 = 0
-	local cy1 = 0
-	local cx2 = 0
-	local cy2 = 0
-	local x2 = 0
-	local y2 = 0
-	local tmpx = 0
-	local tmpy = 0
-	local dddfx = 0
-	local dddfy = 0
-	local ddfx = 0
-	local ddfy = 0
-	local dfx = 0
-	local dfy = 0
-	local w = 2
-	while i < curveCount do
-		cx1 = world[w + 1]
-		cy1 = world[w + 2]
-		cx2 = world[w + 3]
-		cy2 = world[w + 4]
-		x2 = world[w + 5]
-		y2 = world[w + 6]
-		tmpx = (x1 - cx1 * 2 + cx2) * 0.1875
-		tmpy = (y1 - cy1 * 2 + cy2) * 0.1875
-		dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375
-		dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375
-		ddfx = tmpx * 2 + dddfx
-		ddfy = tmpy * 2 + dddfy
-		dfx = (cx1 - x1) * 0.75 + tmpx + dddfx * 0.16666667
-		dfy = (cy1 - y1) * 0.75 + tmpy + dddfy * 0.16666667
-		pathLength = pathLength + math_sqrt(dfx * dfx + dfy * dfy)
-		dfx = dfx + ddfx
-		dfy = dfy + ddfy
-		ddfx = ddfx + dddfx
-		ddfy = ddfy + dddfy
-		pathLength = pathLength + math_sqrt(dfx * dfx + dfy * dfy)
-		dfx = dfx + ddfx
-		dfy = dfy + ddfy
-		pathLength = pathLength + math_sqrt(dfx * dfx + dfy * dfy)
-		dfx = dfx + ddfx + dddfx
-		dfy = dfy + ddfy + dddfy
-		pathLength = pathLength + math_sqrt(dfx * dfx + dfy * dfy)
-		curves[i + 1] = pathLength
-		x1 = x2
-		y1 = y2
-		i = i + 1
-		w = w + 6
-	end
-	if percentPosition then
-		position = position * pathLength
-	else
-		position = position * pathLength / path.lengths[curveCount];
-	end
-	if percentSpacing then
-		i = 1
-		while i < spacesCount do
-			spaces[i + 1] = spaces[i + 1] * pathLength
-			i = i + 1
-		end
-	end
-
-	local segments = self.segments
-	local curveLength = 0
-	i = 0
-	local o = 0
-	local curve = 0
-	local segment = 0
-	while i < spacesCount do
-		local space = spaces[i + 1]
-		position = position + space
-		local p = position
-
-		local skip = false
-		if closed then
-			p = p % pathLength
-			if p < 0 then p = p + pathLength end
-			curve = 0
-		elseif p < 0 then
-			self:addBeforePosition(p, world, 0, out, o)
-			skip = true
-		elseif p > pathLength then
-			self:addAfterPosition(p - pathLength, world, verticesLength - 4, out, o)
-			skip = true
-		end
-
-		if not skip then
-			-- Determine curve containing position.
-			while true do
-				local length = curves[curve + 1]
-				if p <= length then
-					if curve == 0 then
-						p = p / length
-					else
-						local prev = curves[curve - 1 + 1]
-						p = (p - prev) / (length - prev)
-					end
-					break
-				end
-				curve = curve + 1
-			end
-
-			-- Curve segment lengths.
-			if curve ~= prevCurve then
-				prevCurve = curve
-				local ii = curve * 6
-				x1 = world[ii + 1]
-				y1 = world[ii + 2]
-				cx1 = world[ii + 3]
-				cy1 = world[ii + 4]
-				cx2 = world[ii + 5]
-				cy2 = world[ii + 6]
-				x2 = world[ii + 7]
-				y2 = world[ii + 8]
-				tmpx = (x1 - cx1 * 2 + cx2) * 0.03
-				tmpy = (y1 - cy1 * 2 + cy2) * 0.03
-				dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006
-				dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.006
-				ddfx = tmpx * 2 + dddfx
-				ddfy = tmpy * 2 + dddfy
-				dfx = (cx1 - x1) * 0.3 + tmpx + dddfx * 0.16666667
-				dfy = (cy1 - y1) * 0.3 + tmpy + dddfy * 0.16666667
-				curveLength = math_sqrt(dfx * dfx + dfy * dfy)
-				segments[1] = curveLength
-				ii = 1
-				while ii < 8 do
-					dfx = dfx + ddfx
-					dfy = dfy + ddfy
-					ddfx = ddfx + dddfx
-					ddfy = ddfy + dddfy
-					curveLength = curveLength + math_sqrt(dfx * dfx + dfy * dfy)
-					segments[ii + 1] = curveLength
-					ii = ii + 1
-				end
-				dfx = dfx + ddfx
-				dfy = dfy + ddfy
-				curveLength = curveLength + math_sqrt(dfx * dfx + dfy * dfy)
-				segments[9] = curveLength
-				dfx = dfx + ddfx + dddfx
-				dfy = dfy + ddfy + dddfy
-				curveLength = curveLength + math_sqrt(dfx * dfx + dfy * dfy)
-				segments[10] = curveLength
-				segment = 0
-			end
-
-			-- Weight by segment length.
-			p = p * curveLength
-			while true do
-				local length = segments[segment + 1]
-				if p <= length then
-					if segment == 0 then
-						p = p / length
-					else
-						local prev = segments[segment - 1 + 1]
-						p = segment + (p - prev) / (length - prev)
-					end
-					break;
-				end
-				segment = segment + 1
-			end
-			self:addCurvePosition(p * 0.1, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents or (i > 0 and space == 0))
-		end
-
-		i = i + 1
-		o = o + 3
-	end
-	return out
-end
-
-function PathConstraint:addBeforePosition (p, temp, i, out, o)
-	local x1 = temp[i + 1]
-	local y1 = temp[i + 2]
-	local dx = temp[i + 3] - x1
-	local dy = temp[i + 4] - y1
-	local r = math_atan2(dy, dx)
-	out[o + 1] = x1 + p * math_cos(r)
-	out[o + 2] = y1 + p * math_sin(r)
-	out[o + 3] = r
-end
-
-function PathConstraint:addAfterPosition(p, temp, i, out, o)
-	local x1 = temp[i + 3]
-	local y1 = temp[i + 4]
-	local dx = x1 - temp[i + 1]
-	local dy = y1 - temp[i + 2]
-	local r = math_atan2(dy, dx)
-	out[o + 1] = x1 + p * math_cos(r)
-	out[o + 2] = y1 + p * math_sin(r)
-	out[o + 3] = r
-end
-
-function PathConstraint:addCurvePosition(p, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents)
-	if p == 0 or (p ~= p) then
-		out[o + 1] = x1
-		out[o + 2] = y1
-		out[o + 3] = math_atan2(cy1 - y1, cx1 - x1)
-		return;
-	end
-	local tt = p * p
-	local ttt = tt * p
-	local u = 1 - p
-	local uu = u * u
-	local uuu = uu * u
-	local ut = u * p
-	local ut3 = ut * 3
-	local uut3 = u * ut3
-	local utt3 = ut3 * p
-	local x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt
-	local y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt
-	out[o + 1] = x
-	out[o + 2] = y
-	if tangents then
-		if p < 0.001 then
-			out[o + 3] = math_atan2(cy1 - y1, cx1 - x1)
-		else
-			out[o + 3] = math_atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt))
-		end
-	end
-end
-
-return PathConstraint
+-------------------------------------------------------------------------------
+-- 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 the logic in this file uses 0-based indexing. Each array
+-- access adds 1 to the calculated index. We should switch the logic
+-- to 1-based indexing eventually.
+
+local setmetatable = setmetatable
+local AttachmentType = require "spine-lua.attachments.AttachmentType"
+local PathConstraintData = require "spine-lua.PathConstraintData"
+local utils = require "spine-lua.utils"
+local math_pi = math.pi
+local math_pi2 = math.pi * 2
+local math_atan2 = math.atan2
+local math_sqrt = math.sqrt
+local math_acos = math.acos
+local math_sin = math.sin
+local math_cos = math.cos
+local table_insert = table.insert
+local math_deg = math.deg
+local math_rad = math.rad
+local math_abs = math.abs
+local math_max = math.max
+
+local PathConstraint = {}
+PathConstraint.__index = PathConstraint
+
+PathConstraint.NONE = -1
+PathConstraint.BEFORE = -2
+PathConstraint.AFTER = -3
+PathConstraint.epsilon = 0.00001
+
+function PathConstraint.new (data, skeleton)
+	if not data then error("data cannot be nil", 2) end
+	if not skeleton then error("skeleton cannot be nil", 2) end
+
+	local self = {
+		data = data,
+		bones = {},
+		target = skeleton:findSlot(data.target.name),
+		position = data.position,
+		spacing = data.spacing,
+		rotateMix = data.rotateMix,
+		translateMix = data.translateMix,
+		spaces = {},
+		positions = {},
+		world = {},
+		curves = {},
+		lengths = {},
+		segments = {},
+		active = false
+	}
+	setmetatable(self, PathConstraint)
+
+	for _,boneData in ipairs(data.bones) do
+		table_insert(self.bones, skeleton:findBone(boneData.name))
+	end
+
+	return self
+end
+
+function PathConstraint:apply ()
+	self:update()
+end
+
+function PathConstraint:update ()
+	local attachment = self.target.attachment
+	if not attachment or not (attachment.type == AttachmentType.path) then return end
+
+	local rotateMix = self.rotateMix
+	local translateMix = self.translateMix
+	local translate = translateMix > 0
+	local rotate = rotateMix > 0
+	if not translate and not rotate then return end
+
+	local data = self.data;
+	local percentSpacing = data.spacingMode == PathConstraintData.SpacingMode.percent
+	local rotateMode = data.rotateMode
+	local tangents = rotateMode == PathConstraintData.RotateMode.tangent
+	local scale = rotateMode == PathConstraintData.RotateMode.chainscale
+	local bones = self.bones
+	local boneCount = #bones
+	local spacesCount = boneCount + 1
+	if tangents then spacesCount = boneCount end
+	local spaces = utils.setArraySize(self.spaces, spacesCount)
+	local lengths = nil
+	local spacing = self.spacing
+	if scale or not percentSpacing then
+		if scale then lengths = utils.setArraySize(self.lengths, boneCount) end
+		local lengthSpacing = data.spacingMode == PathConstraintData.SpacingMode.length
+		local i = 0
+		local n = spacesCount - 1
+		while i < n do
+			local bone = bones[i + 1];
+			local setupLength = bone.data.length
+			if setupLength < PathConstraint.epsilon then
+				if scale then lengths[i + 1] = 0 end
+				i = i + 1
+				spaces[i + 1] = 0
+			elseif percentSpacing then
+				if scale then
+					local x = setupLength * bone.a
+					local y = setupLength * bone.c
+					local length = math_sqrt(x * x + y * y)
+					lengths[i + 1] = length
+				end
+				i = i + 1
+				spaces[i + 1] = spacing
+			else
+	 			local x = setupLength * bone.a
+				local y = setupLength * bone.c
+				local length = math_sqrt(x * x + y * y)
+				if scale then lengths[i + 1] = length end
+				i = i + 1
+				if lengthSpacing then
+					spaces[i + 1] = (setupLength + spacing) * length / setupLength
+				else
+					spaces[i + 1] = spacing * length / setupLength
+				end
+			end
+		end
+	else
+		local i = 1
+		while i < spacesCount do
+			spaces[i + 1] = spacing
+			i = i + 1
+		end
+	end
+
+	local positions = self:computeWorldPositions(attachment, spacesCount, tangents, data.positionMode == PathConstraintData.PositionMode.percent, percentSpacing)
+	local boneX = positions[1]
+	local boneY = positions[2]
+	local offsetRotation = data.offsetRotation
+	local tip = false;
+	if offsetRotation == 0 then
+			tip = rotateMode == PathConstraintData.RotateMode.chain
+	else
+		tip = false;
+		local p = self.target.bone;
+		if p.a * p.d - p.b * p.c > 0 then
+			offsetRotation = offsetRotation * utils.degRad
+		else
+			offsetRotation = offsetRotation * -utils.degRad
+		end
+	end
+
+	local i = 0
+	local p = 3
+	while i < boneCount do
+		local bone = bones[i + 1]
+		bone.worldX = bone.worldX + (boneX - bone.worldX) * translateMix
+		bone.worldY = bone.worldY + (boneY - bone.worldY) * translateMix
+		local x = positions[p + 1]
+		local y = positions[p + 2]
+		local dx = x - boneX
+		local dy = y - boneY
+		if scale then
+			local length = lengths[i + 1]
+			if length ~= 0 then
+				local s = (math_sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1
+				bone.a = bone.a * s
+				bone.c = bone.c * s
+			end
+		end
+		boneX = x
+		boneY = y
+		if rotate then
+			local a = bone.a
+			local b = bone.b
+			local c = bone.c
+			local d = bone.d
+			local r = 0
+			local cos = 0
+			local sin = 0
+			if tangents then
+				r = positions[p - 1 + 1]
+			elseif spaces[i + 1 + 1] == 0 then
+				r = positions[p + 2 + 1]
+			else
+				r = math_atan2(dy, dx)
+			end
+			r = r - math_atan2(c, a)
+			if tip then
+				cos = math_cos(r)
+				sin = math_sin(r)
+				local length = bone.data.length
+				boneX = boneX + (length * (cos * a - sin * c) - dx) * rotateMix;
+				boneY = boneY + (length * (sin * a + cos * c) - dy) * rotateMix;
+			else
+				r = r + offsetRotation
+			end
+			if r > math_pi then
+				r = r - math_pi2
+			elseif r < -math_pi then
+				r = r + math_pi2
+			end
+			r = r * rotateMix
+			cos = math_cos(r)
+			sin = math.sin(r)
+			bone.a = cos * a - sin * c
+			bone.b = cos * b - sin * d
+			bone.c = sin * a + cos * c
+			bone.d = sin * b + cos * d
+		end
+		bone.appliedValid = false
+		i = i + 1
+		p = p + 3
+	end
+end
+
+function PathConstraint:computeWorldPositions (path, spacesCount, tangents, percentPosition, percentSpacing)
+	local target = self.target
+	local position = self.position
+	local spaces = self.spaces
+	local out = utils.setArraySize(self.positions, spacesCount * 3 + 2)
+	local world = nil
+	local closed = path.closed
+	local verticesLength = path.worldVerticesLength
+	local curveCount = verticesLength / 6
+	local prevCurve = PathConstraint.NONE
+	local i = 0
+
+	if not path.constantSpeed then
+		local lengths = path.lengths
+		if closed then curveCount = curveCount - 1 else curveCount = curveCount - 2 end
+		local pathLength = lengths[curveCount + 1];
+		if percentPosition then position = position * pathLength end
+		if percentSpacing then
+			i = 1
+			while i < spacesCount do
+				spaces[i + 1] = spaces[i + 1] * pathLength
+				i = i + 1
+			end
+		end
+		world = utils.setArraySize(self.world, 8);
+		i = 0
+		local o = 0
+		local curve = 0
+		while i < spacesCount do
+			local space = spaces[i + 1];
+			position = position + space
+			local p = position
+
+			local skip = false
+			if closed then
+				p = p % pathLength
+				if p < 0 then p = p + pathLength end
+				curve = 0
+			elseif p < 0 then
+				if prevCurve ~= PathConstraint.BEFORE then
+					prevCurve = PathConstraint.BEFORE
+					path:computeWorldVertices(target, 2, 4, world, 0, 2)
+				end
+				self:addBeforePosition(p, world, 0, out, o)
+				skip = true
+			elseif p > pathLength then
+				if prevCurve ~= PathConstraint.AFTER then
+					prevCurve = PathConstraint.AFTER
+					path:computeWorldVertices(target, verticesLength - 6, 4, world, 0, 2)
+				end
+				self:addAfterPosition(p - pathLength, world, 0, out, o)
+				skip = true
+			end
+
+			if not skip then
+				-- Determine curve containing position.
+				while true do
+					local length = lengths[curve + 1]
+					if p <= length then
+						if curve == 0 then
+							p = p / length
+						else
+							local prev = lengths[curve - 1 + 1]
+							p = (p - prev) / (length - prev)
+						end
+						break
+					end
+					curve = curve + 1
+				end
+				if curve ~= prevCurve then
+					prevCurve = curve
+					if closed and curve == curveCount then
+						path:computeWorldVertices(target, verticesLength - 4, 4, world, 0, 2)
+						path:computeWorldVertices(target, 0, 4, world, 4, 2)
+					else
+						path:computeWorldVertices(target, curve * 6 + 2, 8, world, 0, 2)
+					end
+				end
+				self:addCurvePosition(p, world[1], world[2], world[3], world[4], world[5], world[6], world[7], world[8], out, o, tangents or (i > 0 and space == 0))
+			end
+
+			i = i + 1
+			o = o + 3
+		end
+		return out
+	end
+
+	-- World vertices.
+	if closed then
+		verticesLength = verticesLength + 2
+		world = utils.setArraySize(self.world, verticesLength)
+		path:computeWorldVertices(target, 2, verticesLength - 4, world, 0, 2)
+		path:computeWorldVertices(target, 0, 2, world, verticesLength - 4, 2)
+		world[verticesLength - 2 + 1] = world[0 + 1]
+		world[verticesLength - 1 + 1] = world[1 + 1]
+	else
+		curveCount = curveCount - 1
+		verticesLength = verticesLength - 4;
+		world = utils.setArraySize(self.world, verticesLength)
+		path:computeWorldVertices(target, 2, verticesLength, world, 0, 2)
+	end
+
+	-- Curve lengths.
+	local curves = utils.setArraySize(self.curves, curveCount)
+	local pathLength = 0;
+	local x1 = world[0 + 1]
+	local y1 = world[1 + 1]
+	local cx1 = 0
+	local cy1 = 0
+	local cx2 = 0
+	local cy2 = 0
+	local x2 = 0
+	local y2 = 0
+	local tmpx = 0
+	local tmpy = 0
+	local dddfx = 0
+	local dddfy = 0
+	local ddfx = 0
+	local ddfy = 0
+	local dfx = 0
+	local dfy = 0
+	local w = 2
+	while i < curveCount do
+		cx1 = world[w + 1]
+		cy1 = world[w + 2]
+		cx2 = world[w + 3]
+		cy2 = world[w + 4]
+		x2 = world[w + 5]
+		y2 = world[w + 6]
+		tmpx = (x1 - cx1 * 2 + cx2) * 0.1875
+		tmpy = (y1 - cy1 * 2 + cy2) * 0.1875
+		dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375
+		dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375
+		ddfx = tmpx * 2 + dddfx
+		ddfy = tmpy * 2 + dddfy
+		dfx = (cx1 - x1) * 0.75 + tmpx + dddfx * 0.16666667
+		dfy = (cy1 - y1) * 0.75 + tmpy + dddfy * 0.16666667
+		pathLength = pathLength + math_sqrt(dfx * dfx + dfy * dfy)
+		dfx = dfx + ddfx
+		dfy = dfy + ddfy
+		ddfx = ddfx + dddfx
+		ddfy = ddfy + dddfy
+		pathLength = pathLength + math_sqrt(dfx * dfx + dfy * dfy)
+		dfx = dfx + ddfx
+		dfy = dfy + ddfy
+		pathLength = pathLength + math_sqrt(dfx * dfx + dfy * dfy)
+		dfx = dfx + ddfx + dddfx
+		dfy = dfy + ddfy + dddfy
+		pathLength = pathLength + math_sqrt(dfx * dfx + dfy * dfy)
+		curves[i + 1] = pathLength
+		x1 = x2
+		y1 = y2
+		i = i + 1
+		w = w + 6
+	end
+	if percentPosition then
+		position = position * pathLength
+	else
+		position = position * pathLength / path.lengths[curveCount];
+	end
+	if percentSpacing then
+		i = 1
+		while i < spacesCount do
+			spaces[i + 1] = spaces[i + 1] * pathLength
+			i = i + 1
+		end
+	end
+
+	local segments = self.segments
+	local curveLength = 0
+	i = 0
+	local o = 0
+	local curve = 0
+	local segment = 0
+	while i < spacesCount do
+		local space = spaces[i + 1]
+		position = position + space
+		local p = position
+
+		local skip = false
+		if closed then
+			p = p % pathLength
+			if p < 0 then p = p + pathLength end
+			curve = 0
+		elseif p < 0 then
+			self:addBeforePosition(p, world, 0, out, o)
+			skip = true
+		elseif p > pathLength then
+			self:addAfterPosition(p - pathLength, world, verticesLength - 4, out, o)
+			skip = true
+		end
+
+		if not skip then
+			-- Determine curve containing position.
+			while true do
+				local length = curves[curve + 1]
+				if p <= length then
+					if curve == 0 then
+						p = p / length
+					else
+						local prev = curves[curve - 1 + 1]
+						p = (p - prev) / (length - prev)
+					end
+					break
+				end
+				curve = curve + 1
+			end
+
+			-- Curve segment lengths.
+			if curve ~= prevCurve then
+				prevCurve = curve
+				local ii = curve * 6
+				x1 = world[ii + 1]
+				y1 = world[ii + 2]
+				cx1 = world[ii + 3]
+				cy1 = world[ii + 4]
+				cx2 = world[ii + 5]
+				cy2 = world[ii + 6]
+				x2 = world[ii + 7]
+				y2 = world[ii + 8]
+				tmpx = (x1 - cx1 * 2 + cx2) * 0.03
+				tmpy = (y1 - cy1 * 2 + cy2) * 0.03
+				dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006
+				dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.006
+				ddfx = tmpx * 2 + dddfx
+				ddfy = tmpy * 2 + dddfy
+				dfx = (cx1 - x1) * 0.3 + tmpx + dddfx * 0.16666667
+				dfy = (cy1 - y1) * 0.3 + tmpy + dddfy * 0.16666667
+				curveLength = math_sqrt(dfx * dfx + dfy * dfy)
+				segments[1] = curveLength
+				ii = 1
+				while ii < 8 do
+					dfx = dfx + ddfx
+					dfy = dfy + ddfy
+					ddfx = ddfx + dddfx
+					ddfy = ddfy + dddfy
+					curveLength = curveLength + math_sqrt(dfx * dfx + dfy * dfy)
+					segments[ii + 1] = curveLength
+					ii = ii + 1
+				end
+				dfx = dfx + ddfx
+				dfy = dfy + ddfy
+				curveLength = curveLength + math_sqrt(dfx * dfx + dfy * dfy)
+				segments[9] = curveLength
+				dfx = dfx + ddfx + dddfx
+				dfy = dfy + ddfy + dddfy
+				curveLength = curveLength + math_sqrt(dfx * dfx + dfy * dfy)
+				segments[10] = curveLength
+				segment = 0
+			end
+
+			-- Weight by segment length.
+			p = p * curveLength
+			while true do
+				local length = segments[segment + 1]
+				if p <= length then
+					if segment == 0 then
+						p = p / length
+					else
+						local prev = segments[segment - 1 + 1]
+						p = segment + (p - prev) / (length - prev)
+					end
+					break;
+				end
+				segment = segment + 1
+			end
+			self:addCurvePosition(p * 0.1, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents or (i > 0 and space == 0))
+		end
+
+		i = i + 1
+		o = o + 3
+	end
+	return out
+end
+
+function PathConstraint:addBeforePosition (p, temp, i, out, o)
+	local x1 = temp[i + 1]
+	local y1 = temp[i + 2]
+	local dx = temp[i + 3] - x1
+	local dy = temp[i + 4] - y1
+	local r = math_atan2(dy, dx)
+	out[o + 1] = x1 + p * math_cos(r)
+	out[o + 2] = y1 + p * math_sin(r)
+	out[o + 3] = r
+end
+
+function PathConstraint:addAfterPosition(p, temp, i, out, o)
+	local x1 = temp[i + 3]
+	local y1 = temp[i + 4]
+	local dx = x1 - temp[i + 1]
+	local dy = y1 - temp[i + 2]
+	local r = math_atan2(dy, dx)
+	out[o + 1] = x1 + p * math_cos(r)
+	out[o + 2] = y1 + p * math_sin(r)
+	out[o + 3] = r
+end
+
+function PathConstraint:addCurvePosition(p, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents)
+	if p == 0 or (p ~= p) then
+		out[o + 1] = x1
+		out[o + 2] = y1
+		out[o + 3] = math_atan2(cy1 - y1, cx1 - x1)
+		return;
+	end
+	local tt = p * p
+	local ttt = tt * p
+	local u = 1 - p
+	local uu = u * u
+	local uuu = uu * u
+	local ut = u * p
+	local ut3 = ut * 3
+	local uut3 = u * ut3
+	local utt3 = ut3 * p
+	local x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt
+	local y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt
+	out[o + 1] = x
+	out[o + 2] = y
+	if tangents then
+		if p < 0.001 then
+			out[o + 3] = math_atan2(cy1 - y1, cx1 - x1)
+		else
+			out[o + 3] = math_atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt))
+		end
+	end
+end
+
+return PathConstraint

+ 70 - 70
spine-lua/PathConstraintData.lua → spine-lua/spine-lua/PathConstraintData.lua

@@ -1,70 +1,70 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local PathConstraintData = {}
-function PathConstraintData.new (name)
-	if not name then error("name cannot be nil", 2) end
-
-	local self = {
-		name = name,
-		order = 0,
-		skinRequired = false,
-		bones = {},
-		target = nil,
-		positionMode = nil,
-		spacingMode = nil,
-		rotateMode = nil,
-		offsetRotation = 0,
-		position = 0,
-		spacing = 0,
-		rotateMix = 0,
-		translateMix = 0
-	}
-
-	return self
-end
-
-PathConstraintData.PositionMode = {
-	fixed = 0,
-	percent = 1
-}
-
-PathConstraintData.SpacingMode = {
-	length = 0,
-	fixed = 1,
-	percent = 2
-}
-
-PathConstraintData.RotateMode = {
-	tangent = 0,
-	chain = 1,
-	chainscale = 2
-}
-
-return PathConstraintData
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local PathConstraintData = {}
+function PathConstraintData.new (name)
+	if not name then error("name cannot be nil", 2) end
+
+	local self = {
+		name = name,
+		order = 0,
+		skinRequired = false,
+		bones = {},
+		target = nil,
+		positionMode = nil,
+		spacingMode = nil,
+		rotateMode = nil,
+		offsetRotation = 0,
+		position = 0,
+		spacing = 0,
+		rotateMix = 0,
+		translateMix = 0
+	}
+
+	return self
+end
+
+PathConstraintData.PositionMode = {
+	fixed = 0,
+	percent = 1
+}
+
+PathConstraintData.SpacingMode = {
+	length = 0,
+	fixed = 1,
+	percent = 2
+}
+
+PathConstraintData.RotateMode = {
+	tangent = 0,
+	chain = 1,
+	chainscale = 2
+}
+
+return PathConstraintData

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

@@ -1,100 +1,100 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local AttachmentType = require "spine-lua.AttachmentType"
-
-local RegionAttachment = {}
-function RegionAttachment.new (name)
-	if not name then error("name cannot be nil", 2) end
-
-	local self = {
-		name = name,
-		type = AttachmentType.region,
-		x = 0, y = 0,
-		rotation = 0,
-		scaleX = 1, scaleY = 1,
-		width = 0, height = 0,
-		offset = {},
-		uvs = {},
-		r = 1, g = 1, b = 1, a = 1,
-		path = nil,
-		rendererObject = nil,
-		regionOffsetX = 0, regionOffsetY = 0,
-		regionWidth = 0, regionHeight = 0,
-		regionOriginalWidth = 0, regionOriginalHeight = 0
-	}
-
-	function self:updateOffset ()
-		local regionScaleX = self.width / self.regionOriginalWidth * self.scaleX
-		local regionScaleY = self.height / self.regionOriginalHeight * self.scaleY
-		local localX = -self.width / 2 * self.scaleX + self.regionOffsetX * regionScaleX
-		local localY = -self.height / 2 * self.scaleY + self.regionOffsetY * regionScaleY
-		local localX2 = localX + self.regionWidth * regionScaleX
-		local localY2 = localY + self.regionHeight * regionScaleY
-		local radians = self.rotation * math.pi / 180
-		local cos = math.cos(radians)
-		local sin = math.sin(radians)
-		local localXCos = localX * cos + self.x
-		local localXSin = localX * sin
-		local localYCos = localY * cos + self.y
-		local localYSin = localY * sin
-		local localX2Cos = localX2 * cos + self.x
-		local localX2Sin = localX2 * sin
-		local localY2Cos = localY2 * cos + self.y
-		local localY2Sin = localY2 * sin
-		local offset = self.offset
-		offset[0] = localXCos - localYSin -- X1
-		offset[1] = localYCos + localXSin -- Y1
-		offset[2] = localXCos - localY2Sin -- X2
-		offset[3] = localY2Cos + localXSin -- Y2
-		offset[4] = localX2Cos - localY2Sin -- X3
-		offset[5] = localY2Cos + localX2Sin -- Y3
-		offset[6] = localX2Cos - localYSin -- X4
-		offset[7] = localYCos + localX2Sin -- Y4
-	end
-
-	function self:computeWorldVertices (x, y, bone, worldVertices)
-		x = x + bone.worldX
-		y = y + bone.worldY
-		local m00, m01, m10, m11 = bone.m00, bone.m01, bone.m10, bone.m11
-		local offset = self.offset
-		local vertices = self.vertices;
-		vertices[0] = offset[0] * m00 + offset[1] * m01 + x
-		vertices[1] = offset[0] * m10 + offset[1] * m11 + y
-		vertices[2] = offset[2] * m00 + offset[3] * m01 + x
-		vertices[3] = offset[2] * m10 + offset[3] * m11 + y
-		vertices[4] = offset[4] * m00 + offset[5] * m01 + x
-		vertices[5] = offset[4] * m10 + offset[5] * m11 + y
-		vertices[6] = offset[6] * m00 + offset[7] * m01 + x
-		vertices[7] = offset[6] * m10 + offset[7] * m11 + y
-	end
-
-	return self
-end
-return RegionAttachment
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local AttachmentType = require "spine-lua.AttachmentType"
+
+local RegionAttachment = {}
+function RegionAttachment.new (name)
+	if not name then error("name cannot be nil", 2) end
+
+	local self = {
+		name = name,
+		type = AttachmentType.region,
+		x = 0, y = 0,
+		rotation = 0,
+		scaleX = 1, scaleY = 1,
+		width = 0, height = 0,
+		offset = {},
+		uvs = {},
+		r = 1, g = 1, b = 1, a = 1,
+		path = nil,
+		rendererObject = nil,
+		regionOffsetX = 0, regionOffsetY = 0,
+		regionWidth = 0, regionHeight = 0,
+		regionOriginalWidth = 0, regionOriginalHeight = 0
+	}
+
+	function self:updateOffset ()
+		local regionScaleX = self.width / self.regionOriginalWidth * self.scaleX
+		local regionScaleY = self.height / self.regionOriginalHeight * self.scaleY
+		local localX = -self.width / 2 * self.scaleX + self.regionOffsetX * regionScaleX
+		local localY = -self.height / 2 * self.scaleY + self.regionOffsetY * regionScaleY
+		local localX2 = localX + self.regionWidth * regionScaleX
+		local localY2 = localY + self.regionHeight * regionScaleY
+		local radians = self.rotation * math.pi / 180
+		local cos = math.cos(radians)
+		local sin = math.sin(radians)
+		local localXCos = localX * cos + self.x
+		local localXSin = localX * sin
+		local localYCos = localY * cos + self.y
+		local localYSin = localY * sin
+		local localX2Cos = localX2 * cos + self.x
+		local localX2Sin = localX2 * sin
+		local localY2Cos = localY2 * cos + self.y
+		local localY2Sin = localY2 * sin
+		local offset = self.offset
+		offset[0] = localXCos - localYSin -- X1
+		offset[1] = localYCos + localXSin -- Y1
+		offset[2] = localXCos - localY2Sin -- X2
+		offset[3] = localY2Cos + localXSin -- Y2
+		offset[4] = localX2Cos - localY2Sin -- X3
+		offset[5] = localY2Cos + localX2Sin -- Y3
+		offset[6] = localX2Cos - localYSin -- X4
+		offset[7] = localYCos + localX2Sin -- Y4
+	end
+
+	function self:computeWorldVertices (x, y, bone, worldVertices)
+		x = x + bone.worldX
+		y = y + bone.worldY
+		local m00, m01, m10, m11 = bone.m00, bone.m01, bone.m10, bone.m11
+		local offset = self.offset
+		local vertices = self.vertices;
+		vertices[0] = offset[0] * m00 + offset[1] * m01 + x
+		vertices[1] = offset[0] * m10 + offset[1] * m11 + y
+		vertices[2] = offset[2] * m00 + offset[3] * m01 + x
+		vertices[3] = offset[2] * m10 + offset[3] * m11 + y
+		vertices[4] = offset[4] * m00 + offset[5] * m01 + x
+		vertices[5] = offset[4] * m10 + offset[5] * m11 + y
+		vertices[6] = offset[6] * m00 + offset[7] * m01 + x
+		vertices[7] = offset[6] * m10 + offset[7] * m11 + y
+	end
+
+	return self
+end
+return RegionAttachment

+ 571 - 571
spine-lua/Skeleton.lua → spine-lua/spine-lua/Skeleton.lua

@@ -1,571 +1,571 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local utils = require "spine-lua.utils"
-local Bone = require "spine-lua.Bone"
-local Slot = require "spine-lua.Slot"
-local IkConstraint = require "spine-lua.IkConstraint"
-local PathConstraint = require "spine-lua.PathConstraint"
-local TransformConstraint = require "spine-lua.TransformConstraint"
-local AttachmentLoader = require "spine-lua.AttachmentLoader"
-local AttachmentType = require "spine-lua.attachments.AttachmentType"
-local Color = require "spine-lua.Color"
-
-local setmetatable = setmetatable
-local ipairs = ipairs
-local table_insert = table.insert
-local math_min = math.min
-local math_max = math.max
-
-local Skeleton = {}
-Skeleton.__index = Skeleton
-
-function Skeleton.new (data)
-	if not data then error("data cannot be nil", 2) end
-
-	local self = {
-		data = data,
-		bones = {},
-		slots = {},
-		slotsByName = {},
-		drawOrder = {},
-		ikConstraints = {},
-		transformConstraints = {},
-		pathConstraints = {},
-		_updateCache = {},
-		updateCacheReset = {},
-		skin = nil,
-		color = Color.newWith(1, 1, 1, 1),
-		time = 0,
-		scaleX = 1, scaleY = 1,
-		x = 0, y = 0
-	}
-	setmetatable(self, Skeleton)
-
-	for _,boneData in ipairs(data.bones) do
-		local bone = nil
-		if boneData.parent == nil then
-			bone = Bone.new(boneData, self, nil)
-		else
-			local parent = self.bones[boneData.parent.index]
-			bone = Bone.new(boneData, self, parent)
-			table_insert(parent.children, bone)
-		end
-		table_insert(self.bones, bone)
-	end
-
-	for _,slotData in ipairs(data.slots) do
-		local bone = self.bones[slotData.boneData.index]
-		local slot = Slot.new(slotData, bone)
-		table_insert(self.slots, slot)
-		self.slotsByName[slot.data.name] = slot
-		table_insert(self.drawOrder, slot)
-	end
-
-	for _, ikConstraintData in ipairs(data.ikConstraints) do
-		table_insert(self.ikConstraints, IkConstraint.new(ikConstraintData, self))
-	end
-
-	for _, transformConstraintData in ipairs(data.transformConstraints) do
-		table_insert(self.transformConstraints, TransformConstraint.new(transformConstraintData, self))
-	end
-
-	for _, pathConstraintData in ipairs(data.pathConstraints) do
-		table_insert(self.pathConstraints, PathConstraint.new(pathConstraintData, self))
-	end
-
-	self:updateCache()
-
-	return self
-end
-
-
--- Caches information about bones and IK constraints. Must be called if bones or IK constraints are added or removed.
-function Skeleton:updateCache ()
-	local updateCache = {}
-	self._updateCache = updateCache
-	self.updateCacheReset = {}
-
-	local bones = self.bones
-	for _, bone in ipairs(bones) do
-		bone.sorted = bone.data.skinRequired
-		bone.active = not bone.sorted
-	end
-
-	if self.skin then
-		local skinBones = self.skin.bones
-		for i, boneData in ipairs(skinBones) do
-			local bone = bones[boneData.index]
-			while bone do
-				bone.sorted = false
-				bone.active = true
-				bone = bone.parent
-			end
-		end
-	end
-
-	local ikConstraints = self.ikConstraints
-	local transformConstraints = self.transformConstraints
-	local pathConstraints = self.pathConstraints
-	local ikCount = #ikConstraints
-	local transformCount = #transformConstraints
-	local pathCount = #pathConstraints
-	local constraintCount = ikCount + transformCount + pathCount
-
-	local i = 0
-	while i < constraintCount do
-		local found = false
-		local ii = 1
-		while ii <= ikCount do
-			local constraint = ikConstraints[ii]
-			if constraint.data.order == i then
-				self:sortIkConstraint(constraint)
-				found = true
-				break
-			end
-			ii = ii + 1
-		end
-
-		if not found then
-			ii = 1
-			while ii <= transformCount do
-				local constraint = transformConstraints[ii]
-				if constraint.data.order == i then
-					self:sortTransformConstraint(constraint)
-					found = true
-					break
-				end
-				ii = ii + 1
-			end
-		end
-
-		if not found then
-			ii = 1
-			while ii <= pathCount do
-				local constraint = pathConstraints[ii]
-				if constraint.data.order == i then
-					self:sortPathConstraint(constraint)
-					break
-				end
-				ii = ii + 1
-			end
-		end
-
-		i = i + 1
-	end
-
-	for _, bone in ipairs(self.bones) do
-		self:sortBone(bone)
-	end
-end
-
-function Skeleton:sortIkConstraint (constraint)
-	constraint.active = constraint.target.active and ((not constraint.data.skinRequired) or (self.skin and utils.arrayContains(self.skin.constraints, constraint.data)))
-	if not constraint.active then return end
-
-	local target = constraint.target
-	self:sortBone(target)
-
-	local constrained = constraint.bones
-	local parent = constrained[1]
-	self:sortBone(parent)
-
-	if #constrained > 1 then
-		local child = constrained[#constrained]
-		local contains = false
-		for _, updatable in ipairs(self._updateCache) do
-			if updatable == child then
-				contains = true
-				break
-			end
-		end
-		if not contains then table_insert(self.updateCacheReset, child) end
-	end
-
-	table_insert(self._updateCache, constraint)
-
-	self:sortReset(parent.children)
-	constrained[#constrained].sorted = true
-end
-
-function Skeleton:sortPathConstraint(constraint)
-	constraint.active = constraint.target.bone.active and ((not constraint.data.skinRequired) or (self.skin and utils.arrayContains(self.skin.constraints, constraint.data)))
-	if not constraint.active then return end
-
-	local slot = constraint.target
-	local slotIndex = slot.data.index
-	local slotBone = slot.bone
-	if self.skin then self:sortPathConstraintAttachment(self.skin, slotIndex, slotBone) end
-	if self.data.defaultSkin and not (self.data.defaultSkin == self.skin) then
-		self:sortPathConstraintAttachment(self.data.defaultSkin, slotIndex, slotBone)
-	end
-	for _,skin in ipairs(self.data.skins) do
-		self:sortPathConstraintAttachment(skin, slotIndex, slotBone)
-	end
-
-	local attachment = slot.attachment
-	if attachment and attachment.type == AttachmentType.path then self:sortPathConstraintAttachmentWith(attachment, slotBone) end
-
-	local constrained = constraint.bones
-	for _,bone in ipairs(constrained) do
-		self:sortBone(bone)
-	end
-
-	table_insert(self._updateCache, constraint)
-
-	for _,bone in ipairs(constrained) do
-		self:sortReset(bone.children)
-	end
-
-	for _,bone in ipairs(constrained) do
-		bone.sorted = true
-	end
-end
-
-function Skeleton:sortTransformConstraint(constraint)
-	constraint.active = constraint.target.active and ((not constraint.data.skinRequired) or (self.skin and utils.arrayContains(self.skin.constraints, constraint.data)))
-	if not constraint.active then return end
-
-	self:sortBone(constraint.target)
-
-	local constrained = constraint.bones
-	if constraint.data.local_ then
-		for _,bone in ipairs(constrained) do
-			local child = constrained[#constrained]
-			local contains = false
-			self:sortBone(child.parent)
-			for _,updatable in ipairs(self._updateCache) do
-				if updatable == child then
-					contains = true
-					break
-				end
-			end
-			if not contains then table_insert(self.updateCacheReset, child) end
-		end
-	else
-		for _,bone in ipairs(constrained) do
-			self:sortBone(bone)
-		end
-	end
-
-	table_insert(self._updateCache, constraint)
-
-	for _,bone in ipairs(constrained) do
-		self:sortReset(bone.children)
-	end
-
-	for _,bone in ipairs(constrained) do
-		bone.sorted = true
-	end
-end
-
-function Skeleton:sortPathConstraintAttachment(skin, slotIndex, slotBone)
-	local attachments = skin.attachments[slotIndex]
-	if not attachments then return end
-	for _,attachment in pairs(attachments) do
-		self:sortPathConstraintAttachmentWith(attachment, slotBone)
-	end
-end
-
-function Skeleton:sortPathConstraintAttachmentWith(attachment, slotBone)
-	if attachment.type ~= AttachmentType.path then return end
-	local pathBones = attachment.bones
-	if not pathBones then
-		self:sortBone(slotBone)
-	else
-		local bones = self.bones
-		local i = 0
-		local n = #pathBones
-		while i < n do
-			local boneCount = pathBones[i + 1]
-			i = i + 1
-			local nn = i + boneCount
-			while i < nn do
-				self:sortBone(bones[pathBones[i + 1]])
-				i = i + 1
-			end
-		end
-	end
-end
-
-function Skeleton:sortBone(bone)
-	if bone.sorted then return end
-	local parent = bone.parent
-	if parent then self:sortBone(parent) end
-	bone.sorted = true
-	table_insert(self._updateCache, bone)
-end
-
-function Skeleton:sortReset(bones)
-	for _, bone in ipairs(bones) do
-		if bone.active then
-			if bone.sorted then self:sortReset(bone.children) end
-			bone.sorted = false
-		end
-	end
-end
-
--- Updates the world transform for each bone and applies IK constraints.
-function Skeleton:updateWorldTransform ()
-	local updateCacheReset = self.updateCacheReset
-	for _,bone in ipairs(updateCacheReset) do
-		bone.ax = bone.x
-		bone.ay = bone.y
-		bone.arotation = bone.rotation
-		bone.ascaleX = bone.scaleX
-		bone.ascaleY = bone.scaleY
-		bone.ashearX = bone.shearX
-		bone.ashearY = bone.shearY
-		bone.appliedValid = true
-	end
-
-	local updateCache = self._updateCache
-	for _, updatable in ipairs(updateCache) do
-		updatable:update()
-	end
-end
-
-function Skeleton:setToSetupPose ()
-	self:setBonesToSetupPose()
-	self:setSlotsToSetupPose()
-end
-
-function Skeleton:setBonesToSetupPose ()
-	for _,bone in ipairs(self.bones) do
-		bone:setToSetupPose()
-	end
-
-	for _,ikConstraint in ipairs(self.ikConstraints) do
-		ikConstraint.mix = ikConstraint.data.mix
-		ikConstraint.softness = ikConstraint.data.softness
-		ikConstraint.bendDirection = ikConstraint.data.bendDirection
-		ikConstraint.compress = ikConstraint.data.compress
-		ikConstraint.stretch = ikConstraint.data.stretch
-	end
-
-	local transformConstraints = self.transformConstraints
-	for _, constraint in ipairs(transformConstraints) do
-		local data = constraint.data
-		constraint.rotateMix = data.rotateMix
-		constraint.translateMix = data.translateMix
-		constraint.scaleMix = data.scaleMix
-		constraint.shearMix = data.shearMix
-	end
-
-	local pathConstraints = self.pathConstraints
-	for _, constraint in ipairs(pathConstraints) do
-		local data = constraint.data
-		constraint.position = data.position
-		constraint.spacing = data.spacing
-		constraint.rotateMix = data.rotateMix
-		constraint.translateMix = data.translateMix
-	end
-end
-
-function Skeleton:setSlotsToSetupPose ()
-	for i,slot in ipairs(self.slots) do
-		self.drawOrder[i] = slot
-		slot:setToSetupPose()
-	end
-end
-
-function Skeleton:getRootBone ()
-	return self.bones[1]
-end
-
-function Skeleton:findBone (boneName)
-	if not boneName then error("boneName cannot be nil.", 2) end
-	for _,bone in ipairs(self.bones) do
-		if bone.data.name == boneName then return bone end
-	end
-	return nil
-end
-
-function Skeleton:findBoneIndex(boneName)
-	if not boneName then error("boneName cannot be nil.", 2) end
-	for i,bone in ipairs(self.bones) do
-		if bone.data.name == boneName then return i end
-	end
-	return -1
-end
-
-function Skeleton:findSlot (slotName)
-	if not slotName then error("slotName cannot be nil.", 2) end
-	return self.slotsByName[slotName]
-end
-
-function Skeleton:findSlotIndex(slotName)
-	if not slotName then error("slotName cannot be nil.", 2) end
-	for i, slot in ipairs(self.slots) do
-		if slot.data.name == slotName then return i end
-	end
-	return -1
-end
-
--- Sets the skin used to look up attachments before looking in the {@link SkeletonData#getDefaultSkin() default skin}.
--- Attachments from the new skin are attached if the corresponding attachment from the old skin was attached. If there was
--- no old skin, each slot's setup mode attachment is attached from the new skin.
-function Skeleton:setSkin (skinName)
-	local skin = self.data:findSkin(skinName)
-	if not skin then error("Skin not found: " .. skinName, 2) end
-	self:setSkinByReference(skin)
-end
-
-function Skeleton:setSkinByReference(newSkin)
-	if (self.skin == newSkin) then return end
-	if newSkin then
-		if self.skin then
-			newSkin:attachAll(self, self.skin)
-		else
-			local slots = self.slots
-			for i, slot in ipairs(slots) do
-				local name = slot.data.attachmentName
-				if name then
-					local attachment = newSkin:getAttachment(i, name)
-					if attachment then
-						slot:setAttachment(attachment)
-					end
-				end
-			end
-		end
-	end
-	self.skin = newSkin
-	self:updateCache()
-end
-
-function Skeleton:getAttachment (slotName, attachmentName)
-	return self:getAttachmentByIndex(self.data.slotNameIndices[slotName], attachmentName)
-end
-
-function Skeleton:getAttachmentByIndex (slotIndex, attachmentName)
-	if self.skin then
-		local attachment = self.skin:getAttachment(slotIndex, attachmentName)
-		if attachment then return attachment end
-	end
-	if self.data.defaultSkin then
-		return self.data.defaultSkin:getAttachment(slotIndex, attachmentName)
-	end
-	return nil
-end
-
-function Skeleton:setAttachment (slotName, attachmentName)
-	if not slotName then error("slotName cannot be nil.", 2) end
-	for i,slot in ipairs(self.slots) do
-		if slot.data.name == slotName then
-			local attachment = nil
-			if attachmentName then
-				attachment = self:getAttachmentByIndex(i, attachmentName)
-				if not attachment then error("Attachment not found: " .. attachmentName .. ", for slot: " .. slotName, 2) end
-			end
-			slot:setAttachment(attachment)
-			return
-		end
-	end
-	error("Slot not found: " .. slotName, 2)
-end
-
-function Skeleton:findIkConstraint(constraintName)
-	if not constraintName then error("constraintName cannot be null.", 2) end
-	local ikConstraints = self.ikConstraints
-	for _, ikConstraint in ipairs(ikConstraints) do
-		if ikConstraint.data.name == constraintName then return ikConstraint end
-	end
-	return nil
-end
-
-function Skeleton:findTransformConstraint(constraintName)
-	if not constraintName then error("constraintName cannot be null.", 2) end
-	local transformConstraints = self.transformConstraints
-	for _, transformConstraint in ipairs(transformConstraints) do
-		if transformConstraint.data.name == constraintName then return transformConstraint end
-	end
-	return nil
-end
-
-function Skeleton:findPathConstraint(constraintName)
-	if not constraintName then error("constraintName cannot be null.", 2) end
-	local pathConstraints = self.pathConstraints
-	for _, pathConstraint in ipairs(pathConstraints) do
-		if pathConstraint.data.name == constraintName then return pathConstraint end
-	end
-	return nil
-end
-
-function Skeleton:getBounds(offset, size)
-	if not offset then error("offset cannot be null.", 2) end
-	if not size then error("size cannot be null.", 2) end
-	local drawOrder = self.drawOrder;
-	local minX = 99999999
-	local minY = 99999999
-	local maxX = -99999999
-	local maxY = -99999999
-	for _, slot in ipairs(drawOrder) do
-		if slot.bone.active then
-			local vertices = {}
-			local attachment = slot.attachment
-			if attachment then
-				if attachment.type == AttachmentType.region then
-					attachment:computeWorldVertices(slot.bone, vertices, 0, 2)
-				elseif attachment.type == AttachmentType.mesh then
-					attachment:computeWorldVertices(slot, 0, attachment.worldVerticesLength, vertices, 0, 2)
-				end
-			end
-			if #vertices > 0 then
-				local nn = #vertices
-				local ii = 1
-				while ii <= nn do
-					local x = vertices[ii]
-					local y = vertices[ii + 1]
-					minX = math_min(minX, x)
-					minY = math_min(minY, y)
-					maxX = math_max(maxX, x)
-					maxY = math_max(maxY, y)
-					ii = ii + 2
-				end
-			end
-		end
-	end
-	offset[1] = minX
-	offset[2] = minY
-	size[1] = maxX - minX
-	size[2] = maxY - minY
-end
-
-function Skeleton:update (delta)
-	self.time = self.time + delta
-end
-
-function Skeleton:setColor (r, g, b, a)
-	self.color.r = r
-	self.color.g = g
-	self.color.b = b
-	self.color.a = a
-end
-
-return Skeleton
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local utils = require "spine-lua.utils"
+local Bone = require "spine-lua.Bone"
+local Slot = require "spine-lua.Slot"
+local IkConstraint = require "spine-lua.IkConstraint"
+local PathConstraint = require "spine-lua.PathConstraint"
+local TransformConstraint = require "spine-lua.TransformConstraint"
+local AttachmentLoader = require "spine-lua.AttachmentLoader"
+local AttachmentType = require "spine-lua.attachments.AttachmentType"
+local Color = require "spine-lua.Color"
+
+local setmetatable = setmetatable
+local ipairs = ipairs
+local table_insert = table.insert
+local math_min = math.min
+local math_max = math.max
+
+local Skeleton = {}
+Skeleton.__index = Skeleton
+
+function Skeleton.new (data)
+	if not data then error("data cannot be nil", 2) end
+
+	local self = {
+		data = data,
+		bones = {},
+		slots = {},
+		slotsByName = {},
+		drawOrder = {},
+		ikConstraints = {},
+		transformConstraints = {},
+		pathConstraints = {},
+		_updateCache = {},
+		updateCacheReset = {},
+		skin = nil,
+		color = Color.newWith(1, 1, 1, 1),
+		time = 0,
+		scaleX = 1, scaleY = 1,
+		x = 0, y = 0
+	}
+	setmetatable(self, Skeleton)
+
+	for _,boneData in ipairs(data.bones) do
+		local bone = nil
+		if boneData.parent == nil then
+			bone = Bone.new(boneData, self, nil)
+		else
+			local parent = self.bones[boneData.parent.index]
+			bone = Bone.new(boneData, self, parent)
+			table_insert(parent.children, bone)
+		end
+		table_insert(self.bones, bone)
+	end
+
+	for _,slotData in ipairs(data.slots) do
+		local bone = self.bones[slotData.boneData.index]
+		local slot = Slot.new(slotData, bone)
+		table_insert(self.slots, slot)
+		self.slotsByName[slot.data.name] = slot
+		table_insert(self.drawOrder, slot)
+	end
+
+	for _, ikConstraintData in ipairs(data.ikConstraints) do
+		table_insert(self.ikConstraints, IkConstraint.new(ikConstraintData, self))
+	end
+
+	for _, transformConstraintData in ipairs(data.transformConstraints) do
+		table_insert(self.transformConstraints, TransformConstraint.new(transformConstraintData, self))
+	end
+
+	for _, pathConstraintData in ipairs(data.pathConstraints) do
+		table_insert(self.pathConstraints, PathConstraint.new(pathConstraintData, self))
+	end
+
+	self:updateCache()
+
+	return self
+end
+
+
+-- Caches information about bones and IK constraints. Must be called if bones or IK constraints are added or removed.
+function Skeleton:updateCache ()
+	local updateCache = {}
+	self._updateCache = updateCache
+	self.updateCacheReset = {}
+
+	local bones = self.bones
+	for _, bone in ipairs(bones) do
+		bone.sorted = bone.data.skinRequired
+		bone.active = not bone.sorted
+	end
+
+	if self.skin then
+		local skinBones = self.skin.bones
+		for i, boneData in ipairs(skinBones) do
+			local bone = bones[boneData.index]
+			while bone do
+				bone.sorted = false
+				bone.active = true
+				bone = bone.parent
+			end
+		end
+	end
+
+	local ikConstraints = self.ikConstraints
+	local transformConstraints = self.transformConstraints
+	local pathConstraints = self.pathConstraints
+	local ikCount = #ikConstraints
+	local transformCount = #transformConstraints
+	local pathCount = #pathConstraints
+	local constraintCount = ikCount + transformCount + pathCount
+
+	local i = 0
+	while i < constraintCount do
+		local found = false
+		local ii = 1
+		while ii <= ikCount do
+			local constraint = ikConstraints[ii]
+			if constraint.data.order == i then
+				self:sortIkConstraint(constraint)
+				found = true
+				break
+			end
+			ii = ii + 1
+		end
+
+		if not found then
+			ii = 1
+			while ii <= transformCount do
+				local constraint = transformConstraints[ii]
+				if constraint.data.order == i then
+					self:sortTransformConstraint(constraint)
+					found = true
+					break
+				end
+				ii = ii + 1
+			end
+		end
+
+		if not found then
+			ii = 1
+			while ii <= pathCount do
+				local constraint = pathConstraints[ii]
+				if constraint.data.order == i then
+					self:sortPathConstraint(constraint)
+					break
+				end
+				ii = ii + 1
+			end
+		end
+
+		i = i + 1
+	end
+
+	for _, bone in ipairs(self.bones) do
+		self:sortBone(bone)
+	end
+end
+
+function Skeleton:sortIkConstraint (constraint)
+	constraint.active = constraint.target.active and ((not constraint.data.skinRequired) or (self.skin and utils.arrayContains(self.skin.constraints, constraint.data)))
+	if not constraint.active then return end
+
+	local target = constraint.target
+	self:sortBone(target)
+
+	local constrained = constraint.bones
+	local parent = constrained[1]
+	self:sortBone(parent)
+
+	if #constrained > 1 then
+		local child = constrained[#constrained]
+		local contains = false
+		for _, updatable in ipairs(self._updateCache) do
+			if updatable == child then
+				contains = true
+				break
+			end
+		end
+		if not contains then table_insert(self.updateCacheReset, child) end
+	end
+
+	table_insert(self._updateCache, constraint)
+
+	self:sortReset(parent.children)
+	constrained[#constrained].sorted = true
+end
+
+function Skeleton:sortPathConstraint(constraint)
+	constraint.active = constraint.target.bone.active and ((not constraint.data.skinRequired) or (self.skin and utils.arrayContains(self.skin.constraints, constraint.data)))
+	if not constraint.active then return end
+
+	local slot = constraint.target
+	local slotIndex = slot.data.index
+	local slotBone = slot.bone
+	if self.skin then self:sortPathConstraintAttachment(self.skin, slotIndex, slotBone) end
+	if self.data.defaultSkin and not (self.data.defaultSkin == self.skin) then
+		self:sortPathConstraintAttachment(self.data.defaultSkin, slotIndex, slotBone)
+	end
+	for _,skin in ipairs(self.data.skins) do
+		self:sortPathConstraintAttachment(skin, slotIndex, slotBone)
+	end
+
+	local attachment = slot.attachment
+	if attachment and attachment.type == AttachmentType.path then self:sortPathConstraintAttachmentWith(attachment, slotBone) end
+
+	local constrained = constraint.bones
+	for _,bone in ipairs(constrained) do
+		self:sortBone(bone)
+	end
+
+	table_insert(self._updateCache, constraint)
+
+	for _,bone in ipairs(constrained) do
+		self:sortReset(bone.children)
+	end
+
+	for _,bone in ipairs(constrained) do
+		bone.sorted = true
+	end
+end
+
+function Skeleton:sortTransformConstraint(constraint)
+	constraint.active = constraint.target.active and ((not constraint.data.skinRequired) or (self.skin and utils.arrayContains(self.skin.constraints, constraint.data)))
+	if not constraint.active then return end
+
+	self:sortBone(constraint.target)
+
+	local constrained = constraint.bones
+	if constraint.data.local_ then
+		for _,bone in ipairs(constrained) do
+			local child = constrained[#constrained]
+			local contains = false
+			self:sortBone(child.parent)
+			for _,updatable in ipairs(self._updateCache) do
+				if updatable == child then
+					contains = true
+					break
+				end
+			end
+			if not contains then table_insert(self.updateCacheReset, child) end
+		end
+	else
+		for _,bone in ipairs(constrained) do
+			self:sortBone(bone)
+		end
+	end
+
+	table_insert(self._updateCache, constraint)
+
+	for _,bone in ipairs(constrained) do
+		self:sortReset(bone.children)
+	end
+
+	for _,bone in ipairs(constrained) do
+		bone.sorted = true
+	end
+end
+
+function Skeleton:sortPathConstraintAttachment(skin, slotIndex, slotBone)
+	local attachments = skin.attachments[slotIndex]
+	if not attachments then return end
+	for _,attachment in pairs(attachments) do
+		self:sortPathConstraintAttachmentWith(attachment, slotBone)
+	end
+end
+
+function Skeleton:sortPathConstraintAttachmentWith(attachment, slotBone)
+	if attachment.type ~= AttachmentType.path then return end
+	local pathBones = attachment.bones
+	if not pathBones then
+		self:sortBone(slotBone)
+	else
+		local bones = self.bones
+		local i = 0
+		local n = #pathBones
+		while i < n do
+			local boneCount = pathBones[i + 1]
+			i = i + 1
+			local nn = i + boneCount
+			while i < nn do
+				self:sortBone(bones[pathBones[i + 1]])
+				i = i + 1
+			end
+		end
+	end
+end
+
+function Skeleton:sortBone(bone)
+	if bone.sorted then return end
+	local parent = bone.parent
+	if parent then self:sortBone(parent) end
+	bone.sorted = true
+	table_insert(self._updateCache, bone)
+end
+
+function Skeleton:sortReset(bones)
+	for _, bone in ipairs(bones) do
+		if bone.active then
+			if bone.sorted then self:sortReset(bone.children) end
+			bone.sorted = false
+		end
+	end
+end
+
+-- Updates the world transform for each bone and applies IK constraints.
+function Skeleton:updateWorldTransform ()
+	local updateCacheReset = self.updateCacheReset
+	for _,bone in ipairs(updateCacheReset) do
+		bone.ax = bone.x
+		bone.ay = bone.y
+		bone.arotation = bone.rotation
+		bone.ascaleX = bone.scaleX
+		bone.ascaleY = bone.scaleY
+		bone.ashearX = bone.shearX
+		bone.ashearY = bone.shearY
+		bone.appliedValid = true
+	end
+
+	local updateCache = self._updateCache
+	for _, updatable in ipairs(updateCache) do
+		updatable:update()
+	end
+end
+
+function Skeleton:setToSetupPose ()
+	self:setBonesToSetupPose()
+	self:setSlotsToSetupPose()
+end
+
+function Skeleton:setBonesToSetupPose ()
+	for _,bone in ipairs(self.bones) do
+		bone:setToSetupPose()
+	end
+
+	for _,ikConstraint in ipairs(self.ikConstraints) do
+		ikConstraint.mix = ikConstraint.data.mix
+		ikConstraint.softness = ikConstraint.data.softness
+		ikConstraint.bendDirection = ikConstraint.data.bendDirection
+		ikConstraint.compress = ikConstraint.data.compress
+		ikConstraint.stretch = ikConstraint.data.stretch
+	end
+
+	local transformConstraints = self.transformConstraints
+	for _, constraint in ipairs(transformConstraints) do
+		local data = constraint.data
+		constraint.rotateMix = data.rotateMix
+		constraint.translateMix = data.translateMix
+		constraint.scaleMix = data.scaleMix
+		constraint.shearMix = data.shearMix
+	end
+
+	local pathConstraints = self.pathConstraints
+	for _, constraint in ipairs(pathConstraints) do
+		local data = constraint.data
+		constraint.position = data.position
+		constraint.spacing = data.spacing
+		constraint.rotateMix = data.rotateMix
+		constraint.translateMix = data.translateMix
+	end
+end
+
+function Skeleton:setSlotsToSetupPose ()
+	for i,slot in ipairs(self.slots) do
+		self.drawOrder[i] = slot
+		slot:setToSetupPose()
+	end
+end
+
+function Skeleton:getRootBone ()
+	return self.bones[1]
+end
+
+function Skeleton:findBone (boneName)
+	if not boneName then error("boneName cannot be nil.", 2) end
+	for _,bone in ipairs(self.bones) do
+		if bone.data.name == boneName then return bone end
+	end
+	return nil
+end
+
+function Skeleton:findBoneIndex(boneName)
+	if not boneName then error("boneName cannot be nil.", 2) end
+	for i,bone in ipairs(self.bones) do
+		if bone.data.name == boneName then return i end
+	end
+	return -1
+end
+
+function Skeleton:findSlot (slotName)
+	if not slotName then error("slotName cannot be nil.", 2) end
+	return self.slotsByName[slotName]
+end
+
+function Skeleton:findSlotIndex(slotName)
+	if not slotName then error("slotName cannot be nil.", 2) end
+	for i, slot in ipairs(self.slots) do
+		if slot.data.name == slotName then return i end
+	end
+	return -1
+end
+
+-- Sets the skin used to look up attachments before looking in the {@link SkeletonData#getDefaultSkin() default skin}.
+-- Attachments from the new skin are attached if the corresponding attachment from the old skin was attached. If there was
+-- no old skin, each slot's setup mode attachment is attached from the new skin.
+function Skeleton:setSkin (skinName)
+	local skin = self.data:findSkin(skinName)
+	if not skin then error("Skin not found: " .. skinName, 2) end
+	self:setSkinByReference(skin)
+end
+
+function Skeleton:setSkinByReference(newSkin)
+	if (self.skin == newSkin) then return end
+	if newSkin then
+		if self.skin then
+			newSkin:attachAll(self, self.skin)
+		else
+			local slots = self.slots
+			for i, slot in ipairs(slots) do
+				local name = slot.data.attachmentName
+				if name then
+					local attachment = newSkin:getAttachment(i, name)
+					if attachment then
+						slot:setAttachment(attachment)
+					end
+				end
+			end
+		end
+	end
+	self.skin = newSkin
+	self:updateCache()
+end
+
+function Skeleton:getAttachment (slotName, attachmentName)
+	return self:getAttachmentByIndex(self.data.slotNameIndices[slotName], attachmentName)
+end
+
+function Skeleton:getAttachmentByIndex (slotIndex, attachmentName)
+	if self.skin then
+		local attachment = self.skin:getAttachment(slotIndex, attachmentName)
+		if attachment then return attachment end
+	end
+	if self.data.defaultSkin then
+		return self.data.defaultSkin:getAttachment(slotIndex, attachmentName)
+	end
+	return nil
+end
+
+function Skeleton:setAttachment (slotName, attachmentName)
+	if not slotName then error("slotName cannot be nil.", 2) end
+	for i,slot in ipairs(self.slots) do
+		if slot.data.name == slotName then
+			local attachment = nil
+			if attachmentName then
+				attachment = self:getAttachmentByIndex(i, attachmentName)
+				if not attachment then error("Attachment not found: " .. attachmentName .. ", for slot: " .. slotName, 2) end
+			end
+			slot:setAttachment(attachment)
+			return
+		end
+	end
+	error("Slot not found: " .. slotName, 2)
+end
+
+function Skeleton:findIkConstraint(constraintName)
+	if not constraintName then error("constraintName cannot be null.", 2) end
+	local ikConstraints = self.ikConstraints
+	for _, ikConstraint in ipairs(ikConstraints) do
+		if ikConstraint.data.name == constraintName then return ikConstraint end
+	end
+	return nil
+end
+
+function Skeleton:findTransformConstraint(constraintName)
+	if not constraintName then error("constraintName cannot be null.", 2) end
+	local transformConstraints = self.transformConstraints
+	for _, transformConstraint in ipairs(transformConstraints) do
+		if transformConstraint.data.name == constraintName then return transformConstraint end
+	end
+	return nil
+end
+
+function Skeleton:findPathConstraint(constraintName)
+	if not constraintName then error("constraintName cannot be null.", 2) end
+	local pathConstraints = self.pathConstraints
+	for _, pathConstraint in ipairs(pathConstraints) do
+		if pathConstraint.data.name == constraintName then return pathConstraint end
+	end
+	return nil
+end
+
+function Skeleton:getBounds(offset, size)
+	if not offset then error("offset cannot be null.", 2) end
+	if not size then error("size cannot be null.", 2) end
+	local drawOrder = self.drawOrder;
+	local minX = 99999999
+	local minY = 99999999
+	local maxX = -99999999
+	local maxY = -99999999
+	for _, slot in ipairs(drawOrder) do
+		if slot.bone.active then
+			local vertices = {}
+			local attachment = slot.attachment
+			if attachment then
+				if attachment.type == AttachmentType.region then
+					attachment:computeWorldVertices(slot.bone, vertices, 0, 2)
+				elseif attachment.type == AttachmentType.mesh then
+					attachment:computeWorldVertices(slot, 0, attachment.worldVerticesLength, vertices, 0, 2)
+				end
+			end
+			if #vertices > 0 then
+				local nn = #vertices
+				local ii = 1
+				while ii <= nn do
+					local x = vertices[ii]
+					local y = vertices[ii + 1]
+					minX = math_min(minX, x)
+					minY = math_min(minY, y)
+					maxX = math_max(maxX, x)
+					maxY = math_max(maxY, y)
+					ii = ii + 2
+				end
+			end
+		end
+	end
+	offset[1] = minX
+	offset[2] = minY
+	size[1] = maxX - minX
+	size[2] = maxY - minY
+end
+
+function Skeleton:update (delta)
+	self.time = self.time + delta
+end
+
+function Skeleton:setColor (r, g, b, a)
+	self.color.r = r
+	self.color.g = g
+	self.color.b = b
+	self.color.a = a
+end
+
+return Skeleton

+ 199 - 199
spine-lua/SkeletonBounds.lua → spine-lua/spine-lua/SkeletonBounds.lua

@@ -1,199 +1,199 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local AttachmentType = require "spine-lua.attachments.AttachmentType"
-local utils = require "spine-lua.utils"
-
-local setmetatable = setmetatable
-local math_min = math.min
-local math_max = math.max
-local ipairs = ipairs
-local table_insert = table.insert
-
-local SkeletonBounds = {}
-SkeletonBounds.__index = SkeletonBounds
-
-function SkeletonBounds.new ()
-	local self = {
-		minX = 0, minY = 0, maxX = 0, maxY = 0,
-		polygons = {},
-		boundingBoxes = {},
-	}
-	setmetatable(self, SkeletonBounds)
-
-	return self
-end
-
-function SkeletonBounds:update (skeleton, updateAabb)
-	if skeleton == nil then error("skeleton cannot be null", 2) end
-	local boundingBoxes = {}
-	self.boundingBoxes = boundingBoxes
-	local polygons = {}
-	self.polygons = polygons
-	local slots = skeleton.slots
-
-	for _,slot in ipairs(skeleton.slots) do
-		if (slot.bone.active) then
-			local attachment = slot.attachment
-			if attachment and attachment.type == AttachmentType.boundingbox then
-				local boundingBox = attachment
-				table_insert(boundingBoxes, boundingBox)
-
-				local polygon = {}
-				table_insert(polygons, polygon)
-
-				boundingBox:computeWorldVertices(slot, 0, boundingBox.worldVerticesLength, polygon, 0, 2)
-			end
-		end
-	end
-
-	if updateAabb then
-		self:aabbCompute()
-	else
-		self.minX = 9999999
-		self.minY = 9999999
-		self.maxX = -9999999
-		self.maxY = -9999999
-	end
-end
-
-function SkeletonBounds:aabbCompute ()
-	local minX, minY, maxX, maxY = 9999999, 9999999, -9999999, -9999999
-	local polygons = self.polygons
-	for _,vertices in ipairs(polygons) do
-		local count = #vertices
-		for ii = 1, count, 2 do
-			local x = vertices[ii]
-			local y = vertices[ii + 1]
-			minX = math_min(minX, x)
-			minY = math_min(minY, y)
-			maxX = math_max(maxX, x)
-			maxY = math_max(maxY, y)
-		end
-	end
-	self.minX = minX
-	self.minY = minY
-	self.maxX = maxX
-	self.maxY = maxY
-end
-
-function SkeletonBounds:aabbContainsPoint (x, y)
-	return x >= self.minX and x <= self.maxX and y >= self.minY and y <= self.maxY
-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
-	end
-	local m = (y2 - y1) / (x2 - x1)
-	local y = m * (minX - x1) + y1
-	if y > minY and y < maxY then return true end
-	y = m * (maxX - x1) + y1
-	if y > minY and y < maxY then return true end
-	local x = (minY - y1) / m + x1
-	if x > minX and x < maxX then return true end
-	x = (maxY - y1) / m + x1
-	if x > minX and x < maxX then return true end
-	return false
-end
-
-function SkeletonBounds:aabbIntersectsSkeleton (bounds)
-	return self.minX < bounds.maxX and self.maxX > bounds.minX and self.minY < bounds.maxY and self.maxY > bounds.minY
-end
-
-function SkeletonBounds:containsPoint (x, y)
-	for i,polygon in ipairs(self.polygons) do
-		if self:polygonContainsPoint(polygon, x, y) then return self.boundingBoxes[i] end
-	end
-	return nil
-end
-
-function SkeletonBounds:intersectsSegment (x1, y1, x2, y2)
-	for i,polygon in ipairs(self.polygons) do
-		if self:polygonIntersectsSegment(polygon, x1, y1, x2, y2) then return self.boundingBoxes[i] end
-	end
-	return nil
-end
-
-function SkeletonBounds:polygonContainsPoint (polygon, x, y)
-	local nn = #polygon
-	local prevIndex = nn - 1
-	local inside = false
-	for ii = 1, nn, 2 do
-		local vertexY = polygon[ii + 1]
-		local prevY = polygon[prevIndex + 1]
-		if (vertexY < y and prevY >= y) or (prevY < y and vertexY >= y) then
-			local vertexX = polygon[ii]
-			if vertexX + (y - vertexY) / (prevY - vertexY) * (polygon[prevIndex] - vertexX) < x then inside = not inside end
-		end
-		prevIndex = ii
-	end
-	return inside
-end
-
-function SkeletonBounds:polygonIntersectsSegment (polygon, x1, y1, x2, y2)
-	local nn = #polygon
-	local width12, height12 = x1 - x2, y1 - y2
-	local det1 = x1 * y2 - y1 * x2
-	local x3, y3 = polygon[nn - 2], polygon[nn - 1]
-	for ii = 1, nn, 2 do
-		local x4, y4 = polygon[ii], polygon[ii + 1]
-		local det2 = x3 * y4 - y3 * x4
-		local width34, height34 = x3 - x4, y3 - y4
-		local det3 = width12 * height34 - height12 * width34
-		local x = (det1 * width34 - width12 * det2) / det3
-		if ((x >= x3 and x <= x4) or (x >= x4 and x <= x3)) and ((x >= x1 and x <= x2) or (x >= x2 and x <= x1)) then
-			local y = (det1 * height34 - height12 * det2) / det3
-			if ((y >= y3 and y <= y4) or (y >= y4 and y <= y3)) and ((y >= y1 and y <= y2) or (y >= y2 and y <= y1)) then return true end
-		end
-		x3 = x4
-		y3 = y4
-	end
-	return false
-end
-
-function SkeletonBounds:getPolygon (attachment)
-	local index = utils.indexOf(self.boundingBoxes, attachment)
-	if index == -1 then
-		return nil
-	else
-		return self.polygons[index]
-	end
-end
-
-function SkeletonBounds:getWidth()
-	return self.maxX - self.minX
-end
-
-function SkeletonBounds:getHeight()
-	return self.maxY - self.minY
-end
-
-return SkeletonBounds
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local AttachmentType = require "spine-lua.attachments.AttachmentType"
+local utils = require "spine-lua.utils"
+
+local setmetatable = setmetatable
+local math_min = math.min
+local math_max = math.max
+local ipairs = ipairs
+local table_insert = table.insert
+
+local SkeletonBounds = {}
+SkeletonBounds.__index = SkeletonBounds
+
+function SkeletonBounds.new ()
+	local self = {
+		minX = 0, minY = 0, maxX = 0, maxY = 0,
+		polygons = {},
+		boundingBoxes = {},
+	}
+	setmetatable(self, SkeletonBounds)
+
+	return self
+end
+
+function SkeletonBounds:update (skeleton, updateAabb)
+	if skeleton == nil then error("skeleton cannot be null", 2) end
+	local boundingBoxes = {}
+	self.boundingBoxes = boundingBoxes
+	local polygons = {}
+	self.polygons = polygons
+	local slots = skeleton.slots
+
+	for _,slot in ipairs(skeleton.slots) do
+		if (slot.bone.active) then
+			local attachment = slot.attachment
+			if attachment and attachment.type == AttachmentType.boundingbox then
+				local boundingBox = attachment
+				table_insert(boundingBoxes, boundingBox)
+
+				local polygon = {}
+				table_insert(polygons, polygon)
+
+				boundingBox:computeWorldVertices(slot, 0, boundingBox.worldVerticesLength, polygon, 0, 2)
+			end
+		end
+	end
+
+	if updateAabb then
+		self:aabbCompute()
+	else
+		self.minX = 9999999
+		self.minY = 9999999
+		self.maxX = -9999999
+		self.maxY = -9999999
+	end
+end
+
+function SkeletonBounds:aabbCompute ()
+	local minX, minY, maxX, maxY = 9999999, 9999999, -9999999, -9999999
+	local polygons = self.polygons
+	for _,vertices in ipairs(polygons) do
+		local count = #vertices
+		for ii = 1, count, 2 do
+			local x = vertices[ii]
+			local y = vertices[ii + 1]
+			minX = math_min(minX, x)
+			minY = math_min(minY, y)
+			maxX = math_max(maxX, x)
+			maxY = math_max(maxY, y)
+		end
+	end
+	self.minX = minX
+	self.minY = minY
+	self.maxX = maxX
+	self.maxY = maxY
+end
+
+function SkeletonBounds:aabbContainsPoint (x, y)
+	return x >= self.minX and x <= self.maxX and y >= self.minY and y <= self.maxY
+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
+	end
+	local m = (y2 - y1) / (x2 - x1)
+	local y = m * (minX - x1) + y1
+	if y > minY and y < maxY then return true end
+	y = m * (maxX - x1) + y1
+	if y > minY and y < maxY then return true end
+	local x = (minY - y1) / m + x1
+	if x > minX and x < maxX then return true end
+	x = (maxY - y1) / m + x1
+	if x > minX and x < maxX then return true end
+	return false
+end
+
+function SkeletonBounds:aabbIntersectsSkeleton (bounds)
+	return self.minX < bounds.maxX and self.maxX > bounds.minX and self.minY < bounds.maxY and self.maxY > bounds.minY
+end
+
+function SkeletonBounds:containsPoint (x, y)
+	for i,polygon in ipairs(self.polygons) do
+		if self:polygonContainsPoint(polygon, x, y) then return self.boundingBoxes[i] end
+	end
+	return nil
+end
+
+function SkeletonBounds:intersectsSegment (x1, y1, x2, y2)
+	for i,polygon in ipairs(self.polygons) do
+		if self:polygonIntersectsSegment(polygon, x1, y1, x2, y2) then return self.boundingBoxes[i] end
+	end
+	return nil
+end
+
+function SkeletonBounds:polygonContainsPoint (polygon, x, y)
+	local nn = #polygon
+	local prevIndex = nn - 1
+	local inside = false
+	for ii = 1, nn, 2 do
+		local vertexY = polygon[ii + 1]
+		local prevY = polygon[prevIndex + 1]
+		if (vertexY < y and prevY >= y) or (prevY < y and vertexY >= y) then
+			local vertexX = polygon[ii]
+			if vertexX + (y - vertexY) / (prevY - vertexY) * (polygon[prevIndex] - vertexX) < x then inside = not inside end
+		end
+		prevIndex = ii
+	end
+	return inside
+end
+
+function SkeletonBounds:polygonIntersectsSegment (polygon, x1, y1, x2, y2)
+	local nn = #polygon
+	local width12, height12 = x1 - x2, y1 - y2
+	local det1 = x1 * y2 - y1 * x2
+	local x3, y3 = polygon[nn - 2], polygon[nn - 1]
+	for ii = 1, nn, 2 do
+		local x4, y4 = polygon[ii], polygon[ii + 1]
+		local det2 = x3 * y4 - y3 * x4
+		local width34, height34 = x3 - x4, y3 - y4
+		local det3 = width12 * height34 - height12 * width34
+		local x = (det1 * width34 - width12 * det2) / det3
+		if ((x >= x3 and x <= x4) or (x >= x4 and x <= x3)) and ((x >= x1 and x <= x2) or (x >= x2 and x <= x1)) then
+			local y = (det1 * height34 - height12 * det2) / det3
+			if ((y >= y3 and y <= y4) or (y >= y4 and y <= y3)) and ((y >= y1 and y <= y2) or (y >= y2 and y <= y1)) then return true end
+		end
+		x3 = x4
+		y3 = y4
+	end
+	return false
+end
+
+function SkeletonBounds:getPolygon (attachment)
+	local index = utils.indexOf(self.boundingBoxes, attachment)
+	if index == -1 then
+		return nil
+	else
+		return self.polygons[index]
+	end
+end
+
+function SkeletonBounds:getWidth()
+	return self.maxX - self.minX
+end
+
+function SkeletonBounds:getHeight()
+	return self.maxY - self.minY
+end
+
+return SkeletonBounds

+ 0 - 0
spine-lua/SkeletonClipping.lua → spine-lua/spine-lua/SkeletonClipping.lua


+ 141 - 141
spine-lua/SkeletonData.lua → spine-lua/spine-lua/SkeletonData.lua

@@ -1,141 +1,141 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local setmetatable = setmetatable
-
-local SkeletonData = {}
-SkeletonData.__index = SkeletonData
-
-function SkeletonData.new ()
-	local self = {
-		name,
-		bones = {},
-		slots = {},
-		skins = {},
-		defaultSkin = nil,
-		events = {},
-		animations = {},
-		ikConstraints = {},
-		transformConstraints = {},
-		pathConstraints = {},
-		x, y, width, height,
-		version, hash, imagesPath,
-		slotNameIndices = {}
-	}
-	setmetatable(self, SkeletonData)
-
-	return self
-end
-
-function SkeletonData:findBone (boneName)
-	if not boneName then error("boneName cannot be nil.", 2) end
-	for _,bone in ipairs(self.bones) do
-		if bone.name == boneName then return bone end
-	end
-	return nil
-end
-
-function SkeletonData:findBoneIndex (boneName)
-	if not boneName then error("boneName cannot be nil.", 2) end
-	for i,bone in ipairs(self.bones) do
-		if bone.name == boneName then return i end
-	end
-	return -1
-end
-
-function SkeletonData:findSlot (slotName)
-	if not slotName then error("slotName cannot be nil.", 2) end
-	for i,slot in ipairs(self.slots) do
-		if slot.name == slotName then return slot end
-	end
-	return nil
-end
-
-function SkeletonData:findSlotIndex (slotName)
-	if not slotName then error("slotName cannot be nil.", 2) end
-	return self.slotNameIndices[slotName] or -1
-end
-
-function SkeletonData:findSkin (skinName)
-	if not skinName then error("skinName cannot be nil.", 2) end
-	for _,skin in ipairs(self.skins) do
-		if skin.name == skinName then return skin end
-	end
-	return nil
-end
-
-function SkeletonData:findEvent (eventName)
-	if not eventName then error("eventName cannot be nil.", 2) end
-	for _,event in ipairs(self.events) do
-		if event.name == eventName then return event end
-	end
-	return nil
-end
-
-function SkeletonData:findAnimation (animationName)
-	if not animationName then error("animationName cannot be nil.", 2) end
-	for _,animation in ipairs(self.animations) do
-		if animation.name == animationName then return animation end
-	end
-	return nil
-end
-
-function SkeletonData:findIkConstraint (constraintName)
-	if not constraintName then error("constraintName cannot be nil.", 2) end
-	for _,constraint in ipairs(self.ikConstraints) do
-		if constraint.name == constraintName then return constraint end
-	end
-	return nil
-end
-
-function SkeletonData:findTransformConstraint (constraintName)
-	if not constraintName then error("constraintName cannot be nil.", 2) end
-	for _,constraint in ipairs(self.transformConstraints) do
-		if constraint.name == constraintName then return constraint end
-	end
-	return nil
-end
-
-function SkeletonData:findPathConstraint (constraintName)
-	if not constraintName then error("constraintName cannot be nil.", 2) end
-	for _,constraint in ipairs(self.pathConstraints) do
-		if constraint.name == constraintName then return constraint end
-	end
-	return nil
-end
-
-function SkeletonData:findPathConstraintIndex (constraintName)
-	if not constraintName then error("constraintName cannot be nil.", 2) end
-	for i,constraint in ipairs(self.pathConstraints) do
-		if constraint.name == constraintName then return i end
-	end
-	return -1
-end
-
-return SkeletonData
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+
+local SkeletonData = {}
+SkeletonData.__index = SkeletonData
+
+function SkeletonData.new ()
+	local self = {
+		name,
+		bones = {},
+		slots = {},
+		skins = {},
+		defaultSkin = nil,
+		events = {},
+		animations = {},
+		ikConstraints = {},
+		transformConstraints = {},
+		pathConstraints = {},
+		x, y, width, height,
+		version, hash, imagesPath,
+		slotNameIndices = {}
+	}
+	setmetatable(self, SkeletonData)
+
+	return self
+end
+
+function SkeletonData:findBone (boneName)
+	if not boneName then error("boneName cannot be nil.", 2) end
+	for _,bone in ipairs(self.bones) do
+		if bone.name == boneName then return bone end
+	end
+	return nil
+end
+
+function SkeletonData:findBoneIndex (boneName)
+	if not boneName then error("boneName cannot be nil.", 2) end
+	for i,bone in ipairs(self.bones) do
+		if bone.name == boneName then return i end
+	end
+	return -1
+end
+
+function SkeletonData:findSlot (slotName)
+	if not slotName then error("slotName cannot be nil.", 2) end
+	for i,slot in ipairs(self.slots) do
+		if slot.name == slotName then return slot end
+	end
+	return nil
+end
+
+function SkeletonData:findSlotIndex (slotName)
+	if not slotName then error("slotName cannot be nil.", 2) end
+	return self.slotNameIndices[slotName] or -1
+end
+
+function SkeletonData:findSkin (skinName)
+	if not skinName then error("skinName cannot be nil.", 2) end
+	for _,skin in ipairs(self.skins) do
+		if skin.name == skinName then return skin end
+	end
+	return nil
+end
+
+function SkeletonData:findEvent (eventName)
+	if not eventName then error("eventName cannot be nil.", 2) end
+	for _,event in ipairs(self.events) do
+		if event.name == eventName then return event end
+	end
+	return nil
+end
+
+function SkeletonData:findAnimation (animationName)
+	if not animationName then error("animationName cannot be nil.", 2) end
+	for _,animation in ipairs(self.animations) do
+		if animation.name == animationName then return animation end
+	end
+	return nil
+end
+
+function SkeletonData:findIkConstraint (constraintName)
+	if not constraintName then error("constraintName cannot be nil.", 2) end
+	for _,constraint in ipairs(self.ikConstraints) do
+		if constraint.name == constraintName then return constraint end
+	end
+	return nil
+end
+
+function SkeletonData:findTransformConstraint (constraintName)
+	if not constraintName then error("constraintName cannot be nil.", 2) end
+	for _,constraint in ipairs(self.transformConstraints) do
+		if constraint.name == constraintName then return constraint end
+	end
+	return nil
+end
+
+function SkeletonData:findPathConstraint (constraintName)
+	if not constraintName then error("constraintName cannot be nil.", 2) end
+	for _,constraint in ipairs(self.pathConstraints) do
+		if constraint.name == constraintName then return constraint end
+	end
+	return nil
+end
+
+function SkeletonData:findPathConstraintIndex (constraintName)
+	if not constraintName then error("constraintName cannot be nil.", 2) end
+	for i,constraint in ipairs(self.pathConstraints) do
+		if constraint.name == constraintName then return i end
+	end
+	return -1
+end
+
+return SkeletonData

+ 936 - 936
spine-lua/SkeletonJson.lua → spine-lua/spine-lua/SkeletonJson.lua

@@ -1,936 +1,936 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local table_insert = table.insert
-local SkeletonData = require "spine-lua.SkeletonData"
-local BoneData = require "spine-lua.BoneData"
-local SlotData = require "spine-lua.SlotData"
-local Skin = require "spine-lua.Skin"
-local AttachmentLoader = require "spine-lua.AttachmentLoader"
-local Animation = require "spine-lua.Animation"
-local IkConstraintData = require "spine-lua.IkConstraintData"
-local IkConstraint = require "spine-lua.IkConstraint"
-local PathConstraintData = require "spine-lua.PathConstraintData"
-local PathConstraint = require "spine-lua.PathConstraint"
-local TransformConstraintData = require "spine-lua.TransformConstraintData"
-local TransformConstraint = require "spine-lua.TransformConstraint"
-local EventData = require "spine-lua.EventData"
-local Event = require "spine-lua.Event"
-local AttachmentType = require "spine-lua.attachments.AttachmentType"
-local BlendMode = require "spine-lua.BlendMode"
-local TransformMode = require "spine-lua.TransformMode"
-local utils = require "spine-lua.utils"
-local Color = require "spine-lua.Color"
-
-local SkeletonJson = {}
-function SkeletonJson.new (attachmentLoader)
-	if not attachmentLoader then attachmentLoader = AttachmentLoader.new() end
-
-	local self = {
-		attachmentLoader = attachmentLoader,
-		scale = 1,
-		linkedMeshes = {}
-	}
-
-	function self:readSkeletonDataFile (fileName, base)
-		return self:readSkeletonData(utils.readFile(fileName, base))
-	end
-
-	local readAttachment
-	local readAnimation
-	local readCurve
-	local getArray
-
-	local getValue = function (map, name, default)
-		local value = map[name]
-		if value == nil then return default else return value end
-	end
-
-	function self:readSkeletonData (jsonText)
-		local scale = self.scale
-		local skeletonData = SkeletonData.new(self.attachmentLoader)
-		local root = utils.readJSON(jsonText)
-		if not root then error("Invalid JSON: " .. jsonText, 2) end
-
-		-- Skeleton.
-		local skeletonMap = root["skeleton"]
-		if skeletonMap then
-			skeletonData.hash = skeletonMap["hash"]
-			skeletonData.version = skeletonMap["spine"]
-			if ("3.8.75" == skeletonData.version) then
-				error("Unsupported skeleton data, please export with a newer version of Spine.")
-			end
-			skeletonData.x = skeletonMap["x"]
-			skeletonData.y = skeletonMap["y"]
-			skeletonData.width = skeletonMap["width"]
-			skeletonData.height = skeletonMap["height"]
-			skeletonData.fps = skeletonMap["fps"]
-			skeletonData.imagesPath = skeletonMap["images"]
-		end
-
-		-- Bones.
-		for i,boneMap in ipairs(root["bones"]) do
-			local boneName = boneMap["name"]
-
-			local parent = nil
-			local parentName = boneMap["parent"]
-			if parentName then
-				parent = skeletonData:findBone(parentName)
-				if not parent then error("Parent bone not found: " .. parentName) end
-			end
-			local data = BoneData.new(i, boneName, parent)
-			data.length = getValue(boneMap, "length", 0) * scale;
-			data.x = getValue(boneMap, "x", 0) * scale;
-			data.y = getValue(boneMap, "y", 0) * scale;
-			data.rotation = getValue(boneMap, "rotation", 0);
-			data.scaleX = getValue(boneMap, "scaleX", 1);
-			data.scaleY = getValue(boneMap, "scaleY", 1);
-			data.shearX = getValue(boneMap, "shearX", 0);
-			data.shearY = getValue(boneMap, "shearY", 0);
-			data.transformMode = TransformMode[getValue(boneMap, "transform", "normal")]
-			data.skinRequired = getValue(boneMap, "skin", false)
-
-			table_insert(skeletonData.bones, data)
-		end
-
-		-- Slots.
-		if root["slots"] then
-			for i,slotMap in ipairs(root["slots"]) do
-				local slotName = slotMap["name"]
-				local boneName = slotMap["bone"]
-				local boneData = skeletonData:findBone(boneName)
-				if not boneData then error("Slot bone not found: " .. boneName) end
-				local data = SlotData.new(i, slotName, boneData)
-
-				local color = slotMap["color"]
-				if color then
-					data.color:set(tonumber(color:sub(1, 2), 16) / 255,
-						tonumber(color:sub(3, 4), 16) / 255,
-						tonumber(color:sub(5, 6), 16) / 255,
-						tonumber(color:sub(7, 8), 16) / 255)
-				end
-
-				local dark = slotMap["dark"]
-				if dark then
-					data.darkColor = Color.newWith(1, 1, 1, 1)
-					data.darkColor:set(tonumber(dark:sub(1, 2), 16) / 255,
-						tonumber(dark:sub(3, 4), 16) / 255,
-						tonumber(dark:sub(5, 6), 16) / 255,
-						0)
-				end
-
-				data.attachmentName = getValue(slotMap, "attachment", nil)
-				data.blendMode = BlendMode[getValue(slotMap, "blend", "normal")]
-
-				table_insert(skeletonData.slots, data)
-				skeletonData.slotNameIndices[data.name] = #skeletonData.slots
-			end
-		end
-
-		-- IK constraints.
-		if root["ik"] then
-			for _,constraintMap in ipairs(root["ik"]) do
-				local data = IkConstraintData.new(constraintMap["name"])
-				data.order = getValue(constraintMap, "order", 0)
-				data.skinRequired = getValue(constraintMap, "skin", false)
-
-				for _,boneName in ipairs(constraintMap["bones"]) do
-					local bone = skeletonData:findBone(boneName)
-					if not bone then error("IK 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("Target bone not found: " .. targetName) end
-
-				data.mix = getValue(constraintMap, "mix", 1)
-				data.softness = getValue(constraintMap, "softness", 0) * scale
-				if constraintMap["bendPositive"] == nil or constraintMap["bendPositive"] == true then
-					data.bendDirection = 1
-				else
-					data.bendDirection = -1
-				end
-				if constraintMap["compress"] == nil or constraintMap["compress"] == false then data.compress = false else data.compress = true end
-				if constraintMap["stretch"] == nil	or constraintMap["stretch"] == false then data.stretch = false else data.stretch = true end
-				if constraintMap["uniform"] == nil or	constraintMap["uniform"] == false then data.uniform = false else data.uniform = true end
-
-				table_insert(skeletonData.ikConstraints, data)
-			end
-		end
-
-		-- Transform constraints
-		if root["transform"] then
-			for _,constraintMap in ipairs(root["transform"]) do
-				local data = TransformConstraintData.new(constraintMap.name)
-				data.order = getValue(constraintMap, "order", 0)
-				data.skinRequired = getValue(constraintMap, "skin", false)
-
-				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
-					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
-
-				data.local_ = getValue(constraintMap, "local", false)
-				data.relative = getValue(constraintMap, "relative", false)
-				data.offsetRotation = getValue(constraintMap, "rotation", 0);
-				data.offsetX = getValue(constraintMap, "x", 0) * scale;
-				data.offsetY = getValue(constraintMap, "y", 0) * scale;
-				data.offsetScaleX = getValue(constraintMap, "scaleX", 0);
-				data.offsetScaleY = getValue(constraintMap, "scaleY", 0);
-				data.offsetShearY = getValue(constraintMap, "shearY", 0);
-
-				data.rotateMix = getValue(constraintMap, "rotateMix", 1);
-				data.translateMix = getValue(constraintMap, "translateMix", 1);
-				data.scaleMix = getValue(constraintMap, "scaleMix", 1);
-				data.shearMix = getValue(constraintMap, "shearMix", 1);
-
-				table_insert(skeletonData.transformConstraints, data)
-			end
-		end
-
-		-- Path constraints
-		if root["path"] then
-			for _,constraintMap in ipairs(root.path) do
-				local data = PathConstraintData.new(constraintMap.name);
-				data.order = getValue(constraintMap, "order", 0)
-				data.skinRequired = getValue(constraintMap, "skin", false)
-
-				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
-					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
-
-				data.positionMode = PathConstraintData.PositionMode[getValue(constraintMap, "positionMode", "percent"):lower()]
-				data.spacingMode = PathConstraintData.SpacingMode[getValue(constraintMap, "spacingMode", "length"):lower()]
-				data.rotateMode = PathConstraintData.RotateMode[getValue(constraintMap, "rotateMode", "tangent"):lower()]
-				data.offsetRotation = getValue(constraintMap, "rotation", 0);
-				data.position = getValue(constraintMap, "position", 0);
-				if data.positionMode == PathConstraintData.PositionMode.fixed then data.position = data.position * scale end
-				data.spacing = getValue(constraintMap, "spacing", 0);
-				if data.spacingMode == PathConstraintData.SpacingMode.length or data.spacingMode == PathConstraintData.SpacingMode.fixed then data.spacing = data.spacing * scale end
-				data.rotateMix = getValue(constraintMap, "rotateMix", 1);
-				data.translateMix = getValue(constraintMap, "translateMix", 1);
-
-				table_insert(skeletonData.pathConstraints, data)
-			end
-		end
-
-		-- Skins.
-		if root["skins"] then
-			for skinName,skinMap in pairs(root["skins"]) do
-				local skin = Skin.new(skinMap["name"])
-				
-				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
-						table_insert(skin.bones, bone)
-					end
-				end
-				
-				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
-						table_insert(skin.constraints, constraint)
-					end
-				end
-				
-				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
-						table_insert(skin.constraints, constraint)
-					end
-				end
-				
-				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
-						table_insert(skin.constraints, constraint)
-					end
-				end
-				
-				for slotName,slotMap in pairs(skinMap.attachments) do
-					local slotIndex = skeletonData.slotNameIndices[slotName]
-					for attachmentName,attachmentMap in pairs(slotMap) do
-						local attachment = readAttachment(attachmentMap, skin, slotIndex, attachmentName, skeletonData)
-						if attachment then
-							skin:setAttachment(slotIndex, attachmentName, attachment)
-						end
-					end
-				end
-				table_insert(skeletonData.skins, skin)
-				if skin.name == "default" then skeletonData.defaultSkin = skin end
-			end
-		end
-
-		-- Linked meshes
-		for _, linkedMesh in ipairs(self.linkedMeshes) do
-			local skin = skeletonData.defaultSkin
-			if linkedMesh.skin then skin = skeletonData:findSkin(linkedMesh.skin) end
-			if not skin then error("Skin not found: " .. linkedMesh.skin) end
-			local parent = skin:getAttachment(linkedMesh.slotIndex, linkedMesh.parent)
-			if not parent then error("Parent mesh not found: " + linkedMesh.parent) end
-			if linkedMesh.inheritDeform then
-				linkedMesh.mesh.deformAttachment = parent
-			else
-				linkedMesh.mesh.deformAttachment = linkedMesh.mesh
-			end
-
-			linkedMesh.mesh:setParentMesh(parent)
-			linkedMesh.mesh:updateUVs()
-		end
-		self.linkedMeshes = {}
-
-		-- Events.
-		if root["events"] then
-			for eventName,eventMap in pairs(root["events"]) do
-				local data = EventData.new(eventName)
-				data.intValue = getValue(eventMap, "int", 0)
-				data.floatValue = getValue(eventMap, "float", 0)
-				data.stringValue = getValue(eventMap, "string", "")
-				data.audioPath = getValue(eventMap, "audio", nil)
-				if data.audioPath ~= nil then
-					data.volume = getValue(eventMap, "volume", 1)
-					data.balance = getValue(eventMap, "balance", 0)
-				end
-				table_insert(skeletonData.events, data)
-			end
-		end
-
-		-- Animations.
-		if root["animations"] then
-			for animationName,animationMap in pairs(root["animations"]) do
-				readAnimation(animationMap, animationName, skeletonData)
-			end
-		end
-
-		return skeletonData
-	end
-
-	readAttachment = function (map, skin, slotIndex, name, skeletonData)
-		local scale = self.scale
-		name = getValue(map, "name", name)
-
-		local type = AttachmentType[getValue(map, "type", "region")]
-		local path = getValue(map, "path", name)
-
-		if type == AttachmentType.region then
-			local region = attachmentLoader:newRegionAttachment(skin, name, path)
-			if not region then return nil end
-			region.path = path
-			region.x = getValue(map, "x", 0) * scale
-			region.y = getValue(map, "y", 0) * scale
-			region.scaleX = getValue(map, "scaleX", 1);
-			region.scaleY = getValue(map, "scaleY", 1);
-			region.rotation = getValue(map, "rotation", 0);
-			region.width = map.width * scale;
-			region.height = map.height * scale;
-
-			local color = map["color"]
-			if color then
-				region.color:set(tonumber(color:sub(1, 2), 16) / 255,
-					tonumber(color:sub(3, 4), 16) / 255,
-					tonumber(color:sub(5, 6), 16) / 255,
-					tonumber(color:sub(7, 8), 16) / 255)
-			end
-
-			region:updateOffset()
-			return region
-
-		elseif type == AttachmentType.boundingbox then
-			local box = attachmentLoader:newBoundingBoxAttachment(skin, name)
-			if not box then return nil end
-			readVertices(map, box, map.vertexCount * 2)
-			local color = map.color
-			if color then
-				box.color:set(tonumber(color:sub(1, 2), 16) / 255,
-					tonumber(color:sub(3, 4), 16) / 255,
-					tonumber(color:sub(5, 6), 16) / 255,
-					tonumber(color:sub(7, 8), 16) / 255)
-			end
-			return box
-
-		elseif type == AttachmentType.mesh or type == AttachmentType.linkedmesh then
-			local mesh = attachmentLoader:newMeshAttachment(skin, name, path)
-			if not mesh then return nil end
-			mesh.path = path
-
-			local color = map.color
-			if color then
-				mesh.color:set(tonumber(color:sub(1, 2), 16) / 255,
-					tonumber(color:sub(3, 4), 16) / 255,
-					tonumber(color:sub(5, 6), 16) / 255,
-					tonumber(color:sub(7, 8), 16) / 255)
-			end
-
-			mesh.width = getValue(map, "width", 0) * scale
-			mesh.height = getValue(map, "height", 0) * scale
-
-			local parent = map.parent
-			if parent then
-				table_insert(self.linkedMeshes, {
-					mesh = mesh,
-					skin = getValue(map, "skin", nil),
-					slotIndex = slotIndex,
-					parent = parent,
-					inheritDeform = getValue(map, "deform", true)
-				})
-				return mesh
-			end
-
-			local uvs = getArray(map, "uvs", 1)
-			readVertices(map, mesh, #uvs)
-			mesh.triangles = getArray(map, "triangles", 1)
-			-- adjust triangle indices by 1, vertices are one-indexed
-			for i,v in ipairs(mesh.triangles) do
-				mesh.triangles[i] = v + 1
-			end
-			mesh.regionUVs = uvs
-			mesh:updateUVs()
-
-			mesh.hullLength = getValue(map, "hull", 0) * 2
-			return mesh
-
-		elseif type == AttachmentType.path then
-			local path = self.attachmentLoader:newPathAttachment(skin, name)
-			if not path then return nil end
-			path.closed = getValue(map, "closed", false)
-			path.constantSpeed = getValue(map, "constantSpeed", true)
-
-			local vertexCount = map.vertexCount
-			readVertices(map, path, vertexCount * 2)
-
-			local lengths = utils.newNumberArray(vertexCount / 3, 0)
-			for i,v in ipairs(map.lengths) do
-				lengths[i] = v * scale
-			end
-			path.lengths = lengths
-
-			local color = map.color
-			if color then
-				path.color:set(tonumber(color:sub(1, 2), 16) / 255,
-					tonumber(color:sub(3, 4), 16) / 255,
-					tonumber(color:sub(5, 6), 16) / 255,
-					tonumber(color:sub(7, 8), 16) / 255)
-			end
-			return path;
-
-		elseif type == AttachmentType.point then
-			local point = self.attachmentLoader:newPointAttachment(skin, name)
-			if not point then return nil end
-			point.x = getValue(map, "x", 0) * scale
-			point.y = getValue(map, "y", 0) * scale
-			point.rotation = getValue(map, "rotation", 0)
-
-			local color = map.color
-			if color then
-				path.color:set(tonumber(color:sub(1, 2), 16) / 255,
-					tonumber(color:sub(3, 4), 16) / 255,
-					tonumber(color:sub(5, 6), 16) / 255,
-					tonumber(color:sub(7, 8), 16) / 255)
-			end
-			return point
-
-		elseif type == AttachmentType.clipping then
-			local clip = attachmentLoader:newClippingAttachment(skin, name)
-			if not clip then return nil end
-
-			local _end = getValue(map, "end", nil)
-			if _end then
-				local slot = skeletonData:findSlot(_end)
-				if not slot then error("Clipping end slot not found: " + _end) end
-				clip.endSlot = slot
-			end
-
-			readVertices(map, clip, map.vertexCount * 2)
-			local color = map.color
-			if color then
-				clip.color:set(tonumber(color:sub(1, 2), 16) / 255,
-					tonumber(color:sub(3, 4), 16) / 255,
-					tonumber(color:sub(5, 6), 16) / 255,
-					tonumber(color:sub(7, 8), 16) / 255)
-			end
-			return clip
-		end
-
-		error("Unknown attachment type: " .. type .. " (" .. name .. ")")
-	end
-
-	readVertices = function (map, attachment, verticesLength)
-		local scale = self.scale
-		attachment.worldVerticesLength = verticesLength
-		local vertices = getArray(map, "vertices", 1)
-		if verticesLength == #vertices then
-			if scale ~= 1 then
-				local i = 0
-				local n = #vertices
-				while i < n do
-					vertices[i + 1] = vertices[i + 1] * scale
-					i = i + 1
-				end
-			end
-			attachment.vertices = vertices
-			return
-		end
-
-		local weights = {}
-		local bones = {}
-		local i = 0
-		local n = #vertices
-		while i < n do
-			local boneCount = vertices[i + 1]
-			i = i + 1
-			table_insert(bones, boneCount)
-			local nn = i + boneCount * 4
-			while i < nn do
-				table_insert(bones, vertices[i + 1] + 1) -- +1 because bones are one-indexed
-				table_insert(weights, vertices[i + 2] * scale)
-				table_insert(weights, vertices[i + 3] * scale)
-				table_insert(weights, vertices[i + 4])
-				i = i + 4
-			end
-		end
-		attachment.bones = bones
-		attachment.vertices = weights
-	end
-
-	readAnimation = function (map, name, skeletonData)
-		local timelines = {}
-		local duration = 0
-		local scale = self.scale
-
-		-- Slot timelines
-		local slotsMap = map["slots"]
-		if slotsMap then
-			for slotName,timelineMap in pairs(slotsMap) do
-				local slotIndex = skeletonData.slotNameIndices[slotName]
-
-				for timelineName,values in pairs(timelineMap) do
-					if timelineName == "color" then
-						local timeline = Animation.ColorTimeline.new(#values)
-						timeline.slotIndex = slotIndex
-
-						local frameIndex = 0
-						for _,valueMap in ipairs(values) do
-							local color = valueMap["color"]
-							timeline:setFrame(
-								frameIndex, getValue(valueMap, "time", 0),
-								tonumber(color:sub(1, 2), 16) / 255,
-								tonumber(color:sub(3, 4), 16) / 255,
-								tonumber(color:sub(5, 6), 16) / 255,
-								tonumber(color:sub(7, 8), 16) / 255
-							)
-							readCurve(valueMap, timeline, frameIndex)
-							frameIndex = frameIndex + 1
-						end
-						table_insert(timelines, timeline)
-						duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.ColorTimeline.ENTRIES])
-					elseif timelineName == "twoColor" then
-						local timeline = Animation.TwoColorTimeline.new(#values)
-						timeline.slotIndex = slotIndex
-
-						local frameIndex = 0
-						for _,valueMap in ipairs(values) do
-							local light = valueMap["light"]
-							local dark = valueMap["dark"]
-							timeline:setFrame(
-								frameIndex, getValue(valueMap, "time", 0),
-								tonumber(light:sub(1, 2), 16) / 255,
-								tonumber(light:sub(3, 4), 16) / 255,
-								tonumber(light:sub(5, 6), 16) / 255,
-								tonumber(light:sub(7, 8), 16) / 255,
-								tonumber(dark:sub(1, 2), 16) / 255,
-								tonumber(dark:sub(3, 4), 16) / 255,
-								tonumber(dark:sub(5, 6), 16) / 255
-							)
-							readCurve(valueMap, timeline, frameIndex)
-							frameIndex = frameIndex + 1
-						end
-						table_insert(timelines, timeline)
-						duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.TwoColorTimeline.ENTRIES])
-					elseif timelineName == "attachment" then
-						local timeline = Animation.AttachmentTimeline.new(#values)
-						timeline.slotIndex = slotIndex
-
-						local frameIndex = 0
-						for _,valueMap in ipairs(values) do
-							local attachmentName = valueMap["name"]
-							timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), attachmentName)
-							frameIndex = frameIndex + 1
-						end
-						table_insert(timelines, timeline)
-						duration = math.max(duration, timeline.frames[timeline:getFrameCount() - 1])
-
-					else
-						error("Invalid frame type for a slot: " .. timelineName .. " (" .. slotName .. ")")
-					end
-				end
-			end
-		end
-
-		-- Bone timelines
-		local bonesMap = map["bones"]
-		if bonesMap then
-			for boneName,timelineMap in pairs(bonesMap) do
-				local boneIndex = skeletonData:findBoneIndex(boneName)
-				if boneIndex == -1 then error("Bone not found: " .. boneName) end
-
-				for timelineName,values in pairs(timelineMap) do
-					if timelineName == "rotate" then
-						local timeline = Animation.RotateTimeline.new(#values)
-						timeline.boneIndex = boneIndex
-
-						local frameIndex = 0
-						for _,valueMap in ipairs(values) do
-							timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), getValue(valueMap, "angle", 0))
-							readCurve(valueMap, timeline, frameIndex)
-							frameIndex = frameIndex + 1
-						end
-						table_insert(timelines, timeline)
-						duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.RotateTimeline.ENTRIES])
-
-					elseif timelineName == "translate" or timelineName == "scale" or timelineName == "shear" then
-						local timeline
-						local timelineScale = 1
-						local defaultValue = 0
-						if timelineName == "scale" then
-							timeline = Animation.ScaleTimeline.new(#values)
-							defaultValue = 1
-						elseif timelineName == "shear" then
-							timeline = Animation.ShearTimeline.new(#values)
-						else
-							timeline = Animation.TranslateTimeline.new(#values)
-							timelineScale = self.scale
-						end
-						timeline.boneIndex = boneIndex
-
-						local frameIndex = 0
-						for _,valueMap in ipairs(values) do
-							local x = getValue(valueMap, "x", defaultValue) * timelineScale
-							local y = getValue(valueMap, "y", defaultValue) * timelineScale
-							timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), x, y)
-							readCurve(valueMap, timeline, frameIndex)
-							frameIndex = frameIndex + 1
-						end
-						table_insert(timelines, timeline)
-						duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.TranslateTimeline.ENTRIES])
-					else
-						error("Invalid timeline type for a bone: " .. timelineName .. " (" .. boneName .. ")")
-					end
-				end
-			end
-		end
-
-		-- IK timelines.
-		local ik = map["ik"]
-		if ik then
-			for ikConstraintName,values in pairs(ik) do
-				local ikConstraint = skeletonData:findIkConstraint(ikConstraintName)
-				local timeline = Animation.IkConstraintTimeline.new(#values)
-				for i,other in pairs(skeletonData.ikConstraints) do
-					if other == ikConstraint then
-						timeline.ikConstraintIndex = i
-						break
-					end
-				end
-				local frameIndex = 0
-				for _,valueMap in ipairs(values) do
-					local mix = 1
-					local softness = 0
-					if valueMap["mix"] ~= nil then mix = valueMap["mix"] end
-					if valueMap["softness"] ~= nil then softness = valueMap["softness"] * scale end
-					local bendPositive = 1
-					if valueMap["bendPositive"] == false then bendPositive = -1 end
-					local stretch = false
-					if valueMap["stretch"] ~= nil then stretch = valueMap["stretch"] end
-					local compress = false
-					if valueMap["compress"] ~= nil then compress = valueMap["compress"] end
-					timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), mix, softness, bendPositive, compress, stretch)
-					readCurve(valueMap, timeline, frameIndex)
-					frameIndex = frameIndex + 1
-				end
-				table_insert(timelines, timeline)
-				duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.IkConstraintTimeline.ENTRIES])
-			end
-		end
-
-		-- Transform constraint timelines.
-		local transform = map["transform"]
-		if transform then
-			for constraintName, values in pairs(transform) do
-				local constraint = skeletonData:findTransformConstraint(constraintName)
-				local timeline = Animation.TransformConstraintTimeline.new(#values)
-				for i,other in pairs(skeletonData.transformConstraints) do
-					if other == constraint then
-						timeline.transformConstraintIndex = i
-						break
-					end
-				end
-				local frameIndex = 0
-				for _,valueMap in ipairs(values) do
-					timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), getValue(valueMap, "rotateMix", 1), getValue(valueMap, "translateMix", 1), getValue(valueMap, "scaleMix", 1), getValue(valueMap, "shearMix", 1))
-					readCurve(valueMap, timeline, frameIndex)
-					frameIndex = frameIndex + 1
-				end
-				table_insert(timelines, timeline)
-				duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.TransformConstraintTimeline.ENTRIES])
-			end
-		end
-
-		-- Path constraint timelines.
-		if map.path then
-			for constraintName,constraintMap in pairs(map.path) do
-				local index = skeletonData:findPathConstraintIndex(constraintName)
-				if index == -1 then error("Path constraint not found: " .. constraintName, 2) end
-				local data = skeletonData.pathConstraints[index]
-				for timelineName, timelineMap in pairs(constraintMap) do
-					if timelineName == "position" or timelineName == "spacing" then
-						local timeline = nil
-						local timelineScale = 1
-						if timelineName == "spacing" then
-							timeline = Animation.PathConstraintSpacingTimeline.new(#timelineMap)
-							if data.spacingMode == PathConstraintData.SpacingMode.length or data.spacingMode == PathConstraintData.SpacingMode.fixed then timelineScale = scale end
-						else
-							timeline = Animation.PathConstraintPositionTimeline.new(#timelineMap)
-							if data.positionMode == PathConstraintData.PositionMode.fixed then timelineScale = scale end
-						end
-						timeline.pathConstraintIndex = index
-						local frameIndex = 0
-						for _,valueMap in ipairs(timelineMap) do
-							timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), getValue(valueMap, timelineName, 0) * timelineScale)
-							readCurve(valueMap, timeline, frameIndex)
-							frameIndex = frameIndex + 1
-						end
-						table_insert(timelines, timeline)
-						duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.PathConstraintPositionTimeline.ENTRIES])
-					elseif timelineName == "mix" then
-						local timeline = Animation.PathConstraintMixTimeline.new(#timelineMap)
-						timeline.pathConstraintIndex = index
-						local frameIndex = 0
-						for _,valueMap in ipairs(timelineMap) do
-							timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), getValue(valueMap, "rotateMix", 1), getValue(valueMap, "translateMix", 1))
-							readCurve(valueMap, timeline, frameIndex)
-							frameIndex = frameIndex + 1
-						end
-						table_insert(timelines, timeline)
-						duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.PathConstraintMixTimeline.ENTRIES])
-					end
-				end
-			end
-		end
-
-		-- Deform timelines.
-		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
-				for slotName,slotMap in pairs(deformMap) do
-					local slotIndex = skeletonData:findSlotIndex(slotName)
-					if slotIndex == -1 then error("Slot not found: " .. slotMap.name, 2) end
-					for timelineName,timelineMap in pairs(slotMap) do
-						local attachment = skin:getAttachment(slotIndex, timelineName)
-						if not attachment then error("Deform attachment not found: " .. timelineMap.name, 2) end
-						local weighted = attachment.bones ~= nil
-						local vertices = attachment.vertices;
-						local deformLength = #vertices
-						if weighted then deformLength = math.floor(#vertices / 3) * 2 end
-
-						local timeline = Animation.DeformTimeline.new(#timelineMap)
-						timeline.slotIndex = slotIndex
-						timeline.attachment = attachment
-
-						local frameIndex = 0
-						for _,valueMap in ipairs(timelineMap) do
-							local deform = nil
-							local verticesValue = getValue(valueMap, "vertices", nil)
-							if verticesValue == nil then
-								deform = vertices
-								if weighted then deform = utils.newNumberArray(deformLength) end
-							else
-								deform = utils.newNumberArray(deformLength)
-								local start = getValue(valueMap, "offset", 0) + 1
-								utils.arrayCopy(verticesValue, 1, deform, start, #verticesValue)
-								if scale ~= 1 then
-									local i = start
-									local n = i + #verticesValue
-									while i < n do
-										deform[i] = deform[i] * scale
-										i = i + 1
-									end
-								end
-								if not weighted then
-									local i = 1
-									local n = i + deformLength
-									while i < n do
-										deform[i] = deform[i] + vertices[i]
-										i = i + 1
-									end
-								end
-							end
-
-							timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), deform)
-							readCurve(valueMap, timeline, frameIndex)
-							frameIndex = frameIndex + 1
-						end
-						table_insert(timelines, timeline)
-						duration = math.max(duration, timeline.frames[timeline:getFrameCount() - 1])
-					end
-				end
-			end
-		end
-
-		-- Draworder timeline.
-		local drawOrderValues = map["drawOrder"]
-		if not drawOrderValues then drawOrderValues = map["draworder"] end
-		if drawOrderValues then
-			local timeline = Animation.DrawOrderTimeline.new(#drawOrderValues)
-			local slotCount = #skeletonData.slots
-			local frameIndex = 0
-			for _,drawOrderMap in ipairs(drawOrderValues) do
-				local drawOrder = nil
-				local offsets = drawOrderMap["offsets"]
-				if offsets then
-					drawOrder = {}
-					local unchanged = {}
-					local originalIndex = 1
-					local unchangedIndex = 1
-					for _,offsetMap in ipairs(offsets) do
-						local slotIndex = skeletonData:findSlotIndex(offsetMap["slot"])
-						if slotIndex == -1 then error("Slot not found: " .. offsetMap["slot"]) end
-						-- Collect unchanged items.
-						while originalIndex ~= slotIndex do
-							unchanged[unchangedIndex] = originalIndex
-							unchangedIndex = unchangedIndex + 1
-							originalIndex = originalIndex + 1
-						end
-						-- Set changed items.
-						drawOrder[originalIndex + offsetMap["offset"]] = originalIndex
-						originalIndex = originalIndex + 1
-					end
-					-- Collect remaining unchanged items.
-					while originalIndex <= slotCount do
-						unchanged[unchangedIndex] = originalIndex
-						unchangedIndex = unchangedIndex + 1
-						originalIndex = originalIndex + 1
-					end
-					-- Fill in unchanged items.
-					for ii = slotCount, 1, -1 do
-						if not drawOrder[ii] then
-							unchangedIndex = unchangedIndex - 1
-							drawOrder[ii] = unchanged[unchangedIndex]
-						end
-					end
-				end
-				timeline:setFrame(frameIndex, getValue(drawOrderMap, "time", 0), drawOrder)
-				frameIndex = frameIndex + 1
-			end
-			table_insert(timelines, timeline)
-			duration = math.max(duration, timeline.frames[timeline:getFrameCount() - 1])
-		end
-
-		-- Event timeline.
-		local events = map["events"]
-		if events then
-			local timeline = Animation.EventTimeline.new(#events)
-			local frameIndex = 0
-			for _,eventMap in ipairs(events) do
-				local eventData = skeletonData:findEvent(eventMap["name"])
-				if not eventData then error("Event not found: " .. eventMap["name"]) end
-				local event = Event.new(getValue(eventMap, "time", 0), eventData)
-				if eventMap["int"] ~= nil then
-					event.intValue = eventMap["int"]
-				else
-					event.intValue = eventData.intValue
-				end
-				if eventMap["float"] ~= nil then
-					event.floatValue = eventMap["float"]
-				else
-					event.floatValue = eventData.floatValue
-				end
-				if eventMap["string"] ~= nil then
-					event.stringValue = eventMap["string"]
-				else
-					event.stringValue = eventData.stringValue
-				end
-				if eventData.audioPath ~= nil then
-					event.volume = getValue(eventMap, "volume", 1)
-					event.balance = getValue(eventMap, "balance", 0)
-				end
-				timeline:setFrame(frameIndex, event)
-				frameIndex = frameIndex + 1
-			end
-			table_insert(timelines, timeline)
-			duration = math.max(duration, timeline.frames[timeline:getFrameCount() - 1])
-		end
-
-		table_insert(skeletonData.animations, Animation.new(name, timelines, duration))
-	end
-
-	readCurve = function (map, timeline, frameIndex)
-		local curve = map["curve"]
-		if not curve then return end
-		if curve == "stepped" then
-			timeline:setStepped(frameIndex)
-		else
-			timeline:setCurve(frameIndex, getValue(map, "curve", 0), getValue(map, "c2", 0), getValue(map, "c3", 1), getValue(map, "c4", 1))
-		end
-	end
-
-	getArray = function (map, name, scale)
-		local list = map[name]
-		local values = {}
-		if scale == 1 then
-			for i = 1, #list do
-				values[i] = list[i]
-			end
-		else
-			for i = 1, #list do
-				values[i] = list[i] * scale
-			end
-		end
-		return values
-	end
-
-	return self
-end
-return SkeletonJson
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local table_insert = table.insert
+local SkeletonData = require "spine-lua.SkeletonData"
+local BoneData = require "spine-lua.BoneData"
+local SlotData = require "spine-lua.SlotData"
+local Skin = require "spine-lua.Skin"
+local AttachmentLoader = require "spine-lua.AttachmentLoader"
+local Animation = require "spine-lua.Animation"
+local IkConstraintData = require "spine-lua.IkConstraintData"
+local IkConstraint = require "spine-lua.IkConstraint"
+local PathConstraintData = require "spine-lua.PathConstraintData"
+local PathConstraint = require "spine-lua.PathConstraint"
+local TransformConstraintData = require "spine-lua.TransformConstraintData"
+local TransformConstraint = require "spine-lua.TransformConstraint"
+local EventData = require "spine-lua.EventData"
+local Event = require "spine-lua.Event"
+local AttachmentType = require "spine-lua.attachments.AttachmentType"
+local BlendMode = require "spine-lua.BlendMode"
+local TransformMode = require "spine-lua.TransformMode"
+local utils = require "spine-lua.utils"
+local Color = require "spine-lua.Color"
+
+local SkeletonJson = {}
+function SkeletonJson.new (attachmentLoader)
+	if not attachmentLoader then attachmentLoader = AttachmentLoader.new() end
+
+	local self = {
+		attachmentLoader = attachmentLoader,
+		scale = 1,
+		linkedMeshes = {}
+	}
+
+	function self:readSkeletonDataFile (fileName, base)
+		return self:readSkeletonData(utils.readFile(fileName, base))
+	end
+
+	local readAttachment
+	local readAnimation
+	local readCurve
+	local getArray
+
+	local getValue = function (map, name, default)
+		local value = map[name]
+		if value == nil then return default else return value end
+	end
+
+	function self:readSkeletonData (jsonText)
+		local scale = self.scale
+		local skeletonData = SkeletonData.new(self.attachmentLoader)
+		local root = utils.readJSON(jsonText)
+		if not root then error("Invalid JSON: " .. jsonText, 2) end
+
+		-- Skeleton.
+		local skeletonMap = root["skeleton"]
+		if skeletonMap then
+			skeletonData.hash = skeletonMap["hash"]
+			skeletonData.version = skeletonMap["spine"]
+			if ("3.8.75" == skeletonData.version) then
+				error("Unsupported skeleton data, please export with a newer version of Spine.")
+			end
+			skeletonData.x = skeletonMap["x"]
+			skeletonData.y = skeletonMap["y"]
+			skeletonData.width = skeletonMap["width"]
+			skeletonData.height = skeletonMap["height"]
+			skeletonData.fps = skeletonMap["fps"]
+			skeletonData.imagesPath = skeletonMap["images"]
+		end
+
+		-- Bones.
+		for i,boneMap in ipairs(root["bones"]) do
+			local boneName = boneMap["name"]
+
+			local parent = nil
+			local parentName = boneMap["parent"]
+			if parentName then
+				parent = skeletonData:findBone(parentName)
+				if not parent then error("Parent bone not found: " .. parentName) end
+			end
+			local data = BoneData.new(i, boneName, parent)
+			data.length = getValue(boneMap, "length", 0) * scale;
+			data.x = getValue(boneMap, "x", 0) * scale;
+			data.y = getValue(boneMap, "y", 0) * scale;
+			data.rotation = getValue(boneMap, "rotation", 0);
+			data.scaleX = getValue(boneMap, "scaleX", 1);
+			data.scaleY = getValue(boneMap, "scaleY", 1);
+			data.shearX = getValue(boneMap, "shearX", 0);
+			data.shearY = getValue(boneMap, "shearY", 0);
+			data.transformMode = TransformMode[getValue(boneMap, "transform", "normal")]
+			data.skinRequired = getValue(boneMap, "skin", false)
+
+			table_insert(skeletonData.bones, data)
+		end
+
+		-- Slots.
+		if root["slots"] then
+			for i,slotMap in ipairs(root["slots"]) do
+				local slotName = slotMap["name"]
+				local boneName = slotMap["bone"]
+				local boneData = skeletonData:findBone(boneName)
+				if not boneData then error("Slot bone not found: " .. boneName) end
+				local data = SlotData.new(i, slotName, boneData)
+
+				local color = slotMap["color"]
+				if color then
+					data.color:set(tonumber(color:sub(1, 2), 16) / 255,
+						tonumber(color:sub(3, 4), 16) / 255,
+						tonumber(color:sub(5, 6), 16) / 255,
+						tonumber(color:sub(7, 8), 16) / 255)
+				end
+
+				local dark = slotMap["dark"]
+				if dark then
+					data.darkColor = Color.newWith(1, 1, 1, 1)
+					data.darkColor:set(tonumber(dark:sub(1, 2), 16) / 255,
+						tonumber(dark:sub(3, 4), 16) / 255,
+						tonumber(dark:sub(5, 6), 16) / 255,
+						0)
+				end
+
+				data.attachmentName = getValue(slotMap, "attachment", nil)
+				data.blendMode = BlendMode[getValue(slotMap, "blend", "normal")]
+
+				table_insert(skeletonData.slots, data)
+				skeletonData.slotNameIndices[data.name] = #skeletonData.slots
+			end
+		end
+
+		-- IK constraints.
+		if root["ik"] then
+			for _,constraintMap in ipairs(root["ik"]) do
+				local data = IkConstraintData.new(constraintMap["name"])
+				data.order = getValue(constraintMap, "order", 0)
+				data.skinRequired = getValue(constraintMap, "skin", false)
+
+				for _,boneName in ipairs(constraintMap["bones"]) do
+					local bone = skeletonData:findBone(boneName)
+					if not bone then error("IK 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("Target bone not found: " .. targetName) end
+
+				data.mix = getValue(constraintMap, "mix", 1)
+				data.softness = getValue(constraintMap, "softness", 0) * scale
+				if constraintMap["bendPositive"] == nil or constraintMap["bendPositive"] == true then
+					data.bendDirection = 1
+				else
+					data.bendDirection = -1
+				end
+				if constraintMap["compress"] == nil or constraintMap["compress"] == false then data.compress = false else data.compress = true end
+				if constraintMap["stretch"] == nil	or constraintMap["stretch"] == false then data.stretch = false else data.stretch = true end
+				if constraintMap["uniform"] == nil or	constraintMap["uniform"] == false then data.uniform = false else data.uniform = true end
+
+				table_insert(skeletonData.ikConstraints, data)
+			end
+		end
+
+		-- Transform constraints
+		if root["transform"] then
+			for _,constraintMap in ipairs(root["transform"]) do
+				local data = TransformConstraintData.new(constraintMap.name)
+				data.order = getValue(constraintMap, "order", 0)
+				data.skinRequired = getValue(constraintMap, "skin", false)
+
+				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
+					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
+
+				data.local_ = getValue(constraintMap, "local", false)
+				data.relative = getValue(constraintMap, "relative", false)
+				data.offsetRotation = getValue(constraintMap, "rotation", 0);
+				data.offsetX = getValue(constraintMap, "x", 0) * scale;
+				data.offsetY = getValue(constraintMap, "y", 0) * scale;
+				data.offsetScaleX = getValue(constraintMap, "scaleX", 0);
+				data.offsetScaleY = getValue(constraintMap, "scaleY", 0);
+				data.offsetShearY = getValue(constraintMap, "shearY", 0);
+
+				data.rotateMix = getValue(constraintMap, "rotateMix", 1);
+				data.translateMix = getValue(constraintMap, "translateMix", 1);
+				data.scaleMix = getValue(constraintMap, "scaleMix", 1);
+				data.shearMix = getValue(constraintMap, "shearMix", 1);
+
+				table_insert(skeletonData.transformConstraints, data)
+			end
+		end
+
+		-- Path constraints
+		if root["path"] then
+			for _,constraintMap in ipairs(root.path) do
+				local data = PathConstraintData.new(constraintMap.name);
+				data.order = getValue(constraintMap, "order", 0)
+				data.skinRequired = getValue(constraintMap, "skin", false)
+
+				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
+					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
+
+				data.positionMode = PathConstraintData.PositionMode[getValue(constraintMap, "positionMode", "percent"):lower()]
+				data.spacingMode = PathConstraintData.SpacingMode[getValue(constraintMap, "spacingMode", "length"):lower()]
+				data.rotateMode = PathConstraintData.RotateMode[getValue(constraintMap, "rotateMode", "tangent"):lower()]
+				data.offsetRotation = getValue(constraintMap, "rotation", 0);
+				data.position = getValue(constraintMap, "position", 0);
+				if data.positionMode == PathConstraintData.PositionMode.fixed then data.position = data.position * scale end
+				data.spacing = getValue(constraintMap, "spacing", 0);
+				if data.spacingMode == PathConstraintData.SpacingMode.length or data.spacingMode == PathConstraintData.SpacingMode.fixed then data.spacing = data.spacing * scale end
+				data.rotateMix = getValue(constraintMap, "rotateMix", 1);
+				data.translateMix = getValue(constraintMap, "translateMix", 1);
+
+				table_insert(skeletonData.pathConstraints, data)
+			end
+		end
+
+		-- Skins.
+		if root["skins"] then
+			for skinName,skinMap in pairs(root["skins"]) do
+				local skin = Skin.new(skinMap["name"])
+				
+				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
+						table_insert(skin.bones, bone)
+					end
+				end
+				
+				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
+						table_insert(skin.constraints, constraint)
+					end
+				end
+				
+				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
+						table_insert(skin.constraints, constraint)
+					end
+				end
+				
+				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
+						table_insert(skin.constraints, constraint)
+					end
+				end
+				
+				for slotName,slotMap in pairs(skinMap.attachments) do
+					local slotIndex = skeletonData.slotNameIndices[slotName]
+					for attachmentName,attachmentMap in pairs(slotMap) do
+						local attachment = readAttachment(attachmentMap, skin, slotIndex, attachmentName, skeletonData)
+						if attachment then
+							skin:setAttachment(slotIndex, attachmentName, attachment)
+						end
+					end
+				end
+				table_insert(skeletonData.skins, skin)
+				if skin.name == "default" then skeletonData.defaultSkin = skin end
+			end
+		end
+
+		-- Linked meshes
+		for _, linkedMesh in ipairs(self.linkedMeshes) do
+			local skin = skeletonData.defaultSkin
+			if linkedMesh.skin then skin = skeletonData:findSkin(linkedMesh.skin) end
+			if not skin then error("Skin not found: " .. linkedMesh.skin) end
+			local parent = skin:getAttachment(linkedMesh.slotIndex, linkedMesh.parent)
+			if not parent then error("Parent mesh not found: " + linkedMesh.parent) end
+			if linkedMesh.inheritDeform then
+				linkedMesh.mesh.deformAttachment = parent
+			else
+				linkedMesh.mesh.deformAttachment = linkedMesh.mesh
+			end
+
+			linkedMesh.mesh:setParentMesh(parent)
+			linkedMesh.mesh:updateUVs()
+		end
+		self.linkedMeshes = {}
+
+		-- Events.
+		if root["events"] then
+			for eventName,eventMap in pairs(root["events"]) do
+				local data = EventData.new(eventName)
+				data.intValue = getValue(eventMap, "int", 0)
+				data.floatValue = getValue(eventMap, "float", 0)
+				data.stringValue = getValue(eventMap, "string", "")
+				data.audioPath = getValue(eventMap, "audio", nil)
+				if data.audioPath ~= nil then
+					data.volume = getValue(eventMap, "volume", 1)
+					data.balance = getValue(eventMap, "balance", 0)
+				end
+				table_insert(skeletonData.events, data)
+			end
+		end
+
+		-- Animations.
+		if root["animations"] then
+			for animationName,animationMap in pairs(root["animations"]) do
+				readAnimation(animationMap, animationName, skeletonData)
+			end
+		end
+
+		return skeletonData
+	end
+
+	readAttachment = function (map, skin, slotIndex, name, skeletonData)
+		local scale = self.scale
+		name = getValue(map, "name", name)
+
+		local type = AttachmentType[getValue(map, "type", "region")]
+		local path = getValue(map, "path", name)
+
+		if type == AttachmentType.region then
+			local region = attachmentLoader:newRegionAttachment(skin, name, path)
+			if not region then return nil end
+			region.path = path
+			region.x = getValue(map, "x", 0) * scale
+			region.y = getValue(map, "y", 0) * scale
+			region.scaleX = getValue(map, "scaleX", 1);
+			region.scaleY = getValue(map, "scaleY", 1);
+			region.rotation = getValue(map, "rotation", 0);
+			region.width = map.width * scale;
+			region.height = map.height * scale;
+
+			local color = map["color"]
+			if color then
+				region.color:set(tonumber(color:sub(1, 2), 16) / 255,
+					tonumber(color:sub(3, 4), 16) / 255,
+					tonumber(color:sub(5, 6), 16) / 255,
+					tonumber(color:sub(7, 8), 16) / 255)
+			end
+
+			region:updateOffset()
+			return region
+
+		elseif type == AttachmentType.boundingbox then
+			local box = attachmentLoader:newBoundingBoxAttachment(skin, name)
+			if not box then return nil end
+			readVertices(map, box, map.vertexCount * 2)
+			local color = map.color
+			if color then
+				box.color:set(tonumber(color:sub(1, 2), 16) / 255,
+					tonumber(color:sub(3, 4), 16) / 255,
+					tonumber(color:sub(5, 6), 16) / 255,
+					tonumber(color:sub(7, 8), 16) / 255)
+			end
+			return box
+
+		elseif type == AttachmentType.mesh or type == AttachmentType.linkedmesh then
+			local mesh = attachmentLoader:newMeshAttachment(skin, name, path)
+			if not mesh then return nil end
+			mesh.path = path
+
+			local color = map.color
+			if color then
+				mesh.color:set(tonumber(color:sub(1, 2), 16) / 255,
+					tonumber(color:sub(3, 4), 16) / 255,
+					tonumber(color:sub(5, 6), 16) / 255,
+					tonumber(color:sub(7, 8), 16) / 255)
+			end
+
+			mesh.width = getValue(map, "width", 0) * scale
+			mesh.height = getValue(map, "height", 0) * scale
+
+			local parent = map.parent
+			if parent then
+				table_insert(self.linkedMeshes, {
+					mesh = mesh,
+					skin = getValue(map, "skin", nil),
+					slotIndex = slotIndex,
+					parent = parent,
+					inheritDeform = getValue(map, "deform", true)
+				})
+				return mesh
+			end
+
+			local uvs = getArray(map, "uvs", 1)
+			readVertices(map, mesh, #uvs)
+			mesh.triangles = getArray(map, "triangles", 1)
+			-- adjust triangle indices by 1, vertices are one-indexed
+			for i,v in ipairs(mesh.triangles) do
+				mesh.triangles[i] = v + 1
+			end
+			mesh.regionUVs = uvs
+			mesh:updateUVs()
+
+			mesh.hullLength = getValue(map, "hull", 0) * 2
+			return mesh
+
+		elseif type == AttachmentType.path then
+			local path = self.attachmentLoader:newPathAttachment(skin, name)
+			if not path then return nil end
+			path.closed = getValue(map, "closed", false)
+			path.constantSpeed = getValue(map, "constantSpeed", true)
+
+			local vertexCount = map.vertexCount
+			readVertices(map, path, vertexCount * 2)
+
+			local lengths = utils.newNumberArray(vertexCount / 3, 0)
+			for i,v in ipairs(map.lengths) do
+				lengths[i] = v * scale
+			end
+			path.lengths = lengths
+
+			local color = map.color
+			if color then
+				path.color:set(tonumber(color:sub(1, 2), 16) / 255,
+					tonumber(color:sub(3, 4), 16) / 255,
+					tonumber(color:sub(5, 6), 16) / 255,
+					tonumber(color:sub(7, 8), 16) / 255)
+			end
+			return path;
+
+		elseif type == AttachmentType.point then
+			local point = self.attachmentLoader:newPointAttachment(skin, name)
+			if not point then return nil end
+			point.x = getValue(map, "x", 0) * scale
+			point.y = getValue(map, "y", 0) * scale
+			point.rotation = getValue(map, "rotation", 0)
+
+			local color = map.color
+			if color then
+				path.color:set(tonumber(color:sub(1, 2), 16) / 255,
+					tonumber(color:sub(3, 4), 16) / 255,
+					tonumber(color:sub(5, 6), 16) / 255,
+					tonumber(color:sub(7, 8), 16) / 255)
+			end
+			return point
+
+		elseif type == AttachmentType.clipping then
+			local clip = attachmentLoader:newClippingAttachment(skin, name)
+			if not clip then return nil end
+
+			local _end = getValue(map, "end", nil)
+			if _end then
+				local slot = skeletonData:findSlot(_end)
+				if not slot then error("Clipping end slot not found: " + _end) end
+				clip.endSlot = slot
+			end
+
+			readVertices(map, clip, map.vertexCount * 2)
+			local color = map.color
+			if color then
+				clip.color:set(tonumber(color:sub(1, 2), 16) / 255,
+					tonumber(color:sub(3, 4), 16) / 255,
+					tonumber(color:sub(5, 6), 16) / 255,
+					tonumber(color:sub(7, 8), 16) / 255)
+			end
+			return clip
+		end
+
+		error("Unknown attachment type: " .. type .. " (" .. name .. ")")
+	end
+
+	readVertices = function (map, attachment, verticesLength)
+		local scale = self.scale
+		attachment.worldVerticesLength = verticesLength
+		local vertices = getArray(map, "vertices", 1)
+		if verticesLength == #vertices then
+			if scale ~= 1 then
+				local i = 0
+				local n = #vertices
+				while i < n do
+					vertices[i + 1] = vertices[i + 1] * scale
+					i = i + 1
+				end
+			end
+			attachment.vertices = vertices
+			return
+		end
+
+		local weights = {}
+		local bones = {}
+		local i = 0
+		local n = #vertices
+		while i < n do
+			local boneCount = vertices[i + 1]
+			i = i + 1
+			table_insert(bones, boneCount)
+			local nn = i + boneCount * 4
+			while i < nn do
+				table_insert(bones, vertices[i + 1] + 1) -- +1 because bones are one-indexed
+				table_insert(weights, vertices[i + 2] * scale)
+				table_insert(weights, vertices[i + 3] * scale)
+				table_insert(weights, vertices[i + 4])
+				i = i + 4
+			end
+		end
+		attachment.bones = bones
+		attachment.vertices = weights
+	end
+
+	readAnimation = function (map, name, skeletonData)
+		local timelines = {}
+		local duration = 0
+		local scale = self.scale
+
+		-- Slot timelines
+		local slotsMap = map["slots"]
+		if slotsMap then
+			for slotName,timelineMap in pairs(slotsMap) do
+				local slotIndex = skeletonData.slotNameIndices[slotName]
+
+				for timelineName,values in pairs(timelineMap) do
+					if timelineName == "color" then
+						local timeline = Animation.ColorTimeline.new(#values)
+						timeline.slotIndex = slotIndex
+
+						local frameIndex = 0
+						for _,valueMap in ipairs(values) do
+							local color = valueMap["color"]
+							timeline:setFrame(
+								frameIndex, getValue(valueMap, "time", 0),
+								tonumber(color:sub(1, 2), 16) / 255,
+								tonumber(color:sub(3, 4), 16) / 255,
+								tonumber(color:sub(5, 6), 16) / 255,
+								tonumber(color:sub(7, 8), 16) / 255
+							)
+							readCurve(valueMap, timeline, frameIndex)
+							frameIndex = frameIndex + 1
+						end
+						table_insert(timelines, timeline)
+						duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.ColorTimeline.ENTRIES])
+					elseif timelineName == "twoColor" then
+						local timeline = Animation.TwoColorTimeline.new(#values)
+						timeline.slotIndex = slotIndex
+
+						local frameIndex = 0
+						for _,valueMap in ipairs(values) do
+							local light = valueMap["light"]
+							local dark = valueMap["dark"]
+							timeline:setFrame(
+								frameIndex, getValue(valueMap, "time", 0),
+								tonumber(light:sub(1, 2), 16) / 255,
+								tonumber(light:sub(3, 4), 16) / 255,
+								tonumber(light:sub(5, 6), 16) / 255,
+								tonumber(light:sub(7, 8), 16) / 255,
+								tonumber(dark:sub(1, 2), 16) / 255,
+								tonumber(dark:sub(3, 4), 16) / 255,
+								tonumber(dark:sub(5, 6), 16) / 255
+							)
+							readCurve(valueMap, timeline, frameIndex)
+							frameIndex = frameIndex + 1
+						end
+						table_insert(timelines, timeline)
+						duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.TwoColorTimeline.ENTRIES])
+					elseif timelineName == "attachment" then
+						local timeline = Animation.AttachmentTimeline.new(#values)
+						timeline.slotIndex = slotIndex
+
+						local frameIndex = 0
+						for _,valueMap in ipairs(values) do
+							local attachmentName = valueMap["name"]
+							timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), attachmentName)
+							frameIndex = frameIndex + 1
+						end
+						table_insert(timelines, timeline)
+						duration = math.max(duration, timeline.frames[timeline:getFrameCount() - 1])
+
+					else
+						error("Invalid frame type for a slot: " .. timelineName .. " (" .. slotName .. ")")
+					end
+				end
+			end
+		end
+
+		-- Bone timelines
+		local bonesMap = map["bones"]
+		if bonesMap then
+			for boneName,timelineMap in pairs(bonesMap) do
+				local boneIndex = skeletonData:findBoneIndex(boneName)
+				if boneIndex == -1 then error("Bone not found: " .. boneName) end
+
+				for timelineName,values in pairs(timelineMap) do
+					if timelineName == "rotate" then
+						local timeline = Animation.RotateTimeline.new(#values)
+						timeline.boneIndex = boneIndex
+
+						local frameIndex = 0
+						for _,valueMap in ipairs(values) do
+							timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), getValue(valueMap, "angle", 0))
+							readCurve(valueMap, timeline, frameIndex)
+							frameIndex = frameIndex + 1
+						end
+						table_insert(timelines, timeline)
+						duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.RotateTimeline.ENTRIES])
+
+					elseif timelineName == "translate" or timelineName == "scale" or timelineName == "shear" then
+						local timeline
+						local timelineScale = 1
+						local defaultValue = 0
+						if timelineName == "scale" then
+							timeline = Animation.ScaleTimeline.new(#values)
+							defaultValue = 1
+						elseif timelineName == "shear" then
+							timeline = Animation.ShearTimeline.new(#values)
+						else
+							timeline = Animation.TranslateTimeline.new(#values)
+							timelineScale = self.scale
+						end
+						timeline.boneIndex = boneIndex
+
+						local frameIndex = 0
+						for _,valueMap in ipairs(values) do
+							local x = getValue(valueMap, "x", defaultValue) * timelineScale
+							local y = getValue(valueMap, "y", defaultValue) * timelineScale
+							timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), x, y)
+							readCurve(valueMap, timeline, frameIndex)
+							frameIndex = frameIndex + 1
+						end
+						table_insert(timelines, timeline)
+						duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.TranslateTimeline.ENTRIES])
+					else
+						error("Invalid timeline type for a bone: " .. timelineName .. " (" .. boneName .. ")")
+					end
+				end
+			end
+		end
+
+		-- IK timelines.
+		local ik = map["ik"]
+		if ik then
+			for ikConstraintName,values in pairs(ik) do
+				local ikConstraint = skeletonData:findIkConstraint(ikConstraintName)
+				local timeline = Animation.IkConstraintTimeline.new(#values)
+				for i,other in pairs(skeletonData.ikConstraints) do
+					if other == ikConstraint then
+						timeline.ikConstraintIndex = i
+						break
+					end
+				end
+				local frameIndex = 0
+				for _,valueMap in ipairs(values) do
+					local mix = 1
+					local softness = 0
+					if valueMap["mix"] ~= nil then mix = valueMap["mix"] end
+					if valueMap["softness"] ~= nil then softness = valueMap["softness"] * scale end
+					local bendPositive = 1
+					if valueMap["bendPositive"] == false then bendPositive = -1 end
+					local stretch = false
+					if valueMap["stretch"] ~= nil then stretch = valueMap["stretch"] end
+					local compress = false
+					if valueMap["compress"] ~= nil then compress = valueMap["compress"] end
+					timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), mix, softness, bendPositive, compress, stretch)
+					readCurve(valueMap, timeline, frameIndex)
+					frameIndex = frameIndex + 1
+				end
+				table_insert(timelines, timeline)
+				duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.IkConstraintTimeline.ENTRIES])
+			end
+		end
+
+		-- Transform constraint timelines.
+		local transform = map["transform"]
+		if transform then
+			for constraintName, values in pairs(transform) do
+				local constraint = skeletonData:findTransformConstraint(constraintName)
+				local timeline = Animation.TransformConstraintTimeline.new(#values)
+				for i,other in pairs(skeletonData.transformConstraints) do
+					if other == constraint then
+						timeline.transformConstraintIndex = i
+						break
+					end
+				end
+				local frameIndex = 0
+				for _,valueMap in ipairs(values) do
+					timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), getValue(valueMap, "rotateMix", 1), getValue(valueMap, "translateMix", 1), getValue(valueMap, "scaleMix", 1), getValue(valueMap, "shearMix", 1))
+					readCurve(valueMap, timeline, frameIndex)
+					frameIndex = frameIndex + 1
+				end
+				table_insert(timelines, timeline)
+				duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.TransformConstraintTimeline.ENTRIES])
+			end
+		end
+
+		-- Path constraint timelines.
+		if map.path then
+			for constraintName,constraintMap in pairs(map.path) do
+				local index = skeletonData:findPathConstraintIndex(constraintName)
+				if index == -1 then error("Path constraint not found: " .. constraintName, 2) end
+				local data = skeletonData.pathConstraints[index]
+				for timelineName, timelineMap in pairs(constraintMap) do
+					if timelineName == "position" or timelineName == "spacing" then
+						local timeline = nil
+						local timelineScale = 1
+						if timelineName == "spacing" then
+							timeline = Animation.PathConstraintSpacingTimeline.new(#timelineMap)
+							if data.spacingMode == PathConstraintData.SpacingMode.length or data.spacingMode == PathConstraintData.SpacingMode.fixed then timelineScale = scale end
+						else
+							timeline = Animation.PathConstraintPositionTimeline.new(#timelineMap)
+							if data.positionMode == PathConstraintData.PositionMode.fixed then timelineScale = scale end
+						end
+						timeline.pathConstraintIndex = index
+						local frameIndex = 0
+						for _,valueMap in ipairs(timelineMap) do
+							timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), getValue(valueMap, timelineName, 0) * timelineScale)
+							readCurve(valueMap, timeline, frameIndex)
+							frameIndex = frameIndex + 1
+						end
+						table_insert(timelines, timeline)
+						duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.PathConstraintPositionTimeline.ENTRIES])
+					elseif timelineName == "mix" then
+						local timeline = Animation.PathConstraintMixTimeline.new(#timelineMap)
+						timeline.pathConstraintIndex = index
+						local frameIndex = 0
+						for _,valueMap in ipairs(timelineMap) do
+							timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), getValue(valueMap, "rotateMix", 1), getValue(valueMap, "translateMix", 1))
+							readCurve(valueMap, timeline, frameIndex)
+							frameIndex = frameIndex + 1
+						end
+						table_insert(timelines, timeline)
+						duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.PathConstraintMixTimeline.ENTRIES])
+					end
+				end
+			end
+		end
+
+		-- Deform timelines.
+		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
+				for slotName,slotMap in pairs(deformMap) do
+					local slotIndex = skeletonData:findSlotIndex(slotName)
+					if slotIndex == -1 then error("Slot not found: " .. slotMap.name, 2) end
+					for timelineName,timelineMap in pairs(slotMap) do
+						local attachment = skin:getAttachment(slotIndex, timelineName)
+						if not attachment then error("Deform attachment not found: " .. timelineMap.name, 2) end
+						local weighted = attachment.bones ~= nil
+						local vertices = attachment.vertices;
+						local deformLength = #vertices
+						if weighted then deformLength = math.floor(#vertices / 3) * 2 end
+
+						local timeline = Animation.DeformTimeline.new(#timelineMap)
+						timeline.slotIndex = slotIndex
+						timeline.attachment = attachment
+
+						local frameIndex = 0
+						for _,valueMap in ipairs(timelineMap) do
+							local deform = nil
+							local verticesValue = getValue(valueMap, "vertices", nil)
+							if verticesValue == nil then
+								deform = vertices
+								if weighted then deform = utils.newNumberArray(deformLength) end
+							else
+								deform = utils.newNumberArray(deformLength)
+								local start = getValue(valueMap, "offset", 0) + 1
+								utils.arrayCopy(verticesValue, 1, deform, start, #verticesValue)
+								if scale ~= 1 then
+									local i = start
+									local n = i + #verticesValue
+									while i < n do
+										deform[i] = deform[i] * scale
+										i = i + 1
+									end
+								end
+								if not weighted then
+									local i = 1
+									local n = i + deformLength
+									while i < n do
+										deform[i] = deform[i] + vertices[i]
+										i = i + 1
+									end
+								end
+							end
+
+							timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), deform)
+							readCurve(valueMap, timeline, frameIndex)
+							frameIndex = frameIndex + 1
+						end
+						table_insert(timelines, timeline)
+						duration = math.max(duration, timeline.frames[timeline:getFrameCount() - 1])
+					end
+				end
+			end
+		end
+
+		-- Draworder timeline.
+		local drawOrderValues = map["drawOrder"]
+		if not drawOrderValues then drawOrderValues = map["draworder"] end
+		if drawOrderValues then
+			local timeline = Animation.DrawOrderTimeline.new(#drawOrderValues)
+			local slotCount = #skeletonData.slots
+			local frameIndex = 0
+			for _,drawOrderMap in ipairs(drawOrderValues) do
+				local drawOrder = nil
+				local offsets = drawOrderMap["offsets"]
+				if offsets then
+					drawOrder = {}
+					local unchanged = {}
+					local originalIndex = 1
+					local unchangedIndex = 1
+					for _,offsetMap in ipairs(offsets) do
+						local slotIndex = skeletonData:findSlotIndex(offsetMap["slot"])
+						if slotIndex == -1 then error("Slot not found: " .. offsetMap["slot"]) end
+						-- Collect unchanged items.
+						while originalIndex ~= slotIndex do
+							unchanged[unchangedIndex] = originalIndex
+							unchangedIndex = unchangedIndex + 1
+							originalIndex = originalIndex + 1
+						end
+						-- Set changed items.
+						drawOrder[originalIndex + offsetMap["offset"]] = originalIndex
+						originalIndex = originalIndex + 1
+					end
+					-- Collect remaining unchanged items.
+					while originalIndex <= slotCount do
+						unchanged[unchangedIndex] = originalIndex
+						unchangedIndex = unchangedIndex + 1
+						originalIndex = originalIndex + 1
+					end
+					-- Fill in unchanged items.
+					for ii = slotCount, 1, -1 do
+						if not drawOrder[ii] then
+							unchangedIndex = unchangedIndex - 1
+							drawOrder[ii] = unchanged[unchangedIndex]
+						end
+					end
+				end
+				timeline:setFrame(frameIndex, getValue(drawOrderMap, "time", 0), drawOrder)
+				frameIndex = frameIndex + 1
+			end
+			table_insert(timelines, timeline)
+			duration = math.max(duration, timeline.frames[timeline:getFrameCount() - 1])
+		end
+
+		-- Event timeline.
+		local events = map["events"]
+		if events then
+			local timeline = Animation.EventTimeline.new(#events)
+			local frameIndex = 0
+			for _,eventMap in ipairs(events) do
+				local eventData = skeletonData:findEvent(eventMap["name"])
+				if not eventData then error("Event not found: " .. eventMap["name"]) end
+				local event = Event.new(getValue(eventMap, "time", 0), eventData)
+				if eventMap["int"] ~= nil then
+					event.intValue = eventMap["int"]
+				else
+					event.intValue = eventData.intValue
+				end
+				if eventMap["float"] ~= nil then
+					event.floatValue = eventMap["float"]
+				else
+					event.floatValue = eventData.floatValue
+				end
+				if eventMap["string"] ~= nil then
+					event.stringValue = eventMap["string"]
+				else
+					event.stringValue = eventData.stringValue
+				end
+				if eventData.audioPath ~= nil then
+					event.volume = getValue(eventMap, "volume", 1)
+					event.balance = getValue(eventMap, "balance", 0)
+				end
+				timeline:setFrame(frameIndex, event)
+				frameIndex = frameIndex + 1
+			end
+			table_insert(timelines, timeline)
+			duration = math.max(duration, timeline.frames[timeline:getFrameCount() - 1])
+		end
+
+		table_insert(skeletonData.animations, Animation.new(name, timelines, duration))
+	end
+
+	readCurve = function (map, timeline, frameIndex)
+		local curve = map["curve"]
+		if not curve then return end
+		if curve == "stepped" then
+			timeline:setStepped(frameIndex)
+		else
+			timeline:setCurve(frameIndex, getValue(map, "curve", 0), getValue(map, "c2", 0), getValue(map, "c3", 1), getValue(map, "c4", 1))
+		end
+	end
+
+	getArray = function (map, name, scale)
+		local list = map[name]
+		local values = {}
+		if scale == 1 then
+			for i = 1, #list do
+				values[i] = list[i]
+			end
+		else
+			for i = 1, #list do
+				values[i] = list[i] * scale
+			end
+		end
+		return values
+	end
+
+	return self
+end
+return SkeletonJson

+ 220 - 220
spine-lua/Skin.lua → spine-lua/spine-lua/Skin.lua

@@ -1,220 +1,220 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local setmetatable = setmetatable
-local table_insert = table.insert
-local AttachmentType = require "spine-lua.attachments.AttachmentType"
-
-local SkinEntry = {}
-SkinEntry.__index = SkinEntry
-
-function SkinEntry.new (slotIndex, name, attachment)
-	local self = {
-		slotIndex = slotIndex,
-		name = name,
-		attachment = attachment
-	}
-	setmetatable(self, SkinEntry)
-
-	return self
-end
-
-local Skin = {}
-Skin.__index = Skin
-
-function Skin.new (name)
-	if not name then error("name cannot be nil", 2) end
-
-	local self = {
-		name = name,
-		attachments = {},
-		bones = {},
-		constraints = {}
-	}
-	setmetatable(self, Skin)
-
-	return self
-end
-
-function Skin:setAttachment (slotIndex, name, attachment)
-	if not name then error("name cannot be nil.", 2) end
-	if not self.attachments[slotIndex] then self.attachments[slotIndex] = {} end
-	self.attachments[slotIndex][name] = attachment
-end
-
-function Skin:addSkin (skin)
-	for i, bone in ipairs(skin.bones) do
-		local contained = false
-		for j, otherBone in ipairs(self.bones) do
-			if otherBone == bone then
-			contained = true
-			break
-			end
-		end
-		if not contained then table_insert(self.bones, bone) end
-	end
-
-	for i, constraint in ipairs(skin.constraints) do
-		local contained = false
-		for j, otherConstraint in ipairs(self.constraints) do
-			if otherConstraint == constraint then
-				contained = true
-				break
-			end
-		end
-		if not contained then table_insert(self.constraints, constraint) end
-	end
-
-	local attachments = skin:getAttachments()
-	for i, entry in ipairs(attachments) do
-		self:setAttachment(entry.slotIndex, entry.name, entry.attachment)
-	end
-end
-
-function Skin:copySkin (skin)
-	for i, bone in ipairs(skin.bones) do
-		local contained = false
-		for j, otherBone in ipairs(self.bones) do
-			if otherBone == bone then
-			contained = true
-			break
-			end
-		end
-		if not contained then table_insert(self.bones, bone) end
-	end
-
-	for i, constraint in ipairs(skin.constraints) do
-		local contained = false
-		for j, otherConstraint in ipairs(self.constraints) do
-			if otherConstraint == constraint then
-			contained = true
-			break
-			end
-		end
-		if not contained then table_insert(self.constraints, constraint) end
-	end
-
-	local attachments = skin:getAttachments()
-	for i, entry in ipairs(attachments) do
-		if entry.attachment.type == AttachmentType.mesh then
-			entry.attachment = entry.attachment:newLinkedMesh()
-			self:setAttachment(entry.slotIndex, entry.name, entry.attachment)
-		else
-			entry.attachment = entry.attachment:copy()
-			self:setAttachment(entry.slotIndex, entry.name, entry.attachment)
-		end
-	end
-end
-
-function Skin:getAttachment (slotIndex, name)
-	if not name then error("name cannot be nil.", 2) end
-	local dictionary = self.attachments[slotIndex]
-	if dictionary then
-		return dictionary[name]
-	else
-		return nil
-	end
-end
-
-function Skin:removeAttachment (slotIndex, name)
-	local slotAttachments = self.attachments[slotIndex]
-	if slotAttachments then
-		slotAttachments[name] = nil
-	end
-end
-
-function Skin:getAttachments ()
-	local entries = {}
-	for slotIndex, slotAttachments in pairs(self.attachments) do
-		if slotAttachments then
-			for name, attachment in pairs(slotAttachments) do
-				if attachment then
-					table_insert(entries, SkinEntry.new(slotIndex, name, attachment))
-				end
-			end
-		end
-	end
-	return entries
-end
-
-function Skin:getAttachmentsForSlot (slotIndex)
-	local entries = {}
-	local slotAttachments = self.attachments[slotIndex]
-	if slotAttachments then
-		for name, attachment in pairs(slotAttachments) do
-			if attachment then
-				table_insert(entries, SkinEntry.new(slotIndex, name, attachment))
-			end
-		end
-	end
-	return entries
-end
-
-function Skin:clear ()
-	self.attachments = {}
-	self.bones = {}
-	self.constraints = {}
-end
-
-function Skin:attachAll(skeleton, oldSkin)
-	for i, slot in ipairs(skeleton.slots) do
-		local slotAttachment = slot.attachment
-		if slotAttachment then
-			local dictionary = oldSkin.attachments[i]
-			if (dictionary) then
-				for key, value in pairs(dictionary) do
-					local skinAttachment = value
-					if slotAttachment == skinAttachment then
-						local attachment = self:getAttachment(i, key)
-						if attachment then
-							slot:setAttachment(attachment)
-						end
-						break
-					end
-				end
-			end
-		end
-	end
-end
-
-function Skin:findNamesForSlot (slotIndex)
-	local names = {}
-	for _,v in self.attachments do
-		if v[1] == slotIndex then table_insert(names, v[2]) end
-	end
-end
-
-function Skin:findAttachmentsForSlot (slotIndex)
-	local attachments = {}
-	for _,v in self.attachments do
-		if v[1] == slotIndex then table_insert(attachments, v[3]) end
-	end
-end
-
-return Skin
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+local table_insert = table.insert
+local AttachmentType = require "spine-lua.attachments.AttachmentType"
+
+local SkinEntry = {}
+SkinEntry.__index = SkinEntry
+
+function SkinEntry.new (slotIndex, name, attachment)
+	local self = {
+		slotIndex = slotIndex,
+		name = name,
+		attachment = attachment
+	}
+	setmetatable(self, SkinEntry)
+
+	return self
+end
+
+local Skin = {}
+Skin.__index = Skin
+
+function Skin.new (name)
+	if not name then error("name cannot be nil", 2) end
+
+	local self = {
+		name = name,
+		attachments = {},
+		bones = {},
+		constraints = {}
+	}
+	setmetatable(self, Skin)
+
+	return self
+end
+
+function Skin:setAttachment (slotIndex, name, attachment)
+	if not name then error("name cannot be nil.", 2) end
+	if not self.attachments[slotIndex] then self.attachments[slotIndex] = {} end
+	self.attachments[slotIndex][name] = attachment
+end
+
+function Skin:addSkin (skin)
+	for i, bone in ipairs(skin.bones) do
+		local contained = false
+		for j, otherBone in ipairs(self.bones) do
+			if otherBone == bone then
+			contained = true
+			break
+			end
+		end
+		if not contained then table_insert(self.bones, bone) end
+	end
+
+	for i, constraint in ipairs(skin.constraints) do
+		local contained = false
+		for j, otherConstraint in ipairs(self.constraints) do
+			if otherConstraint == constraint then
+				contained = true
+				break
+			end
+		end
+		if not contained then table_insert(self.constraints, constraint) end
+	end
+
+	local attachments = skin:getAttachments()
+	for i, entry in ipairs(attachments) do
+		self:setAttachment(entry.slotIndex, entry.name, entry.attachment)
+	end
+end
+
+function Skin:copySkin (skin)
+	for i, bone in ipairs(skin.bones) do
+		local contained = false
+		for j, otherBone in ipairs(self.bones) do
+			if otherBone == bone then
+			contained = true
+			break
+			end
+		end
+		if not contained then table_insert(self.bones, bone) end
+	end
+
+	for i, constraint in ipairs(skin.constraints) do
+		local contained = false
+		for j, otherConstraint in ipairs(self.constraints) do
+			if otherConstraint == constraint then
+			contained = true
+			break
+			end
+		end
+		if not contained then table_insert(self.constraints, constraint) end
+	end
+
+	local attachments = skin:getAttachments()
+	for i, entry in ipairs(attachments) do
+		if entry.attachment.type == AttachmentType.mesh then
+			entry.attachment = entry.attachment:newLinkedMesh()
+			self:setAttachment(entry.slotIndex, entry.name, entry.attachment)
+		else
+			entry.attachment = entry.attachment:copy()
+			self:setAttachment(entry.slotIndex, entry.name, entry.attachment)
+		end
+	end
+end
+
+function Skin:getAttachment (slotIndex, name)
+	if not name then error("name cannot be nil.", 2) end
+	local dictionary = self.attachments[slotIndex]
+	if dictionary then
+		return dictionary[name]
+	else
+		return nil
+	end
+end
+
+function Skin:removeAttachment (slotIndex, name)
+	local slotAttachments = self.attachments[slotIndex]
+	if slotAttachments then
+		slotAttachments[name] = nil
+	end
+end
+
+function Skin:getAttachments ()
+	local entries = {}
+	for slotIndex, slotAttachments in pairs(self.attachments) do
+		if slotAttachments then
+			for name, attachment in pairs(slotAttachments) do
+				if attachment then
+					table_insert(entries, SkinEntry.new(slotIndex, name, attachment))
+				end
+			end
+		end
+	end
+	return entries
+end
+
+function Skin:getAttachmentsForSlot (slotIndex)
+	local entries = {}
+	local slotAttachments = self.attachments[slotIndex]
+	if slotAttachments then
+		for name, attachment in pairs(slotAttachments) do
+			if attachment then
+				table_insert(entries, SkinEntry.new(slotIndex, name, attachment))
+			end
+		end
+	end
+	return entries
+end
+
+function Skin:clear ()
+	self.attachments = {}
+	self.bones = {}
+	self.constraints = {}
+end
+
+function Skin:attachAll(skeleton, oldSkin)
+	for i, slot in ipairs(skeleton.slots) do
+		local slotAttachment = slot.attachment
+		if slotAttachment then
+			local dictionary = oldSkin.attachments[i]
+			if (dictionary) then
+				for key, value in pairs(dictionary) do
+					local skinAttachment = value
+					if slotAttachment == skinAttachment then
+						local attachment = self:getAttachment(i, key)
+						if attachment then
+							slot:setAttachment(attachment)
+						end
+						break
+					end
+				end
+			end
+		end
+	end
+end
+
+function Skin:findNamesForSlot (slotIndex)
+	local names = {}
+	for _,v in self.attachments do
+		if v[1] == slotIndex then table_insert(names, v[2]) end
+	end
+end
+
+function Skin:findAttachmentsForSlot (slotIndex)
+	local attachments = {}
+	for _,v in self.attachments do
+		if v[1] == slotIndex then table_insert(attachments, v[3]) end
+	end
+end
+
+return Skin

+ 89 - 89
spine-lua/Slot.lua → spine-lua/spine-lua/Slot.lua

@@ -1,89 +1,89 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local setmetatable = setmetatable
-
-local Color = require "spine-lua.Color"
-
-local Slot = {}
-Slot.__index = Slot
-
-function Slot.new (data, bone)
-	if not data then error("slotData cannot be nil", 2) end
-	if not bone then error("bone cannot be nil", 2) end
-
-	local self = {
-		data = data,
-		bone = bone,
-		color = Color.newWith(1, 1, 1, 1),
-		darkColor = nil,
-		attachment = nil,
-		attachmentTime = 0,
-		attachmentState = 0,
-		deform = {}
-	}
-
-	setmetatable(self, Slot)
-
-	if data.darkColor then self.darkColor = Color.newWith(1, 1, 1, 1) end
-
-	self:setToSetupPose()
-
-	return self
-end
-
-function Slot:setAttachment (attachment)
-	if self.attachment == attachment then return end
-	self.attachment = attachment
-	self.attachmentTime = self.bone.skeleton.time
-	self.deform = {}
-end
-
-function Slot:setAttachmentTime (time)
-	self.attachmentTime = self.bone.skeleton.time - time
-end
-
-function Slot:getAttachmentTime ()
-	return self.bone.skeleton.time - self.attachmentTime
-end
-
-function Slot:setToSetupPose ()
-	local data = self.data
-
-	self.color:setFrom(data.color)
-	if self.darkColor then self.darkColor:setFrom(data.darkColor) end
-
-	local attachment = nil
-	if data.attachmentName then
-		attachment = self.bone.skeleton:getAttachmentByIndex(data.index, data.attachmentName)
-	end
-	self:setAttachment(attachment)
-end
-
-return Slot
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+
+local Color = require "spine-lua.Color"
+
+local Slot = {}
+Slot.__index = Slot
+
+function Slot.new (data, bone)
+	if not data then error("slotData cannot be nil", 2) end
+	if not bone then error("bone cannot be nil", 2) end
+
+	local self = {
+		data = data,
+		bone = bone,
+		color = Color.newWith(1, 1, 1, 1),
+		darkColor = nil,
+		attachment = nil,
+		attachmentTime = 0,
+		attachmentState = 0,
+		deform = {}
+	}
+
+	setmetatable(self, Slot)
+
+	if data.darkColor then self.darkColor = Color.newWith(1, 1, 1, 1) end
+
+	self:setToSetupPose()
+
+	return self
+end
+
+function Slot:setAttachment (attachment)
+	if self.attachment == attachment then return end
+	self.attachment = attachment
+	self.attachmentTime = self.bone.skeleton.time
+	self.deform = {}
+end
+
+function Slot:setAttachmentTime (time)
+	self.attachmentTime = self.bone.skeleton.time - time
+end
+
+function Slot:getAttachmentTime ()
+	return self.bone.skeleton.time - self.attachmentTime
+end
+
+function Slot:setToSetupPose ()
+	local data = self.data
+
+	self.color:setFrom(data.color)
+	if self.darkColor then self.darkColor:setFrom(data.darkColor) end
+
+	local attachment = nil
+	if data.attachmentName then
+		attachment = self.bone.skeleton:getAttachmentByIndex(data.index, data.attachmentName)
+	end
+	self:setAttachment(attachment)
+end
+
+return Slot

+ 57 - 57
spine-lua/SlotData.lua → spine-lua/spine-lua/SlotData.lua

@@ -1,57 +1,57 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local BlendMode = require "spine-lua.BlendMode"
-local Color = require "spine-lua.Color"
-
-local setmetatable = setmetatable
-
-local SlotData = {}
-SlotData.__index = SlotData
-
-function SlotData.new (index, name, boneData)
-	if index < 0 then error("index must be >= 0", 2) end
-	if not name then error("name cannot be nil", 2) end
-	if not boneData then error("boneData cannot be nil", 2) end
-
-	local self = {
-		index = index,
-		name = name,
-		boneData = boneData,
-		color = Color.newWith(1, 1, 1, 1),
-		darkColor = nil,
-		attachmentName = nil,
-		blendMode = BlendMode.normal
-	}
-	setmetatable(self, SlotData)
-
-	return self
-end
-
-return SlotData
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local BlendMode = require "spine-lua.BlendMode"
+local Color = require "spine-lua.Color"
+
+local setmetatable = setmetatable
+
+local SlotData = {}
+SlotData.__index = SlotData
+
+function SlotData.new (index, name, boneData)
+	if index < 0 then error("index must be >= 0", 2) end
+	if not name then error("name cannot be nil", 2) end
+	if not boneData then error("boneData cannot be nil", 2) end
+
+	local self = {
+		index = index,
+		name = name,
+		boneData = boneData,
+		color = Color.newWith(1, 1, 1, 1),
+		darkColor = nil,
+		attachmentName = nil,
+		blendMode = BlendMode.normal
+	}
+	setmetatable(self, SlotData)
+
+	return self
+end
+
+return SlotData

+ 258 - 258
spine-lua/TextureAtlas.lua → spine-lua/spine-lua/TextureAtlas.lua

@@ -1,258 +1,258 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local setmetatable = setmetatable
-local table_insert = table.insert
-local math_abs = math.abs
-
-local TextureAtlasRegion = require "spine-lua.TextureAtlasRegion"
-local TextureWrap = require "spine-lua.TextureWrap"
-local TextureFilter = require "spine-lua.TextureFilter"
-
-local TextureAtlasPage = {}
-TextureAtlasPage.__index = TextureAtlasPage
-
-function TextureAtlasPage.new ()
-	local self = {
-		name = nil,
-		minFilter = nil,
-		magFilter = nil,
-		uWrap = nil,
-		vWrap = nil,
-		texture = nil,
-		width = 0,
-		height = 0
-	}
-	setmetatable(self, TextureAtlasPage)
-	return self
-end
-
-
-local TextureAtlas = {}
-TextureAtlas.__index = TextureAtlas
-
-function TextureAtlas.new (atlasContent, imageLoader)
-	local self = {
-		pages = {},
-		regions = {}
-	}
-	setmetatable(self, TextureAtlas)
-
-	self:parse(atlasContent, imageLoader)
-
-	return self
-end
-
-function TextureAtlas:parse (atlasContent, imageLoader)
-	if not atlasContent then error("atlasContent cannot be nil.", 2) end
-	if not imageLoader then error("imageLoader cannot be nil.", 2) end
-
-	function lineIterator(s)
-		if s:sub(-1)~="\n" then s=s.."\n" end
-		return s:gmatch("(.-)\n")
-	end
-
-	local lines = {}
-	local index = 0
-	local numLines = 0
-	for line in lineIterator(atlasContent) do
-		lines[numLines] = line
-		numLines = numLines + 1
-	end
-
-	local readLine = function ()
-		if index >= numLines then return nil end
-		local line = lines[index]
-		index = index + 1
-		return line
-	end
-
-	local readValue = function ()
-		local line = readLine()
-		local idx = line:find(":")
-		if not idx then error("Invalid line: " .. line, 2) end
-		return line:sub(idx + 1):match'^%s*(.*%S)' or ''
-	end
-
-	local readTuple = function ()
-		local line = readLine()
-		local idx = line:find(":")
-		if not idx then
-			error("Invalid line: " .. line, 2)
-		end
-		local i = 1
-		local lastMatch = idx + 1
-		local tuple = {}
-		while i <= 3 do
-			local comma = line:find(",", lastMatch)
-			if not comma then break end
-			tuple[i] = line:sub(lastMatch, comma - 1):match'^%s*(.*%S)' or ''
-			lastMatch = comma + 1
-			i = i + 1
-		end
-		tuple[i] = line:sub(lastMatch):match'^%s*(.*%S)' or ''
-		return tuple
-	end
-
-	local parseInt = function (str)
-		return tonumber(str)
-	end
-
-	local filterFromString = function (str)
-		str = str:lower()
-		if str == "nearest" then return TextureFilter.Nearest
-		elseif str == "linear" then return TextureFilter.Linear
-		elseif str == "mipmap" then return TextureFilter.MipMap
-		elseif str == "mipmapnearestnearest" then return TextureFilter.MipMapNearestNearest
-		elseif str == "mipmaplinearnearest" then return TextureFilter.MipMapLinearNearest
-		elseif str == "mipmapnearestlinear" then return TextureFilter.MipMapNearestLinear
-		elseif str == "mipmaplinearlinear" then return TextureFilter.MipMapLinearLinear
-		else error("Unknown texture wrap: " .. str, 2)
-		end
-	end
-
-	local page = nil
-	while true do
-		local line = readLine()
-		if not line then break end
-		line = line:match'^%s*(.*%S)' or ''
-		if line:len() == 0 then
-			page = nil
-		elseif not page then
-			page = TextureAtlasPage.new()
-			page.name = line
-
-			local tuple = readTuple()
-			if #tuple == 2 then
-				page.width = parseInt(tuple[1])
-				page.height = parseInt(tuple[2])
-				tuple = readTuple()
-			else
-				-- We only support atlases that have the page width/height
-				-- encoded in them. That way we don't rely on any special
-				-- wrapper objects for images to get the page size from
-				error("Atlas must specify page width/height. Please export to the latest atlas format", 2)
-			end
-
-			tuple = readTuple()
-			page.minFilter = filterFromString(tuple[1])
-			page.magFilter = filterFromString(tuple[2])
-
-			local direction = readValue()
-			page.uWrap = TextureWrap.ClampToEdge
-			page.vWrap = TextureWrap.ClampToEdge
-			if direction == "x" then
-				page.uWrap = TextureWrap.Repeat
-			elseif direction == "y" then
-				page.vWrap = TextureWrap.Repeat
-			elseif direction == "xy" then
-				page.uWrap = TextureWrap.Repeat
-				page.vWrap = TextureWrap.Repeat
-			end
-
-			page.texture = imageLoader(line)
-			-- FIXME page.texture:setFilters(page.minFilter, page.magFilter)
-			-- FIXME page.texture:setWraps(page.uWrap, page.vWrap)
-			table_insert(self.pages, page)
-		else
-			local region = TextureAtlasRegion.new()
-			region.name = line
-			region.page = page
-
-			local rotateValue = readValue()
-			if rotateValue == "true" then
-				region.degrees = 90
-			elseif rotateValue == "false" then
-				region.degrees = 0
-			else
-				region.degrees = tonumber(rotateValue)
-			end
-			if region.degrees == 90 then region.rotate = true end
-
-			local tuple = readTuple()
-			local x = parseInt(tuple[1])
-			local y = parseInt(tuple[2])
-
-			tuple = readTuple()
-			local width = parseInt(tuple[1])
-			local height = parseInt(tuple[2])
-
-			region.u = x / page.width
-			region.v = y / page.height
-			if region.rotate then
-				region.u2 = (x + height) / page.width
-				region.v2 = (y + width) / page.height
-			else
-				region.u2 = (x + width) / page.width
-				region.v2 = (y + height) / page.height
-			end
-
-			region.x = x
-			region.y = y
-			region.width = math_abs(width)
-			region.height = math_abs(height)
-
-			-- Read and skip optional splits
-			tuple = readTuple()
-			if #tuple == 4 then
-				tuple = readTuple()
-				if #tuple == 4 then
-					readTuple()
-				end
-			end
-
-			region.originalWidth = parseInt(tuple[1])
-			region.originalHeight = parseInt(tuple[2])
-
-			tuple = readTuple()
-			region.offsetX = parseInt(tuple[1])
-			region.offsetY = parseInt(tuple[2])
-
-			region.index = parseInt(readValue())
-			region.texture = page.texture
-			table_insert(self.regions, region)
-		end
-	end
-end
-
-function TextureAtlas:findRegion(name)
-	for _, region in ipairs(self.regions) do
-		if region.name == name then return region end
-	end
-	return nil
-end
-
-function TextureAtlas:dispose()
-	for _, page in ipairs(self.pairs) do
-		-- FIXME implement disposing of pages
-		-- love2d doesn't support manual disposing
-	end
-end
-
-return TextureAtlas
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+local table_insert = table.insert
+local math_abs = math.abs
+
+local TextureAtlasRegion = require "spine-lua.TextureAtlasRegion"
+local TextureWrap = require "spine-lua.TextureWrap"
+local TextureFilter = require "spine-lua.TextureFilter"
+
+local TextureAtlasPage = {}
+TextureAtlasPage.__index = TextureAtlasPage
+
+function TextureAtlasPage.new ()
+	local self = {
+		name = nil,
+		minFilter = nil,
+		magFilter = nil,
+		uWrap = nil,
+		vWrap = nil,
+		texture = nil,
+		width = 0,
+		height = 0
+	}
+	setmetatable(self, TextureAtlasPage)
+	return self
+end
+
+
+local TextureAtlas = {}
+TextureAtlas.__index = TextureAtlas
+
+function TextureAtlas.new (atlasContent, imageLoader)
+	local self = {
+		pages = {},
+		regions = {}
+	}
+	setmetatable(self, TextureAtlas)
+
+	self:parse(atlasContent, imageLoader)
+
+	return self
+end
+
+function TextureAtlas:parse (atlasContent, imageLoader)
+	if not atlasContent then error("atlasContent cannot be nil.", 2) end
+	if not imageLoader then error("imageLoader cannot be nil.", 2) end
+
+	function lineIterator(s)
+		if s:sub(-1)~="\n" then s=s.."\n" end
+		return s:gmatch("(.-)\n")
+	end
+
+	local lines = {}
+	local index = 0
+	local numLines = 0
+	for line in lineIterator(atlasContent) do
+		lines[numLines] = line
+		numLines = numLines + 1
+	end
+
+	local readLine = function ()
+		if index >= numLines then return nil end
+		local line = lines[index]
+		index = index + 1
+		return line
+	end
+
+	local readValue = function ()
+		local line = readLine()
+		local idx = line:find(":")
+		if not idx then error("Invalid line: " .. line, 2) end
+		return line:sub(idx + 1):match'^%s*(.*%S)' or ''
+	end
+
+	local readTuple = function ()
+		local line = readLine()
+		local idx = line:find(":")
+		if not idx then
+			error("Invalid line: " .. line, 2)
+		end
+		local i = 1
+		local lastMatch = idx + 1
+		local tuple = {}
+		while i <= 3 do
+			local comma = line:find(",", lastMatch)
+			if not comma then break end
+			tuple[i] = line:sub(lastMatch, comma - 1):match'^%s*(.*%S)' or ''
+			lastMatch = comma + 1
+			i = i + 1
+		end
+		tuple[i] = line:sub(lastMatch):match'^%s*(.*%S)' or ''
+		return tuple
+	end
+
+	local parseInt = function (str)
+		return tonumber(str)
+	end
+
+	local filterFromString = function (str)
+		str = str:lower()
+		if str == "nearest" then return TextureFilter.Nearest
+		elseif str == "linear" then return TextureFilter.Linear
+		elseif str == "mipmap" then return TextureFilter.MipMap
+		elseif str == "mipmapnearestnearest" then return TextureFilter.MipMapNearestNearest
+		elseif str == "mipmaplinearnearest" then return TextureFilter.MipMapLinearNearest
+		elseif str == "mipmapnearestlinear" then return TextureFilter.MipMapNearestLinear
+		elseif str == "mipmaplinearlinear" then return TextureFilter.MipMapLinearLinear
+		else error("Unknown texture wrap: " .. str, 2)
+		end
+	end
+
+	local page = nil
+	while true do
+		local line = readLine()
+		if not line then break end
+		line = line:match'^%s*(.*%S)' or ''
+		if line:len() == 0 then
+			page = nil
+		elseif not page then
+			page = TextureAtlasPage.new()
+			page.name = line
+
+			local tuple = readTuple()
+			if #tuple == 2 then
+				page.width = parseInt(tuple[1])
+				page.height = parseInt(tuple[2])
+				tuple = readTuple()
+			else
+				-- We only support atlases that have the page width/height
+				-- encoded in them. That way we don't rely on any special
+				-- wrapper objects for images to get the page size from
+				error("Atlas must specify page width/height. Please export to the latest atlas format", 2)
+			end
+
+			tuple = readTuple()
+			page.minFilter = filterFromString(tuple[1])
+			page.magFilter = filterFromString(tuple[2])
+
+			local direction = readValue()
+			page.uWrap = TextureWrap.ClampToEdge
+			page.vWrap = TextureWrap.ClampToEdge
+			if direction == "x" then
+				page.uWrap = TextureWrap.Repeat
+			elseif direction == "y" then
+				page.vWrap = TextureWrap.Repeat
+			elseif direction == "xy" then
+				page.uWrap = TextureWrap.Repeat
+				page.vWrap = TextureWrap.Repeat
+			end
+
+			page.texture = imageLoader(line)
+			-- FIXME page.texture:setFilters(page.minFilter, page.magFilter)
+			-- FIXME page.texture:setWraps(page.uWrap, page.vWrap)
+			table_insert(self.pages, page)
+		else
+			local region = TextureAtlasRegion.new()
+			region.name = line
+			region.page = page
+
+			local rotateValue = readValue()
+			if rotateValue == "true" then
+				region.degrees = 90
+			elseif rotateValue == "false" then
+				region.degrees = 0
+			else
+				region.degrees = tonumber(rotateValue)
+			end
+			if region.degrees == 90 then region.rotate = true end
+
+			local tuple = readTuple()
+			local x = parseInt(tuple[1])
+			local y = parseInt(tuple[2])
+
+			tuple = readTuple()
+			local width = parseInt(tuple[1])
+			local height = parseInt(tuple[2])
+
+			region.u = x / page.width
+			region.v = y / page.height
+			if region.rotate then
+				region.u2 = (x + height) / page.width
+				region.v2 = (y + width) / page.height
+			else
+				region.u2 = (x + width) / page.width
+				region.v2 = (y + height) / page.height
+			end
+
+			region.x = x
+			region.y = y
+			region.width = math_abs(width)
+			region.height = math_abs(height)
+
+			-- Read and skip optional splits
+			tuple = readTuple()
+			if #tuple == 4 then
+				tuple = readTuple()
+				if #tuple == 4 then
+					readTuple()
+				end
+			end
+
+			region.originalWidth = parseInt(tuple[1])
+			region.originalHeight = parseInt(tuple[2])
+
+			tuple = readTuple()
+			region.offsetX = parseInt(tuple[1])
+			region.offsetY = parseInt(tuple[2])
+
+			region.index = parseInt(readValue())
+			region.texture = page.texture
+			table_insert(self.regions, region)
+		end
+	end
+end
+
+function TextureAtlas:findRegion(name)
+	for _, region in ipairs(self.regions) do
+		if region.name == name then return region end
+	end
+	return nil
+end
+
+function TextureAtlas:dispose()
+	for _, page in ipairs(self.pairs) do
+		-- FIXME implement disposing of pages
+		-- love2d doesn't support manual disposing
+	end
+end
+
+return TextureAtlas

+ 53 - 53
spine-lua/TextureAtlasRegion.lua → spine-lua/spine-lua/TextureAtlasRegion.lua

@@ -1,53 +1,53 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local setmetatable = setmetatable
-
-local TextureRegion = require "spine-lua.TextureRegion"
-
-local TextureAtlasRegion = {}
-TextureAtlasRegion.__index = TextureAtlasRegion
-setmetatable(TextureAtlasRegion, { __index = TextureRegion })
-
-function TextureAtlasRegion.new ()
-	local self = TextureRegion.new()
-	self.page = nil
-	self.name = nil
-	self.x = 0
-	self.y = 0
-	self.index = 0
-	self.rotate = false
-	self.degrees = 0
-	self.texture = nil
-	setmetatable(self, TextureAtlasRegion)
-
-	return self
-end
-
-return TextureAtlasRegion
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+
+local TextureRegion = require "spine-lua.TextureRegion"
+
+local TextureAtlasRegion = {}
+TextureAtlasRegion.__index = TextureAtlasRegion
+setmetatable(TextureAtlasRegion, { __index = TextureRegion })
+
+function TextureAtlasRegion.new ()
+	local self = TextureRegion.new()
+	self.page = nil
+	self.name = nil
+	self.x = 0
+	self.y = 0
+	self.index = 0
+	self.rotate = false
+	self.degrees = 0
+	self.texture = nil
+	setmetatable(self, TextureAtlasRegion)
+
+	return self
+end
+
+return TextureAtlasRegion

+ 39 - 39
spine-lua/TextureFilter.lua → spine-lua/spine-lua/TextureFilter.lua

@@ -1,39 +1,39 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local TextureFilter = {
-	Nearest = 0,
-	Linear = 1,
-	MipMap = 2,
-	MipMapNearestNearest = 3,
-	MipMapLinearNearest = 4,
-	MipMapNearestLinear = 5,
-	MipMapLinearLinear = 6
-}
-return TextureFilter
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local TextureFilter = {
+	Nearest = 0,
+	Linear = 1,
+	MipMap = 2,
+	MipMapNearestNearest = 3,
+	MipMapLinearNearest = 4,
+	MipMapNearestLinear = 5,
+	MipMapLinearLinear = 6
+}
+return TextureFilter

+ 51 - 51
spine-lua/TextureRegion.lua → spine-lua/spine-lua/TextureRegion.lua

@@ -1,51 +1,51 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local setmetatable = setmetatable
-
-local TextureRegion = {}
-TextureRegion.__index = TextureRegion
-
-function TextureRegion.new ()
-
-	local self = {
-		renderObject = nil,
-		u = 0, v = 0,
-		u2 = 0, v2 = 0,
-		width = 0, height = 0,
-		rotate = false,
-		offsetX = 0, offsetY = 0,
-		originalWidth = 0, originalHeight = 0
-	}
-	setmetatable(self, TextureRegion)
-
-	return self
-end
-
-return TextureRegion
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+
+local TextureRegion = {}
+TextureRegion.__index = TextureRegion
+
+function TextureRegion.new ()
+
+	local self = {
+		renderObject = nil,
+		u = 0, v = 0,
+		u2 = 0, v2 = 0,
+		width = 0, height = 0,
+		rotate = false,
+		offsetX = 0, offsetY = 0,
+		originalWidth = 0, originalHeight = 0
+	}
+	setmetatable(self, TextureRegion)
+
+	return self
+end
+
+return TextureRegion

+ 34 - 34
spine-lua/TextureWrap.lua → spine-lua/spine-lua/TextureWrap.lua

@@ -1,34 +1,34 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local TextureWrap = {
-	ClampToEdge = 0,
-	Repeat = 1
-}
-return TextureWrap
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local TextureWrap = {
+	ClampToEdge = 0,
+	Repeat = 1
+}
+return TextureWrap

+ 343 - 343
spine-lua/TransformConstraint.lua → spine-lua/spine-lua/TransformConstraint.lua

@@ -1,343 +1,343 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local setmetatable = setmetatable
-local utils = require "spine-lua.utils"
-local math_pi = math.pi
-local math_pi2 = math.pi * 2
-local math_atan2 = math.atan2
-local math_sqrt = math.sqrt
-local math_acos = math.acos
-local math_sin = math.sin
-local math_cos = math.cos
-local table_insert = table.insert
-local math_deg = math.deg
-local math_rad = math.rad
-local math_abs = math.abs
-local math_floor = math.floor
-
-local TransformConstraint = {}
-TransformConstraint.__index = TransformConstraint
-
-function TransformConstraint.new (data, skeleton)
-	if not data then error("data cannot be nil", 2) end
-	if not skeleton then error("skeleton cannot be nil", 2) end
-
-	local self = {
-		data = data,
-		bones = {},
-		target = nil,
-		rotateMix = data.rotateMix, translateMix = data.translateMix, scaleMix = data.scaleMix, shearMix = data.shearMix,
-		temp = { 0, 0 },
-		active = false
-	}
-	setmetatable(self, TransformConstraint)
-
-	for _,bone in ipairs(data.bones) do
-		table_insert(self.bones, skeleton:findBone(bone.name))
-	end
-	self.target = skeleton:findBone(data.target.name)
-
-	return self
-end
-
-function TransformConstraint:apply ()
-	self:update()
-end
-
-function TransformConstraint:update ()
-	if self.data.local_ then
-		if self.data.relative then
-			self:applyRelativeLocal()
-		else
-			self:applyAbsoluteLocal()
-		end
-	else
-		if self.data.relative then
-			self:applyRelativeWorld()
-		else
-			self:applyAbsoluteWorld()
-		end
-	end
-end
-
-function TransformConstraint:applyAbsoluteWorld ()
-	local rotateMix = self.rotateMix
-	local translateMix = self.translateMix
-	local scaleMix = self.scaleMix
-	local shearMix = self.shearMix
-	local target = self.target
-	local ta = target.a
-	local tb = target.b
-	local tc = target.c
-	local td = target.d
-	local degRadReflect = 0;
-	if ta * td - tb * tc > 0 then degRadReflect = utils.degRad else degRadReflect = -utils.degRad end
-	local offsetRotation = self.data.offsetRotation * degRadReflect
-	local offsetShearY = self.data.offsetShearY * degRadReflect
-	local bones = self.bones
-	for _, bone in ipairs(bones) do
-		local modified = false
-		if rotateMix ~= 0 then
-			local a = bone.a
-			local b = bone.b
-			local c = bone.c
-			local d = bone.d
-			local r = math_atan2(tc, ta) - math_atan2(c, a) + offsetRotation
-			if r > math_pi then
-				r = r - math_pi2
-			elseif r < -math_pi then
-				r = r + math_pi2
-			end
-			r = r * rotateMix
-			local cos = math_cos(r)
-			local sin = math_sin(r)
-			bone.a = cos * a - sin * c
-			bone.b = cos * b - sin * d
-			bone.c = sin * a + cos * c
-			bone.d = sin * b + cos * d
-			modified = true
-		end
-
-		if translateMix ~= 0 then
-			local temp = self.temp
-			temp[1] = self.data.offsetX
-			temp[2] = self.data.offsetY
-			target:localToWorld(temp)
-			bone.worldX = bone.worldX + (temp[1] - bone.worldX) * translateMix
-			bone.worldY = bone.worldY + (temp[2] - bone.worldY) * translateMix
-			modified = true
-		end
-
-		if scaleMix > 0 then
-			local s = math_sqrt(bone.a * bone.a + bone.c * bone.c)
-			local ts = math_sqrt(ta * ta + tc * tc)
-			if s > 0.00001 then
-				s = (s + (ts - s + self.data.offsetScaleX) * scaleMix) / s
-			end
-			bone.a = bone.a * s
-			bone.c = bone.c * s
-			s = math_sqrt(bone.b * bone.b + bone.d * bone.d)
-			ts = math_sqrt(tb * tb + td * td)
-			if s > 0.00001 then
-				s = (s + (ts - s + self.data.offsetScaleY) * scaleMix) / s
-			end
-			bone.b = bone.b * s
-			bone.d = bone.d * s
-			modified = true
-		end
-
-		if shearMix > 0 then
-			local b = bone.b
-			local d = bone.d
-			local by = math_atan2(d, b)
-			local r = math_atan2(td, tb) - math_atan2(tc, ta) - (by - math_atan2(bone.c, bone.a))
-			if r > math_pi then
-				r = r - math_pi2
-			elseif r < -math_pi then
-				r = r + math_pi2
-			end
-			r = by + (r + offsetShearY) * shearMix
-			local s = math_sqrt(b * b + d * d)
-			bone.b = math_cos(r) * s
-			bone.d = math_sin(r) * s
-			modified = true
-		end
-
-		if modified then bone.appliedValid = false end
-	end
-end
-
-function TransformConstraint:applyRelativeWorld ()
-	local rotateMix = self.rotateMix
-	local translateMix = self.translateMix
-	local scaleMix = self.scaleMix
-	local shearMix = self.shearMix
-	local target = self.target
-	local ta = target.a
-	local tb = target.b
-	local tc = target.c
-	local td = target.d
-	local degRadReflect = 0;
-	if ta * td - tb * tc > 0 then degRadReflect = utils.degRad else degRadReflect = -utils.degRad end
-	local offsetRotation = self.data.offsetRotation * degRadReflect
-	local offsetShearY = self.data.offsetShearY * degRadReflect
-	local bones = self.bones
-	for _, bone in ipairs(bones) do
-		local modified = false
-
-		if rotateMix ~= 0 then
-			local a = bone.a
-			local b = bone.b
-			local c = bone.c
-			local d = bone.d
-			local r = math_atan2(tc, ta) + offsetRotation
-			if r > math_pi then
-				r = r - math_pi2
-			elseif r < -math_pi then
-				r = r + math_pi2
-			end
-			r = r * rotateMix
-			local cos = math_cos(r)
-			local sin = math_sin(r)
-			bone.a = cos * a - sin * c
-			bone.b = cos * b - sin * d
-			bone.c = sin * a + cos * c
-			bone.d = sin * b + cos * d
-			modified = true
-		end
-
-		if translateMix ~= 0 then
-			local temp = self.temp
-			temp[1] = self.data.offsetX
-			temp[2] = self.data.offsetY
-			target:localToWorld(temp)
-			bone.worldX = bone.worldX + temp[1] * translateMix
-			bone.worldY = bone.worldY + temp[2] * translateMix
-			modified = true
-		end
-
-		if scaleMix > 0 then
-			local s = (math_sqrt(ta * ta + tc * tc) - 1 + self.data.offsetScaleX) * scaleMix + 1
-			bone.a = bone.a * s
-			bone.c = bone.c * s
-			s = (math_sqrt(tb * tb + td * td) - 1 + self.data.offsetScaleY) * scaleMix + 1
-			bone.b = bone.b * s
-			bone.d = bone.d * s
-			modified = true
-		end
-
-		if shearMix > 0 then
-			local r = math_atan2(td, tb) - math_atan2(tc, ta)
-			if r > math_pi then
-				r = r - math_pi2
-			elseif r < -math_pi then
-				r = r + math_pi2
-			end
-			local b = bone.b
-			local d = bone.d
-			r = math_atan2(d, b) + (r - math_pi / 2 + offsetShearY) * shearMix;
-			local s = math_sqrt(b * b + d * d)
-			bone.b = math_cos(r) * s
-			bone.d = math_sin(r) * s
-			modified = true
-		end
-
-		if modified then bone.appliedValid = false end
-	end
-end
-
-function TransformConstraint:applyAbsoluteLocal ()
-	local rotateMix = self.rotateMix
-	local translateMix = self.translateMix
-	local scaleMix = self.scaleMix
-	local shearMix = self.shearMix
-	local target = self.target
-	if not target.appliedValid then target:updatedAppliedTransform() end
-	local bones = self.bones
-	for _, bone in ipairs(bones) do
-		local modified = false
-		if not bone.appliedValid then bone:updateAppliedTransform() end
-
-		local rotation = bone.arotation
-		if rotateMix ~= 0 then
-			local r = target.arotation - rotation + self.data.offsetRotation
-			r = r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360
-			rotation = rotation + r * rotateMix
-		end
-
-		local x = bone.ax
-		local y = bone.ay
-		if translateMix ~= 0 then
-			x = x + (target.ax - x + self.data.offsetX) * translateMix
-			y = x + (target.ay - y + self.data.offsetY) * translateMix
-		end
-
-		local scaleX = bone.ascaleX
-		local scaleY = bone.ascaleY
-		if scaleMix ~= 0 then
-			if scaleX > 0.00001 then
-				scaleX = (scaleX + (target.ascaleX - scaleX + self.data.offsetScaleX) * scaleMix) / scaleX
-			end
-			if scaleY > 0.00001 then
-				scaleY = (scaleY + (target.ascaleY - scaleY + self.data.offsetScaleY) * scaleMix) / scaleY
-			end
-		end
-
-		local shearY = bone.ashearY
-		if shearMix ~= 0 then
-			local r = target.ashearY - shearY + self.data.offsetShearY
-			r = r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360
-			bone.shearY = bone.shearY + r * shearMix
-		end
-
-		bone:updateWorldTransformWith(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY);
-	end
-end
-
-function TransformConstraint:applyRelativeLocal ()
-	local rotateMix = self.rotateMix
-	local translateMix = self.translateMix
-	local scaleMix = self.scaleMix
-	local shearMix = self.shearMix
-	local target = self.target
-	if not target.appliedValid then target:updateAppliedTransform() end
-	local bones = self.bones
-	for _, bone in ipairs(bones) do
-		if not bone.appliedValid then bone:updateAppliedTransform() end
-
-		local rotation = bone.arotation
-		if rotateMix ~= 0 then rotation = rotation + (target.arotation + self.data.offsetRotation) * rotateMix end
-
-		local x = bone.ax
-		local y = bone.ay
-		if translateMix ~= 0 then
-			x = x + (target.ax + self.data.offsetX) * translateMix
-			y = y + (target.ay + self.data.offsetY) * translateMix
-		end
-
-		local scaleX = bone.ascaleX
-		local scaleY = bone.ascaleY
-		if scaleMix ~= 0 then
-			if scaleX > 0.00001 then
-				scaleX = scaleX * (((target.ascaleX - 1 + self.data.offsetScaleX) * scaleMix) + 1)
-			end
-			if scaleY > 0.00001 then
-				scaleY = scaleY * (((target.ascaleY - 1 + self.data.offsetScaleY) * scaleMix) + 1)
-			end
-		end
-
-		local shearY = bone.ashearY
-		if shearMix ~= 0 then shearY = shearY + (target.ashearY + self.data.offsetShearY) * shearMix end
-
-		bone:updateWorldTransformWith(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY)
-	end
-end
-
-return TransformConstraint
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+local utils = require "spine-lua.utils"
+local math_pi = math.pi
+local math_pi2 = math.pi * 2
+local math_atan2 = math.atan2
+local math_sqrt = math.sqrt
+local math_acos = math.acos
+local math_sin = math.sin
+local math_cos = math.cos
+local table_insert = table.insert
+local math_deg = math.deg
+local math_rad = math.rad
+local math_abs = math.abs
+local math_floor = math.floor
+
+local TransformConstraint = {}
+TransformConstraint.__index = TransformConstraint
+
+function TransformConstraint.new (data, skeleton)
+	if not data then error("data cannot be nil", 2) end
+	if not skeleton then error("skeleton cannot be nil", 2) end
+
+	local self = {
+		data = data,
+		bones = {},
+		target = nil,
+		rotateMix = data.rotateMix, translateMix = data.translateMix, scaleMix = data.scaleMix, shearMix = data.shearMix,
+		temp = { 0, 0 },
+		active = false
+	}
+	setmetatable(self, TransformConstraint)
+
+	for _,bone in ipairs(data.bones) do
+		table_insert(self.bones, skeleton:findBone(bone.name))
+	end
+	self.target = skeleton:findBone(data.target.name)
+
+	return self
+end
+
+function TransformConstraint:apply ()
+	self:update()
+end
+
+function TransformConstraint:update ()
+	if self.data.local_ then
+		if self.data.relative then
+			self:applyRelativeLocal()
+		else
+			self:applyAbsoluteLocal()
+		end
+	else
+		if self.data.relative then
+			self:applyRelativeWorld()
+		else
+			self:applyAbsoluteWorld()
+		end
+	end
+end
+
+function TransformConstraint:applyAbsoluteWorld ()
+	local rotateMix = self.rotateMix
+	local translateMix = self.translateMix
+	local scaleMix = self.scaleMix
+	local shearMix = self.shearMix
+	local target = self.target
+	local ta = target.a
+	local tb = target.b
+	local tc = target.c
+	local td = target.d
+	local degRadReflect = 0;
+	if ta * td - tb * tc > 0 then degRadReflect = utils.degRad else degRadReflect = -utils.degRad end
+	local offsetRotation = self.data.offsetRotation * degRadReflect
+	local offsetShearY = self.data.offsetShearY * degRadReflect
+	local bones = self.bones
+	for _, bone in ipairs(bones) do
+		local modified = false
+		if rotateMix ~= 0 then
+			local a = bone.a
+			local b = bone.b
+			local c = bone.c
+			local d = bone.d
+			local r = math_atan2(tc, ta) - math_atan2(c, a) + offsetRotation
+			if r > math_pi then
+				r = r - math_pi2
+			elseif r < -math_pi then
+				r = r + math_pi2
+			end
+			r = r * rotateMix
+			local cos = math_cos(r)
+			local sin = math_sin(r)
+			bone.a = cos * a - sin * c
+			bone.b = cos * b - sin * d
+			bone.c = sin * a + cos * c
+			bone.d = sin * b + cos * d
+			modified = true
+		end
+
+		if translateMix ~= 0 then
+			local temp = self.temp
+			temp[1] = self.data.offsetX
+			temp[2] = self.data.offsetY
+			target:localToWorld(temp)
+			bone.worldX = bone.worldX + (temp[1] - bone.worldX) * translateMix
+			bone.worldY = bone.worldY + (temp[2] - bone.worldY) * translateMix
+			modified = true
+		end
+
+		if scaleMix > 0 then
+			local s = math_sqrt(bone.a * bone.a + bone.c * bone.c)
+			local ts = math_sqrt(ta * ta + tc * tc)
+			if s > 0.00001 then
+				s = (s + (ts - s + self.data.offsetScaleX) * scaleMix) / s
+			end
+			bone.a = bone.a * s
+			bone.c = bone.c * s
+			s = math_sqrt(bone.b * bone.b + bone.d * bone.d)
+			ts = math_sqrt(tb * tb + td * td)
+			if s > 0.00001 then
+				s = (s + (ts - s + self.data.offsetScaleY) * scaleMix) / s
+			end
+			bone.b = bone.b * s
+			bone.d = bone.d * s
+			modified = true
+		end
+
+		if shearMix > 0 then
+			local b = bone.b
+			local d = bone.d
+			local by = math_atan2(d, b)
+			local r = math_atan2(td, tb) - math_atan2(tc, ta) - (by - math_atan2(bone.c, bone.a))
+			if r > math_pi then
+				r = r - math_pi2
+			elseif r < -math_pi then
+				r = r + math_pi2
+			end
+			r = by + (r + offsetShearY) * shearMix
+			local s = math_sqrt(b * b + d * d)
+			bone.b = math_cos(r) * s
+			bone.d = math_sin(r) * s
+			modified = true
+		end
+
+		if modified then bone.appliedValid = false end
+	end
+end
+
+function TransformConstraint:applyRelativeWorld ()
+	local rotateMix = self.rotateMix
+	local translateMix = self.translateMix
+	local scaleMix = self.scaleMix
+	local shearMix = self.shearMix
+	local target = self.target
+	local ta = target.a
+	local tb = target.b
+	local tc = target.c
+	local td = target.d
+	local degRadReflect = 0;
+	if ta * td - tb * tc > 0 then degRadReflect = utils.degRad else degRadReflect = -utils.degRad end
+	local offsetRotation = self.data.offsetRotation * degRadReflect
+	local offsetShearY = self.data.offsetShearY * degRadReflect
+	local bones = self.bones
+	for _, bone in ipairs(bones) do
+		local modified = false
+
+		if rotateMix ~= 0 then
+			local a = bone.a
+			local b = bone.b
+			local c = bone.c
+			local d = bone.d
+			local r = math_atan2(tc, ta) + offsetRotation
+			if r > math_pi then
+				r = r - math_pi2
+			elseif r < -math_pi then
+				r = r + math_pi2
+			end
+			r = r * rotateMix
+			local cos = math_cos(r)
+			local sin = math_sin(r)
+			bone.a = cos * a - sin * c
+			bone.b = cos * b - sin * d
+			bone.c = sin * a + cos * c
+			bone.d = sin * b + cos * d
+			modified = true
+		end
+
+		if translateMix ~= 0 then
+			local temp = self.temp
+			temp[1] = self.data.offsetX
+			temp[2] = self.data.offsetY
+			target:localToWorld(temp)
+			bone.worldX = bone.worldX + temp[1] * translateMix
+			bone.worldY = bone.worldY + temp[2] * translateMix
+			modified = true
+		end
+
+		if scaleMix > 0 then
+			local s = (math_sqrt(ta * ta + tc * tc) - 1 + self.data.offsetScaleX) * scaleMix + 1
+			bone.a = bone.a * s
+			bone.c = bone.c * s
+			s = (math_sqrt(tb * tb + td * td) - 1 + self.data.offsetScaleY) * scaleMix + 1
+			bone.b = bone.b * s
+			bone.d = bone.d * s
+			modified = true
+		end
+
+		if shearMix > 0 then
+			local r = math_atan2(td, tb) - math_atan2(tc, ta)
+			if r > math_pi then
+				r = r - math_pi2
+			elseif r < -math_pi then
+				r = r + math_pi2
+			end
+			local b = bone.b
+			local d = bone.d
+			r = math_atan2(d, b) + (r - math_pi / 2 + offsetShearY) * shearMix;
+			local s = math_sqrt(b * b + d * d)
+			bone.b = math_cos(r) * s
+			bone.d = math_sin(r) * s
+			modified = true
+		end
+
+		if modified then bone.appliedValid = false end
+	end
+end
+
+function TransformConstraint:applyAbsoluteLocal ()
+	local rotateMix = self.rotateMix
+	local translateMix = self.translateMix
+	local scaleMix = self.scaleMix
+	local shearMix = self.shearMix
+	local target = self.target
+	if not target.appliedValid then target:updatedAppliedTransform() end
+	local bones = self.bones
+	for _, bone in ipairs(bones) do
+		local modified = false
+		if not bone.appliedValid then bone:updateAppliedTransform() end
+
+		local rotation = bone.arotation
+		if rotateMix ~= 0 then
+			local r = target.arotation - rotation + self.data.offsetRotation
+			r = r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360
+			rotation = rotation + r * rotateMix
+		end
+
+		local x = bone.ax
+		local y = bone.ay
+		if translateMix ~= 0 then
+			x = x + (target.ax - x + self.data.offsetX) * translateMix
+			y = x + (target.ay - y + self.data.offsetY) * translateMix
+		end
+
+		local scaleX = bone.ascaleX
+		local scaleY = bone.ascaleY
+		if scaleMix ~= 0 then
+			if scaleX > 0.00001 then
+				scaleX = (scaleX + (target.ascaleX - scaleX + self.data.offsetScaleX) * scaleMix) / scaleX
+			end
+			if scaleY > 0.00001 then
+				scaleY = (scaleY + (target.ascaleY - scaleY + self.data.offsetScaleY) * scaleMix) / scaleY
+			end
+		end
+
+		local shearY = bone.ashearY
+		if shearMix ~= 0 then
+			local r = target.ashearY - shearY + self.data.offsetShearY
+			r = r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360
+			bone.shearY = bone.shearY + r * shearMix
+		end
+
+		bone:updateWorldTransformWith(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY);
+	end
+end
+
+function TransformConstraint:applyRelativeLocal ()
+	local rotateMix = self.rotateMix
+	local translateMix = self.translateMix
+	local scaleMix = self.scaleMix
+	local shearMix = self.shearMix
+	local target = self.target
+	if not target.appliedValid then target:updateAppliedTransform() end
+	local bones = self.bones
+	for _, bone in ipairs(bones) do
+		if not bone.appliedValid then bone:updateAppliedTransform() end
+
+		local rotation = bone.arotation
+		if rotateMix ~= 0 then rotation = rotation + (target.arotation + self.data.offsetRotation) * rotateMix end
+
+		local x = bone.ax
+		local y = bone.ay
+		if translateMix ~= 0 then
+			x = x + (target.ax + self.data.offsetX) * translateMix
+			y = y + (target.ay + self.data.offsetY) * translateMix
+		end
+
+		local scaleX = bone.ascaleX
+		local scaleY = bone.ascaleY
+		if scaleMix ~= 0 then
+			if scaleX > 0.00001 then
+				scaleX = scaleX * (((target.ascaleX - 1 + self.data.offsetScaleX) * scaleMix) + 1)
+			end
+			if scaleY > 0.00001 then
+				scaleY = scaleY * (((target.ascaleY - 1 + self.data.offsetScaleY) * scaleMix) + 1)
+			end
+		end
+
+		local shearY = bone.ashearY
+		if shearMix ~= 0 then shearY = shearY + (target.ashearY + self.data.offsetShearY) * shearMix end
+
+		bone:updateWorldTransformWith(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY)
+	end
+end
+
+return TransformConstraint

+ 49 - 49
spine-lua/TransformConstraintData.lua → spine-lua/spine-lua/TransformConstraintData.lua

@@ -1,49 +1,49 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local TransformConstraintData = {}
-function TransformConstraintData.new (name)
-	if not name then error("name cannot be nil", 2) end
-
-	local self = {
-		name = name,
-		order = 0,
-		skinRequired = false,
-		bones = {},
-		target = nil,
-		rotateMix = 0, translateMix = 0, scaleMix = 0, shearMix = 0,
-		offsetRotation = 0, offsetX = 0, offsetY = 0, offsetScaleX = 0, offsetScaleY = 0, offsetShearY = 0,
-		relative = false,
-		local_ = false
-	}
-
-	return self
-end
-
-return TransformConstraintData
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local TransformConstraintData = {}
+function TransformConstraintData.new (name)
+	if not name then error("name cannot be nil", 2) end
+
+	local self = {
+		name = name,
+		order = 0,
+		skinRequired = false,
+		bones = {},
+		target = nil,
+		rotateMix = 0, translateMix = 0, scaleMix = 0, shearMix = 0,
+		offsetRotation = 0, offsetX = 0, offsetY = 0, offsetScaleX = 0, offsetScaleY = 0, offsetShearY = 0,
+		relative = false,
+		local_ = false
+	}
+
+	return self
+end
+
+return TransformConstraintData

+ 0 - 0
spine-lua/TransformMode.lua → spine-lua/spine-lua/TransformMode.lua


+ 0 - 0
spine-lua/Triangulator.lua → spine-lua/spine-lua/Triangulator.lua


+ 53 - 53
spine-lua/attachments/Attachment.lua → spine-lua/spine-lua/attachments/Attachment.lua

@@ -1,53 +1,53 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local setmetatable = setmetatable
-
-local AttachmentType = require "spine-lua.attachments.AttachmentType"
-
-local Attachment = {}
-Attachment.__index = Attachment
-
-function Attachment.new (name, attachmentType)
-	if not name then error("name cannot be nil.", 2) end
-	if not attachmentType then error("attachmentType cannot be nil.", 2) end
-
-	local self = {
-		name = name,
-		type = attachmentType
-	}
-	setmetatable(self, Attachment)
-	return self
-end
-
-function Attachment:copy ()
-	error("Attachment copy not implemented.")
-end
-
-return Attachment
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+
+local AttachmentType = require "spine-lua.attachments.AttachmentType"
+
+local Attachment = {}
+Attachment.__index = Attachment
+
+function Attachment.new (name, attachmentType)
+	if not name then error("name cannot be nil.", 2) end
+	if not attachmentType then error("attachmentType cannot be nil.", 2) end
+
+	local self = {
+		name = name,
+		type = attachmentType
+	}
+	setmetatable(self, Attachment)
+	return self
+end
+
+function Attachment:copy ()
+	error("Attachment copy not implemented.")
+end
+
+return Attachment

+ 39 - 39
spine-lua/attachments/AttachmentType.lua → spine-lua/spine-lua/attachments/AttachmentType.lua

@@ -1,39 +1,39 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local AttachmentType = {
-	region = 0,
-	boundingbox = 1,
-	mesh = 2,
-	linkedmesh = 3,
-	path = 4,
-	point = 5,
-	clipping = 6
-}
-return AttachmentType
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local AttachmentType = {
+	region = 0,
+	boundingbox = 1,
+	mesh = 2,
+	linkedmesh = 3,
+	path = 4,
+	point = 5,
+	clipping = 6
+}
+return AttachmentType

+ 53 - 53
spine-lua/attachments/BoundingBoxAttachment.lua → spine-lua/spine-lua/attachments/BoundingBoxAttachment.lua

@@ -1,53 +1,53 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local AttachmentType = require "spine-lua.attachments.AttachmentType"
-local VertexAttachment = require "spine-lua.attachments.VertexAttachment"
-local Color = require "spine-lua.Color"
-
-local BoundingBoxAttachment = {}
-BoundingBoxAttachment.__index = BoundingBoxAttachment
-setmetatable(BoundingBoxAttachment, { __index = VertexAttachment })
-
-function BoundingBoxAttachment.new (name)
-	if not name then error("name cannot be nil", 2) end
-
-	local self = VertexAttachment.new(name, AttachmentType.boundingbox)
-	self.color = Color.newWith(1, 1, 1, 1)
-	setmetatable(self, BoundingBoxAttachment)
-	return self
-end
-
-function BoundingBoxAttachment:copy ()
-	local copy = BoundingBoxAttachment.new(self.name)
-	self:copyTo(copy)
-	copy.color:setFrom(self.color)
-	return copy
-end
-return BoundingBoxAttachment
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local AttachmentType = require "spine-lua.attachments.AttachmentType"
+local VertexAttachment = require "spine-lua.attachments.VertexAttachment"
+local Color = require "spine-lua.Color"
+
+local BoundingBoxAttachment = {}
+BoundingBoxAttachment.__index = BoundingBoxAttachment
+setmetatable(BoundingBoxAttachment, { __index = VertexAttachment })
+
+function BoundingBoxAttachment.new (name)
+	if not name then error("name cannot be nil", 2) end
+
+	local self = VertexAttachment.new(name, AttachmentType.boundingbox)
+	self.color = Color.newWith(1, 1, 1, 1)
+	setmetatable(self, BoundingBoxAttachment)
+	return self
+end
+
+function BoundingBoxAttachment:copy ()
+	local copy = BoundingBoxAttachment.new(self.name)
+	self:copyTo(copy)
+	copy.color:setFrom(self.color)
+	return copy
+end
+return BoundingBoxAttachment

+ 0 - 0
spine-lua/attachments/ClippingAttachment.lua → spine-lua/spine-lua/attachments/ClippingAttachment.lua


+ 180 - 180
spine-lua/attachments/MeshAttachment.lua → spine-lua/spine-lua/attachments/MeshAttachment.lua

@@ -1,180 +1,180 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local setmetatable = setmetatable
-local AttachmentType = require "spine-lua.attachments.AttachmentType"
-local VertexAttachment = require "spine-lua.attachments.VertexAttachment"
-local utils = require "spine-lua.utils"
-local Color = require "spine-lua.Color"
-
-local MeshAttachment = {}
-MeshAttachment.__index = MeshAttachment
-setmetatable(MeshAttachment, { __index = VertexAttachment })
-
-function MeshAttachment.new (name)
-	if not name then error("name cannot be nil", 2) end
-
-	local self = VertexAttachment.new(name, AttachmentType.mesh)
-	self.region = nil
-	self.path = nil
-	self.regionUVs = nil
-	self.uvs = nil
-	self.triangles = nil
-	self.color = Color.newWith(1, 1, 1, 1)
-	self.hullLength = 0
-	self.parentMesh = nil
-	self.tempColor = Color.newWith(1, 1, 1, 1)
-	self.width = 0
-	self.height = 0
-	setmetatable(self, MeshAttachment)
-	return self
-end
-
-function MeshAttachment:updateUVs ()
-	local u = 0
-	local v = 0
-	local width = 0
-	local height = 0
-
-	local regionUVs = self.regionUVs
-	if not self.uvs or (#self.uvs ~= #regionUVs) then self.uvs = utils.newNumberArray(#regionUVs) end
-	local uvs = self.uvs
-
-	if not self.region then
-		u = 0
-		v = 0
-		width = 1
-		height = 1
-	else
-		local region = self.region
-		local textureWidth = region.page.width
-		local textureHeight = region.page.height
-
-		if region.degrees == 90 then
-			u = region.u - (region.originalHeight - region.offsetY - region.height) / textureWidth
-			v = region.v - (region.originalWidth - region.offsetX - region.width) / textureHeight
-			width = region.originalHeight / textureWidth
-			height = region.originalWidth / textureHeight
-			local i = 0
-			local n = #uvs
-			while i < n do
-				uvs[i + 1] = u + regionUVs[i + 2] * width;
-				uvs[i + 2] = v + (1 - regionUVs[i + 1]) * height;
-				i = i + 2
-			end
-		elseif region.degrees == 180 then
-			u = region.u - (region.originalWidth - region.offsetX - region.width) / textureWidth
-			v = region.v - region.offsetY / textureHeight
-			width = region.originalWidth / textureWidth
-			height = region.originalHeight / textureHeight
-			local i = 0
-			local n = #uvs
-			while i < n do
-				uvs[i + 1] = u + (1 - regionUVs[i + 1]) * width;
-				uvs[i + 2] = v + (1 - regionUVs[i + 2]) * height;
-				i = i + 2
-			end
-		elseif region.degrees == 270 then
-			u = region.u - region.offsetY / textureWidth
-			v = region.v - region.offsetX / textureHeight
-			width = region.originalHeight / textureWidth
-			height = region.originalWidth / textureHeight
-			local i = 0
-			local n = #uvs
-			while i < n do
-				uvs[i + 1] = u + (1 - regionUVs[i + 2]) * width;
-				uvs[i + 2] = v + regionUVs[i + 1] * height;
-				i = i + 2
-			end
-		else
-			u = region.u - region.offsetX / textureWidth;
-			v = region.v - (region.originalHeight - region.offsetY - region.height) / textureHeight;
-			width = region.originalWidth / textureWidth;
-			height = region.originalHeight / textureHeight;
-			local i = 0
-			local n = #uvs
-			while i < n do
-				uvs[i + 1] = u + regionUVs[i + 1] * width;
-				uvs[i + 2] = v + regionUVs[i + 2] * height;
-				i = i + 2
-			end
-		end
-	end
-end
-
-function MeshAttachment:setParentMesh (parentMesh)
-	self.parentMesh = parentMesh
-	if parentMesh then
-		self.bones = parentMesh.bones
-		self.vertices = parentMesh.vertices
-		self.worldVerticesLength = parentMesh.worldVerticesLength
-		self.regionUVs = parentMesh.regionUVs
-		self.triangles = parentMesh.triangles
-		self.hullLength = parentMesh.hullLength
-	end
-end
-
-function MeshAttachment:copy ()
-	if self.parentMesh then return self:newLinkedMesh() end
-
-	local copy = MeshAttachment.new(self.name)
-	copy.region = self.region
-	copy.path = self.path
-	copy.color:setFrom(self.color)
-
-	self:copyTo(copy)
-	copy.regionUVs = utils.copy(self.regionUVs)
-	copy.uvs = utils.copy(self.uvs)
-	copy.triangles = utils.copy(self.triangles)
-	copy.hullLength = self.hullLength
-	if self.edges then
-		copy.edges = utils.copy(edges)
-	end
-	copy.width = self.width
-	copy.height = self.height
-
-	return copy
-end
-
-function MeshAttachment:newLinkedMesh ()
-	local copy = MeshAttachment.new(self.name)
-	copy.region = self.region
-	copy.path = self.path
-	copy.color:setFrom(self.color)
-	if self.parentMesh then
-		copy.deformAttachment = self.parentMesh
-	else
-		copy.deformAttachment = self
-	end
-	copy:setParentMesh(self.parentMesh)
-	copy:updateUVs()
-	return copy
-end
-
-return MeshAttachment
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+local AttachmentType = require "spine-lua.attachments.AttachmentType"
+local VertexAttachment = require "spine-lua.attachments.VertexAttachment"
+local utils = require "spine-lua.utils"
+local Color = require "spine-lua.Color"
+
+local MeshAttachment = {}
+MeshAttachment.__index = MeshAttachment
+setmetatable(MeshAttachment, { __index = VertexAttachment })
+
+function MeshAttachment.new (name)
+	if not name then error("name cannot be nil", 2) end
+
+	local self = VertexAttachment.new(name, AttachmentType.mesh)
+	self.region = nil
+	self.path = nil
+	self.regionUVs = nil
+	self.uvs = nil
+	self.triangles = nil
+	self.color = Color.newWith(1, 1, 1, 1)
+	self.hullLength = 0
+	self.parentMesh = nil
+	self.tempColor = Color.newWith(1, 1, 1, 1)
+	self.width = 0
+	self.height = 0
+	setmetatable(self, MeshAttachment)
+	return self
+end
+
+function MeshAttachment:updateUVs ()
+	local u = 0
+	local v = 0
+	local width = 0
+	local height = 0
+
+	local regionUVs = self.regionUVs
+	if not self.uvs or (#self.uvs ~= #regionUVs) then self.uvs = utils.newNumberArray(#regionUVs) end
+	local uvs = self.uvs
+
+	if not self.region then
+		u = 0
+		v = 0
+		width = 1
+		height = 1
+	else
+		local region = self.region
+		local textureWidth = region.page.width
+		local textureHeight = region.page.height
+
+		if region.degrees == 90 then
+			u = region.u - (region.originalHeight - region.offsetY - region.height) / textureWidth
+			v = region.v - (region.originalWidth - region.offsetX - region.width) / textureHeight
+			width = region.originalHeight / textureWidth
+			height = region.originalWidth / textureHeight
+			local i = 0
+			local n = #uvs
+			while i < n do
+				uvs[i + 1] = u + regionUVs[i + 2] * width;
+				uvs[i + 2] = v + (1 - regionUVs[i + 1]) * height;
+				i = i + 2
+			end
+		elseif region.degrees == 180 then
+			u = region.u - (region.originalWidth - region.offsetX - region.width) / textureWidth
+			v = region.v - region.offsetY / textureHeight
+			width = region.originalWidth / textureWidth
+			height = region.originalHeight / textureHeight
+			local i = 0
+			local n = #uvs
+			while i < n do
+				uvs[i + 1] = u + (1 - regionUVs[i + 1]) * width;
+				uvs[i + 2] = v + (1 - regionUVs[i + 2]) * height;
+				i = i + 2
+			end
+		elseif region.degrees == 270 then
+			u = region.u - region.offsetY / textureWidth
+			v = region.v - region.offsetX / textureHeight
+			width = region.originalHeight / textureWidth
+			height = region.originalWidth / textureHeight
+			local i = 0
+			local n = #uvs
+			while i < n do
+				uvs[i + 1] = u + (1 - regionUVs[i + 2]) * width;
+				uvs[i + 2] = v + regionUVs[i + 1] * height;
+				i = i + 2
+			end
+		else
+			u = region.u - region.offsetX / textureWidth;
+			v = region.v - (region.originalHeight - region.offsetY - region.height) / textureHeight;
+			width = region.originalWidth / textureWidth;
+			height = region.originalHeight / textureHeight;
+			local i = 0
+			local n = #uvs
+			while i < n do
+				uvs[i + 1] = u + regionUVs[i + 1] * width;
+				uvs[i + 2] = v + regionUVs[i + 2] * height;
+				i = i + 2
+			end
+		end
+	end
+end
+
+function MeshAttachment:setParentMesh (parentMesh)
+	self.parentMesh = parentMesh
+	if parentMesh then
+		self.bones = parentMesh.bones
+		self.vertices = parentMesh.vertices
+		self.worldVerticesLength = parentMesh.worldVerticesLength
+		self.regionUVs = parentMesh.regionUVs
+		self.triangles = parentMesh.triangles
+		self.hullLength = parentMesh.hullLength
+	end
+end
+
+function MeshAttachment:copy ()
+	if self.parentMesh then return self:newLinkedMesh() end
+
+	local copy = MeshAttachment.new(self.name)
+	copy.region = self.region
+	copy.path = self.path
+	copy.color:setFrom(self.color)
+
+	self:copyTo(copy)
+	copy.regionUVs = utils.copy(self.regionUVs)
+	copy.uvs = utils.copy(self.uvs)
+	copy.triangles = utils.copy(self.triangles)
+	copy.hullLength = self.hullLength
+	if self.edges then
+		copy.edges = utils.copy(edges)
+	end
+	copy.width = self.width
+	copy.height = self.height
+
+	return copy
+end
+
+function MeshAttachment:newLinkedMesh ()
+	local copy = MeshAttachment.new(self.name)
+	copy.region = self.region
+	copy.path = self.path
+	copy.color:setFrom(self.color)
+	if self.parentMesh then
+		copy.deformAttachment = self.parentMesh
+	else
+		copy.deformAttachment = self
+	end
+	copy:setParentMesh(self.parentMesh)
+	copy:updateUVs()
+	return copy
+end
+
+return MeshAttachment

+ 61 - 61
spine-lua/attachments/PathAttachment.lua → spine-lua/spine-lua/attachments/PathAttachment.lua

@@ -1,61 +1,61 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local AttachmentType = require "spine-lua.attachments.AttachmentType"
-local VertexAttachment = require "spine-lua.attachments.VertexAttachment"
-local Color = require "spine-lua.Color"
-local utils = require "spine-lua.utils"
-
-local PathAttachment = {}
-PathAttachment.__index = PathAttachment
-setmetatable(PathAttachment, { __index = VertexAttachment })
-
-function PathAttachment.new (name)
-	if not name then error("name cannot be nil", 2) end
-
-	local self = VertexAttachment.new(name, AttachmentType.path)
-	self.lengths = nil
-	self.color = Color.newWith(1, 1, 1, 1)
-	self.closed = false
-	self.constantSpeed = false
-	setmetatable(self, PathAttachment)
-	return self
-end
-
-function PathAttachment:copy ()
-	local copy = PathAttachment.new(self.name)
-	self:copyTo(copy)
-	copy.length = utils.copy(self.lengths)
-	copy.closed = self.closed
-	copy.constantSpeed = self.constantSpeed
-	copy.color:setFrom(self.color)
-	return copy
-end
-
-return PathAttachment
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local AttachmentType = require "spine-lua.attachments.AttachmentType"
+local VertexAttachment = require "spine-lua.attachments.VertexAttachment"
+local Color = require "spine-lua.Color"
+local utils = require "spine-lua.utils"
+
+local PathAttachment = {}
+PathAttachment.__index = PathAttachment
+setmetatable(PathAttachment, { __index = VertexAttachment })
+
+function PathAttachment.new (name)
+	if not name then error("name cannot be nil", 2) end
+
+	local self = VertexAttachment.new(name, AttachmentType.path)
+	self.lengths = nil
+	self.color = Color.newWith(1, 1, 1, 1)
+	self.closed = false
+	self.constantSpeed = false
+	setmetatable(self, PathAttachment)
+	return self
+end
+
+function PathAttachment:copy ()
+	local copy = PathAttachment.new(self.name)
+	self:copyTo(copy)
+	copy.length = utils.copy(self.lengths)
+	copy.closed = self.closed
+	copy.constantSpeed = self.constantSpeed
+	copy.color:setFrom(self.color)
+	return copy
+end
+
+return PathAttachment

+ 0 - 0
spine-lua/attachments/PointAttachment.lua → spine-lua/spine-lua/attachments/PointAttachment.lua


+ 264 - 264
spine-lua/attachments/RegionAttachment.lua → spine-lua/spine-lua/attachments/RegionAttachment.lua

@@ -1,264 +1,264 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local setmetatable = setmetatable
-local math_pi = math.pi
-local math_sin = math.sin
-local math_cos = math.cos
-
-local AttachmentType = require "spine-lua.attachments.AttachmentType"
-local Attachment = require "spine-lua.attachments.Attachment"
-local Color = require "spine-lua.Color"
-local Utils = require "spine-lua.utils"
-
-local OX1 = 1
-local OY1 = 2
-local OX2 = 3
-local OY2 = 4
-local OX3 = 5
-local OY3 = 6
-local OX4 = 7
-local OY4 = 8
-
-local X1 = 1
-local Y1 = 2
-local U1 = 3
-local V1 = 4
-local C1R = 5
-local C1G = 6
-local C1B = 7
-local C1A = 8
-
-local X2 = 9
-local Y2 = 10
-local U2 = 11
-local V2 = 12
-local C2R = 13
-local C2G = 14
-local C2B = 15
-local C2A = 16
-
-local X3 = 17
-local Y3 = 18
-local U3 = 19
-local V3 = 20
-local C3R = 21
-local C3G = 22
-local C3B = 23
-local C3A = 24
-
-local X4 = 25
-local Y4 = 26
-local U4 = 27
-local V4 = 28
-local C4R = 29
-local C4G = 30
-local C4B = 31
-local C4A = 32
-
-local RegionAttachment = {}
-RegionAttachment.__index = RegionAttachment
-setmetatable(RegionAttachment, { __index = Attachment })
-
-RegionAttachment.OX1 = 1
-RegionAttachment.OY1 = 2
-RegionAttachment.OX2 = 3
-RegionAttachment.OY2 = 4
-RegionAttachment.OX3 = 5
-RegionAttachment.OY3 = 6
-RegionAttachment.OX4 = 7
-RegionAttachment.OY4 = 8
-
-RegionAttachment.X1 = 1
-RegionAttachment.Y1 = 2
-RegionAttachment.U1 = 3
-RegionAttachment.V1 = 4
-RegionAttachment.C1R = 5
-RegionAttachment.C1G = 6
-RegionAttachment.C1B = 7
-RegionAttachment.C1A = 8
-
-RegionAttachment.X2 = 9
-RegionAttachment.Y2 = 10
-RegionAttachment.U2 = 11
-RegionAttachment.V2 = 12
-RegionAttachment.C2R = 13
-RegionAttachment.C2G = 14
-RegionAttachment.C2B = 15
-RegionAttachment.C2A = 16
-
-RegionAttachment.X3 = 17
-RegionAttachment.Y3 = 18
-RegionAttachment.U3 = 19
-RegionAttachment.V3 = 20
-RegionAttachment.C3R = 21
-RegionAttachment.C3G = 22
-RegionAttachment.C3B = 23
-RegionAttachment.C3A = 24
-
-RegionAttachment.X4 = 25
-RegionAttachment.Y4 = 26
-RegionAttachment.U4 = 27
-RegionAttachment.V4 = 28
-RegionAttachment.C4R = 29
-RegionAttachment.C4G = 30
-RegionAttachment.C4B = 31
-RegionAttachment.C4A = 32
-
-function RegionAttachment.new (name)
-	if not name then error("name cannot be nil", 2) end
-
-	local self = Attachment.new(name, AttachmentType.region)
-	self.x = 0
-	self.y = 0
-	self.scaleX = 1
-	self.scaleY = 1
-	self.rotation = 0
-	self.width = 0
-	self.height = 0
-	self.color = Color.newWith(1, 1, 1, 1)
-	self.path = nil
-	self.rendererObject = nil
-	self.region = nil
-	self.offset = Utils.newNumberArray(8)
-	self.uvs = Utils.newNumberArray(8)
-	self.tempColor = Color.newWith(1, 1, 1, 1)
-	setmetatable(self, RegionAttachment)
-
-	return self
-end
-
-function RegionAttachment:updateOffset ()
-	local regionScaleX = self.width / self.region.originalWidth * self.scaleX
-	local regionScaleY = self.height / self.region.originalHeight * self.scaleY
-	local localX = -self.width / 2 * self.scaleX + self.region.offsetX * regionScaleX
-	local localY = -self.height / 2 * self.scaleY + self.region.offsetY * regionScaleY
-	local localX2 = localX + self.region.width * regionScaleX
-	local localY2 = localY + self.region.height * regionScaleY
-	local radians = self.rotation * math_pi / 180
-	local cos = math_cos(radians)
-	local sin = math_sin(radians)
-	local localXCos = localX * cos + self.x
-	local localXSin = localX * sin
-	local localYCos = localY * cos + self.y
-	local localYSin = localY * sin
-	local localX2Cos = localX2 * cos + self.x
-	local localX2Sin = localX2 * sin
-	local localY2Cos = localY2 * cos + self.y
-	local localY2Sin = localY2 * sin
-	local offset = self.offset
-	offset[OX1] = localXCos - localYSin
-	offset[OY1] = localYCos + localXSin
-	offset[OX2] = localXCos - localY2Sin
-	offset[OY2] = localY2Cos + localXSin
-	offset[OX3] = localX2Cos - localY2Sin
-	offset[OY3] = localY2Cos + localX2Sin
-	offset[OX4] = localX2Cos - localYSin
-	offset[OY4] = localYCos + localX2Sin
-end
-
-function RegionAttachment:setRegion (region)
-	local uvs = self.uvs
-	if region.rotate then
-		uvs[5] = region.u
-		uvs[6] = region.v2
-		uvs[7] = region.u
-		uvs[8] = region.v
-		uvs[1] = region.u2
-		uvs[2] = region.v
-		uvs[3] = region.u2
-		uvs[4] = region.v2
-	else
-		uvs[3] = region.u
-		uvs[4] = region.v2
-		uvs[5] = region.u
-		uvs[6] = region.v
-		uvs[7] = region.u2
-		uvs[8] = region.v
-		uvs[1] = region.u2
-		uvs[2] = region.v2
-	end
-end
-
-function RegionAttachment:computeWorldVertices (bone, worldVertices, offset, stride)
-	offset = offset + 1
-	local vertexOffset = self.offset
-	local x = bone.worldX
-	local y = bone.worldY
-	local a = bone.a
-	local b = bone.b
-	local c = bone.c
-	local d = bone.d
-	local offsetX = 0
-	local offsetY = 0
-
-	offsetX = vertexOffset[7]
-	offsetY = vertexOffset[8]
-	worldVertices[offset] = offsetX * a + offsetY * b + x -- br
-	worldVertices[offset + 1] = offsetX * c + offsetY * d + y
-	offset = offset + stride
-
-	offsetX = vertexOffset[1]
-	offsetY = vertexOffset[2]
-	worldVertices[offset] = offsetX * a + offsetY * b + x -- bl
-	worldVertices[offset + 1] = offsetX * c + offsetY * d + y
-	offset = offset + stride
-
-	offsetX = vertexOffset[3]
-	offsetY = vertexOffset[4]
-	worldVertices[offset] = offsetX * a + offsetY * b + x -- ul
-	worldVertices[offset + 1] = offsetX * c + offsetY * d + y
-	offset = offset + stride
-
-	offsetX = vertexOffset[5]
-	offsetY = vertexOffset[6]
-	worldVertices[offset] = offsetX * a + offsetY * b + x -- ur
-	worldVertices[offset + 1] = offsetX * c + offsetY * d + y
-end
-
-function RegionAttachment:copy ()
-	local copy = RegionAttachment.new(self.name)
-	copy.x = self.x
-	copy.y = self.y
-	copy.scaleX = self.scaleX
-	copy.scaleY = self.scaleY
-	copy.rotation = self.rotation
-	copy.width = self.width
-	copy.height = self.height
-	copy.color:setFrom(self.color)
-	copy.path = self.path
-	copy.rendererObject = self.rendererObject
-	copy.region = self.region
-	copy.offset = Utils.copy(self.offset)
-	copy.uvs = Utils.copy(self.uvs)
-	copy.tempColor:setFrom(self.tempColor)
-	return copy
-end
-
-return RegionAttachment
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+local math_pi = math.pi
+local math_sin = math.sin
+local math_cos = math.cos
+
+local AttachmentType = require "spine-lua.attachments.AttachmentType"
+local Attachment = require "spine-lua.attachments.Attachment"
+local Color = require "spine-lua.Color"
+local Utils = require "spine-lua.utils"
+
+local OX1 = 1
+local OY1 = 2
+local OX2 = 3
+local OY2 = 4
+local OX3 = 5
+local OY3 = 6
+local OX4 = 7
+local OY4 = 8
+
+local X1 = 1
+local Y1 = 2
+local U1 = 3
+local V1 = 4
+local C1R = 5
+local C1G = 6
+local C1B = 7
+local C1A = 8
+
+local X2 = 9
+local Y2 = 10
+local U2 = 11
+local V2 = 12
+local C2R = 13
+local C2G = 14
+local C2B = 15
+local C2A = 16
+
+local X3 = 17
+local Y3 = 18
+local U3 = 19
+local V3 = 20
+local C3R = 21
+local C3G = 22
+local C3B = 23
+local C3A = 24
+
+local X4 = 25
+local Y4 = 26
+local U4 = 27
+local V4 = 28
+local C4R = 29
+local C4G = 30
+local C4B = 31
+local C4A = 32
+
+local RegionAttachment = {}
+RegionAttachment.__index = RegionAttachment
+setmetatable(RegionAttachment, { __index = Attachment })
+
+RegionAttachment.OX1 = 1
+RegionAttachment.OY1 = 2
+RegionAttachment.OX2 = 3
+RegionAttachment.OY2 = 4
+RegionAttachment.OX3 = 5
+RegionAttachment.OY3 = 6
+RegionAttachment.OX4 = 7
+RegionAttachment.OY4 = 8
+
+RegionAttachment.X1 = 1
+RegionAttachment.Y1 = 2
+RegionAttachment.U1 = 3
+RegionAttachment.V1 = 4
+RegionAttachment.C1R = 5
+RegionAttachment.C1G = 6
+RegionAttachment.C1B = 7
+RegionAttachment.C1A = 8
+
+RegionAttachment.X2 = 9
+RegionAttachment.Y2 = 10
+RegionAttachment.U2 = 11
+RegionAttachment.V2 = 12
+RegionAttachment.C2R = 13
+RegionAttachment.C2G = 14
+RegionAttachment.C2B = 15
+RegionAttachment.C2A = 16
+
+RegionAttachment.X3 = 17
+RegionAttachment.Y3 = 18
+RegionAttachment.U3 = 19
+RegionAttachment.V3 = 20
+RegionAttachment.C3R = 21
+RegionAttachment.C3G = 22
+RegionAttachment.C3B = 23
+RegionAttachment.C3A = 24
+
+RegionAttachment.X4 = 25
+RegionAttachment.Y4 = 26
+RegionAttachment.U4 = 27
+RegionAttachment.V4 = 28
+RegionAttachment.C4R = 29
+RegionAttachment.C4G = 30
+RegionAttachment.C4B = 31
+RegionAttachment.C4A = 32
+
+function RegionAttachment.new (name)
+	if not name then error("name cannot be nil", 2) end
+
+	local self = Attachment.new(name, AttachmentType.region)
+	self.x = 0
+	self.y = 0
+	self.scaleX = 1
+	self.scaleY = 1
+	self.rotation = 0
+	self.width = 0
+	self.height = 0
+	self.color = Color.newWith(1, 1, 1, 1)
+	self.path = nil
+	self.rendererObject = nil
+	self.region = nil
+	self.offset = Utils.newNumberArray(8)
+	self.uvs = Utils.newNumberArray(8)
+	self.tempColor = Color.newWith(1, 1, 1, 1)
+	setmetatable(self, RegionAttachment)
+
+	return self
+end
+
+function RegionAttachment:updateOffset ()
+	local regionScaleX = self.width / self.region.originalWidth * self.scaleX
+	local regionScaleY = self.height / self.region.originalHeight * self.scaleY
+	local localX = -self.width / 2 * self.scaleX + self.region.offsetX * regionScaleX
+	local localY = -self.height / 2 * self.scaleY + self.region.offsetY * regionScaleY
+	local localX2 = localX + self.region.width * regionScaleX
+	local localY2 = localY + self.region.height * regionScaleY
+	local radians = self.rotation * math_pi / 180
+	local cos = math_cos(radians)
+	local sin = math_sin(radians)
+	local localXCos = localX * cos + self.x
+	local localXSin = localX * sin
+	local localYCos = localY * cos + self.y
+	local localYSin = localY * sin
+	local localX2Cos = localX2 * cos + self.x
+	local localX2Sin = localX2 * sin
+	local localY2Cos = localY2 * cos + self.y
+	local localY2Sin = localY2 * sin
+	local offset = self.offset
+	offset[OX1] = localXCos - localYSin
+	offset[OY1] = localYCos + localXSin
+	offset[OX2] = localXCos - localY2Sin
+	offset[OY2] = localY2Cos + localXSin
+	offset[OX3] = localX2Cos - localY2Sin
+	offset[OY3] = localY2Cos + localX2Sin
+	offset[OX4] = localX2Cos - localYSin
+	offset[OY4] = localYCos + localX2Sin
+end
+
+function RegionAttachment:setRegion (region)
+	local uvs = self.uvs
+	if region.rotate then
+		uvs[5] = region.u
+		uvs[6] = region.v2
+		uvs[7] = region.u
+		uvs[8] = region.v
+		uvs[1] = region.u2
+		uvs[2] = region.v
+		uvs[3] = region.u2
+		uvs[4] = region.v2
+	else
+		uvs[3] = region.u
+		uvs[4] = region.v2
+		uvs[5] = region.u
+		uvs[6] = region.v
+		uvs[7] = region.u2
+		uvs[8] = region.v
+		uvs[1] = region.u2
+		uvs[2] = region.v2
+	end
+end
+
+function RegionAttachment:computeWorldVertices (bone, worldVertices, offset, stride)
+	offset = offset + 1
+	local vertexOffset = self.offset
+	local x = bone.worldX
+	local y = bone.worldY
+	local a = bone.a
+	local b = bone.b
+	local c = bone.c
+	local d = bone.d
+	local offsetX = 0
+	local offsetY = 0
+
+	offsetX = vertexOffset[7]
+	offsetY = vertexOffset[8]
+	worldVertices[offset] = offsetX * a + offsetY * b + x -- br
+	worldVertices[offset + 1] = offsetX * c + offsetY * d + y
+	offset = offset + stride
+
+	offsetX = vertexOffset[1]
+	offsetY = vertexOffset[2]
+	worldVertices[offset] = offsetX * a + offsetY * b + x -- bl
+	worldVertices[offset + 1] = offsetX * c + offsetY * d + y
+	offset = offset + stride
+
+	offsetX = vertexOffset[3]
+	offsetY = vertexOffset[4]
+	worldVertices[offset] = offsetX * a + offsetY * b + x -- ul
+	worldVertices[offset + 1] = offsetX * c + offsetY * d + y
+	offset = offset + stride
+
+	offsetX = vertexOffset[5]
+	offsetY = vertexOffset[6]
+	worldVertices[offset] = offsetX * a + offsetY * b + x -- ur
+	worldVertices[offset + 1] = offsetX * c + offsetY * d + y
+end
+
+function RegionAttachment:copy ()
+	local copy = RegionAttachment.new(self.name)
+	copy.x = self.x
+	copy.y = self.y
+	copy.scaleX = self.scaleX
+	copy.scaleY = self.scaleY
+	copy.rotation = self.rotation
+	copy.width = self.width
+	copy.height = self.height
+	copy.color:setFrom(self.color)
+	copy.path = self.path
+	copy.rendererObject = self.rendererObject
+	copy.region = self.region
+	copy.offset = Utils.copy(self.offset)
+	copy.uvs = Utils.copy(self.uvs)
+	copy.tempColor:setFrom(self.tempColor)
+	return copy
+end
+
+return RegionAttachment

+ 168 - 168
spine-lua/attachments/VertexAttachment.lua → spine-lua/spine-lua/attachments/VertexAttachment.lua

@@ -1,168 +1,168 @@
--------------------------------------------------------------------------------
--- 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 the logic in this file uses 0-based indexing. Each array
--- access adds 1 to the calculated index. We should switch the logic
--- to 1-based indexing eventually.
-
-local setmetatable = setmetatable
-local utils = require "spine-lua.utils"
-local AttachmentType = require "spine-lua.attachments.AttachmentType"
-local Attachment = require "spine-lua.attachments.Attachment"
-
-local nextID = 0;
-local SHL_11 = 2048;
-
-local VertexAttachment = {}
-VertexAttachment.__index = VertexAttachment
-setmetatable(VertexAttachment, { __index = Attachment })
-
-function VertexAttachment.new (name, attachmentType)
-	local self = Attachment.new(name, attachmentType)
-	self.bones = nil
-	self.vertices = nil
-	self.worldVerticesLength = 0
-	while nextID > 65535 do
-		nextID = nextID - 65535
-	end
-	self.id = nextID * SHL_11
-	self.deformAttachment = self
-	nextID = nextID + 1
-	setmetatable(self, VertexAttachment)
-	return self
-end
-
-function VertexAttachment:computeWorldVertices (slot, start, count, worldVertices, offset, stride)
-	count = offset + (count / 2) * stride
-	local skeleton = slot.bone.skeleton
-	local deformArray = slot.deform
-	local vertices = self.vertices
-	local bones = self.bones
-	if not bones then
-		if #deformArray > 0 then vertices = deformArray end
-		local bone = slot.bone
-		x = bone.worldX
-		y = bone.worldY
-		local a = bone.a
-		local b = bone.b
-		local c = bone.c
-		local d = bone.d
-		local v = start
-		local w = offset
-		while w < count do
-			local vx = vertices[v + 1]
-			local vy = vertices[v + 2]
-			worldVertices[w + 1] = vx * a + vy * b + x
-			worldVertices[w + 2] = vx * c + vy * d + y
-			v = v + 2
-			w = w + stride
-		end
-		return
-	end
-	local v = 0
-	local skip = 0
-	local i = 0
-	while i < start do
-		local n = bones[v + 1]
-		v = v + n + 1
-		skip = skip + n
-		i = i + 2
-	end
-	local skeletonBones = skeleton.bones
-	if #deformArray == 0 then
-		local w = offset
-		local b = skip * 3
-		while w < count do
-			local wx = 0
-			local wy = 0
-			local n = bones[v + 1]
-			v = v + 1
-			n = n + v
-			while v < n do
-				local bone = skeletonBones[bones[v + 1]]
-				local vx = vertices[b + 1]
-				local vy = vertices[b + 2]
-				local weight = vertices[b + 3]
-				wx = wx + (vx * bone.a + vy * bone.b + bone.worldX) * weight
-				wy = wy + (vx * bone.c + vy * bone.d + bone.worldY) * weight
-				v = v + 1
-				b = b + 3
-			end
-			worldVertices[w + 1] = wx
-			worldVertices[w + 2] = wy
-			w = w + stride
-		end
-	else
-		local deform = deformArray
-		local w = offset
-		local b = skip * 3
-		local f = skip * 2
-		while w < count do
-			local wx = 0
-			local wy = 0
-			local n = bones[v + 1]
-			v = v + 1
-			n = n + v
-
-			while v < n do
-				local bone = skeletonBones[bones[v + 1]]
-				local vx = vertices[b + 1] + deform[f + 1]
-				local vy = vertices[b + 2] + deform[f + 2]
-				local weight = vertices[b + 3]
-				wx = wx + (vx * bone.a + vy * bone.b + bone.worldX) * weight
-				wy = wy + (vx * bone.c + vy * bone.d + bone.worldY) * weight
-				v = v + 1
-				b = b + 3
-				f = f + 2
-			end
-			worldVertices[w + 1] = wx
-			worldVertices[w + 2] = wy
-			w = w + stride
-		end
-	end
-end
-
-function VertexAttachment:copyTo (attachment)
-	if self.bones then
-		attachment.bones = utils.copy(self.bones)
-	else
-		attachment.bones = nil
-	end
-
-	if self.vertices then
-		attachment.vertices = utils.copy(self.vertices)
-	else
-		attachment.vertices = nil
-	end
-
-	attachment.worldVerticesLength = self.worldVerticesLength
-	attachment.deformAttachment = self.deformAttachment
-end
-
-return VertexAttachment
+-------------------------------------------------------------------------------
+-- 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 the logic in this file uses 0-based indexing. Each array
+-- access adds 1 to the calculated index. We should switch the logic
+-- to 1-based indexing eventually.
+
+local setmetatable = setmetatable
+local utils = require "spine-lua.utils"
+local AttachmentType = require "spine-lua.attachments.AttachmentType"
+local Attachment = require "spine-lua.attachments.Attachment"
+
+local nextID = 0;
+local SHL_11 = 2048;
+
+local VertexAttachment = {}
+VertexAttachment.__index = VertexAttachment
+setmetatable(VertexAttachment, { __index = Attachment })
+
+function VertexAttachment.new (name, attachmentType)
+	local self = Attachment.new(name, attachmentType)
+	self.bones = nil
+	self.vertices = nil
+	self.worldVerticesLength = 0
+	while nextID > 65535 do
+		nextID = nextID - 65535
+	end
+	self.id = nextID * SHL_11
+	self.deformAttachment = self
+	nextID = nextID + 1
+	setmetatable(self, VertexAttachment)
+	return self
+end
+
+function VertexAttachment:computeWorldVertices (slot, start, count, worldVertices, offset, stride)
+	count = offset + (count / 2) * stride
+	local skeleton = slot.bone.skeleton
+	local deformArray = slot.deform
+	local vertices = self.vertices
+	local bones = self.bones
+	if not bones then
+		if #deformArray > 0 then vertices = deformArray end
+		local bone = slot.bone
+		x = bone.worldX
+		y = bone.worldY
+		local a = bone.a
+		local b = bone.b
+		local c = bone.c
+		local d = bone.d
+		local v = start
+		local w = offset
+		while w < count do
+			local vx = vertices[v + 1]
+			local vy = vertices[v + 2]
+			worldVertices[w + 1] = vx * a + vy * b + x
+			worldVertices[w + 2] = vx * c + vy * d + y
+			v = v + 2
+			w = w + stride
+		end
+		return
+	end
+	local v = 0
+	local skip = 0
+	local i = 0
+	while i < start do
+		local n = bones[v + 1]
+		v = v + n + 1
+		skip = skip + n
+		i = i + 2
+	end
+	local skeletonBones = skeleton.bones
+	if #deformArray == 0 then
+		local w = offset
+		local b = skip * 3
+		while w < count do
+			local wx = 0
+			local wy = 0
+			local n = bones[v + 1]
+			v = v + 1
+			n = n + v
+			while v < n do
+				local bone = skeletonBones[bones[v + 1]]
+				local vx = vertices[b + 1]
+				local vy = vertices[b + 2]
+				local weight = vertices[b + 3]
+				wx = wx + (vx * bone.a + vy * bone.b + bone.worldX) * weight
+				wy = wy + (vx * bone.c + vy * bone.d + bone.worldY) * weight
+				v = v + 1
+				b = b + 3
+			end
+			worldVertices[w + 1] = wx
+			worldVertices[w + 2] = wy
+			w = w + stride
+		end
+	else
+		local deform = deformArray
+		local w = offset
+		local b = skip * 3
+		local f = skip * 2
+		while w < count do
+			local wx = 0
+			local wy = 0
+			local n = bones[v + 1]
+			v = v + 1
+			n = n + v
+
+			while v < n do
+				local bone = skeletonBones[bones[v + 1]]
+				local vx = vertices[b + 1] + deform[f + 1]
+				local vy = vertices[b + 2] + deform[f + 2]
+				local weight = vertices[b + 3]
+				wx = wx + (vx * bone.a + vy * bone.b + bone.worldX) * weight
+				wy = wy + (vx * bone.c + vy * bone.d + bone.worldY) * weight
+				v = v + 1
+				b = b + 3
+				f = f + 2
+			end
+			worldVertices[w + 1] = wx
+			worldVertices[w + 2] = wy
+			w = w + stride
+		end
+	end
+end
+
+function VertexAttachment:copyTo (attachment)
+	if self.bones then
+		attachment.bones = utils.copy(self.bones)
+	else
+		attachment.bones = nil
+	end
+
+	if self.vertices then
+		attachment.vertices = utils.copy(self.vertices)
+	else
+		attachment.vertices = nil
+	end
+
+	attachment.worldVerticesLength = self.worldVerticesLength
+	attachment.deformAttachment = self.deformAttachment
+end
+
+return VertexAttachment

+ 197 - 197
spine-lua/utils.lua → spine-lua/spine-lua/utils.lua

@@ -1,197 +1,197 @@
--------------------------------------------------------------------------------
--- Spine Runtimes License Agreement
--- Last updated January 1, 2020. Replaces all prior versions.
---
--- Copyright (c) 2013-2020, Esoteric Software LLC
---
--- Integration of the Spine Runtimes into software or otherwise creating
--- derivative works of the Spine Runtimes is permitted under the terms and
--- conditions of Section 2 of the Spine Editor License Agreement:
--- http://esotericsoftware.com/spine-editor-license
---
--- Otherwise, it is permitted to integrate the Spine Runtimes into software
--- or otherwise create derivative works of the Spine Runtimes (collectively,
--- "Products"), provided that each user of the Products must obtain their own
--- Spine Editor license and redistribution of the Products in any form must
--- include this license and copyright notice.
---
--- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
--- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
--- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
--- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
--- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
--- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
--- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
--- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
-
-local utils = {}
-
-local math_sqrt = math.sqrt
-local math_random = math.random
-
-utils.degRad = math.pi / 180
-
-function tablePrint (tt, indent, done)
-	done = done or {}
-	for key, value in pairs(tt) do
-		local spaces = string.rep (" ", indent)
-		if type(value) == "table" and not done [value] then
-			done [value] = true
-			print(spaces .. "{")
-			utils.print(value, indent + 2, done)
-			print(spaces .. "}")
-		else
-			io.write(spaces .. tostring(key) .. " = ")
-			utils.print(value, indent + 2, done)
-		end
-	end
-end
-
-function utils.print (value, indent, done)
-	indent = indent or 0
-	if "nil" == type(value) then
-		print(tostring(nil))
-	elseif "table" == type(value) then
-		local spaces = string.rep (" ", indent)
-		print(spaces .. "{")
-		tablePrint(value, indent + 2)
-		print(spaces .. "}")
-	elseif "string" == type(value) then
-		print("\"" .. value .. "\"")
-	else
-		print(tostring(value))
-	end
-end
-
-function utils.indexOf (haystack, needle)
-	for i,value in ipairs(haystack) do
-		if value == needle then return i end
-	end
-	return nil
-end
-
-function utils.copy (from, to)
-	if not to then to = {} end
-	for k,v in pairs(from) do
-		to[k] = v
-	end
-	return to
-end
-
-function utils.newNumberArray (size)
-	local a = {}
-	local i = 1
-	while i <= size do
-		a[i] = 0
-		i = i + 1
-	end
-	return a
-end
-
-function utils.newNumberArrayZero (size)
-	local a = {}
-	local i = 0
-	while i < size do
-		a[i] = 0
-		i = i + 1
-	end
-	return a
-end
-
-function utils.setArraySize (array, size)
-	if #array == size then return array end
-	if #array < size then
-		local i = #array + 1
-		while i <= size do
-			array[i] = 0
-			i = i + 1
-		end
-	else
-		local originalSize = #array
-		local i = originalSize
-		while i > size do
-			array[i] = nil -- dirty trick to appease # without realloc
-			i = i - 1
-		end
-	end
-	return array
-end
-
-function utils.arrayCopy (src, srcOffset, dst, dstOffset, size)
-	local n = srcOffset + size
-	while srcOffset < n do
-		dst[dstOffset] = src[srcOffset]
-		dstOffset = dstOffset + 1
-		srcOffset = srcOffset + 1
-	end
-end
-
-function utils.arrayContains(array, element)
-	for i, arrayElement in ipairs(array) do
-		if arrayElement == element then return true end
-	end
-	return false
-end
-
-function utils.clamp (value, min, max)
-	if value < min then return min end
-	if value > max then return max end
-	return value
-end
-
-function utils.signum (value)
-	if value < 0 then
-		return -1
-	elseif value > 0 then
-		return 1
-	else
-		return 0
-	end
-end
-
--- Implements Java float modulo
-function utils.mod(a, b)
-	if b < 0 then b = -b end
-	if a < 0 then
-		return -(-a % b)
-	else
-		return a % b
-	end
-end
-
-function utils.randomTriangular(min, max)
-	return utils.randomTriangularWith(min, max, (min + max) * 0.5)
-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
-	return max - math_sqrt((1 - u) * d * (max - mode))
-end
-
-function utils.testBit(value, bit)
-	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 % (2 * bit) >= bit then
-		return value
-	end
-	return value + bit
-end
-
-function utils.clearBit(value, bit)
-	if (value == nil) then return 0 end
-	if value % (2 * bit) >= bit then
-		return value - bit
-	end
-	return value
-end
-
-return utils
+-------------------------------------------------------------------------------
+-- Spine Runtimes License Agreement
+-- Last updated January 1, 2020. Replaces all prior versions.
+--
+-- Copyright (c) 2013-2020, Esoteric Software LLC
+--
+-- Integration of the Spine Runtimes into software or otherwise creating
+-- derivative works of the Spine Runtimes is permitted under the terms and
+-- conditions of Section 2 of the Spine Editor License Agreement:
+-- http://esotericsoftware.com/spine-editor-license
+--
+-- Otherwise, it is permitted to integrate the Spine Runtimes into software
+-- or otherwise create derivative works of the Spine Runtimes (collectively,
+-- "Products"), provided that each user of the Products must obtain their own
+-- Spine Editor license and redistribution of the Products in any form must
+-- include this license and copyright notice.
+--
+-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local utils = {}
+
+local math_sqrt = math.sqrt
+local math_random = math.random
+
+utils.degRad = math.pi / 180
+
+function tablePrint (tt, indent, done)
+	done = done or {}
+	for key, value in pairs(tt) do
+		local spaces = string.rep (" ", indent)
+		if type(value) == "table" and not done [value] then
+			done [value] = true
+			print(spaces .. "{")
+			utils.print(value, indent + 2, done)
+			print(spaces .. "}")
+		else
+			io.write(spaces .. tostring(key) .. " = ")
+			utils.print(value, indent + 2, done)
+		end
+	end
+end
+
+function utils.print (value, indent, done)
+	indent = indent or 0
+	if "nil" == type(value) then
+		print(tostring(nil))
+	elseif "table" == type(value) then
+		local spaces = string.rep (" ", indent)
+		print(spaces .. "{")
+		tablePrint(value, indent + 2)
+		print(spaces .. "}")
+	elseif "string" == type(value) then
+		print("\"" .. value .. "\"")
+	else
+		print(tostring(value))
+	end
+end
+
+function utils.indexOf (haystack, needle)
+	for i,value in ipairs(haystack) do
+		if value == needle then return i end
+	end
+	return nil
+end
+
+function utils.copy (from, to)
+	if not to then to = {} end
+	for k,v in pairs(from) do
+		to[k] = v
+	end
+	return to
+end
+
+function utils.newNumberArray (size)
+	local a = {}
+	local i = 1
+	while i <= size do
+		a[i] = 0
+		i = i + 1
+	end
+	return a
+end
+
+function utils.newNumberArrayZero (size)
+	local a = {}
+	local i = 0
+	while i < size do
+		a[i] = 0
+		i = i + 1
+	end
+	return a
+end
+
+function utils.setArraySize (array, size)
+	if #array == size then return array end
+	if #array < size then
+		local i = #array + 1
+		while i <= size do
+			array[i] = 0
+			i = i + 1
+		end
+	else
+		local originalSize = #array
+		local i = originalSize
+		while i > size do
+			array[i] = nil -- dirty trick to appease # without realloc
+			i = i - 1
+		end
+	end
+	return array
+end
+
+function utils.arrayCopy (src, srcOffset, dst, dstOffset, size)
+	local n = srcOffset + size
+	while srcOffset < n do
+		dst[dstOffset] = src[srcOffset]
+		dstOffset = dstOffset + 1
+		srcOffset = srcOffset + 1
+	end
+end
+
+function utils.arrayContains(array, element)
+	for i, arrayElement in ipairs(array) do
+		if arrayElement == element then return true end
+	end
+	return false
+end
+
+function utils.clamp (value, min, max)
+	if value < min then return min end
+	if value > max then return max end
+	return value
+end
+
+function utils.signum (value)
+	if value < 0 then
+		return -1
+	elseif value > 0 then
+		return 1
+	else
+		return 0
+	end
+end
+
+-- Implements Java float modulo
+function utils.mod(a, b)
+	if b < 0 then b = -b end
+	if a < 0 then
+		return -(-a % b)
+	else
+		return a % b
+	end
+end
+
+function utils.randomTriangular(min, max)
+	return utils.randomTriangularWith(min, max, (min + max) * 0.5)
+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
+	return max - math_sqrt((1 - u) * d * (max - mode))
+end
+
+function utils.testBit(value, bit)
+	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 % (2 * bit) >= bit then
+		return value
+	end
+	return value + bit
+end
+
+function utils.clearBit(value, bit)
+	if (value == nil) then return 0 end
+	if value % (2 * bit) >= bit then
+		return value - bit
+	end
+	return value
+end
+
+return utils

+ 0 - 0
spine-lua/vertexeffects/JitterEffect.lua → spine-lua/spine-lua/vertexeffects/JitterEffect.lua


+ 0 - 0
spine-lua/vertexeffects/SwirlEffect.lua → spine-lua/spine-lua/vertexeffects/SwirlEffect.lua