瀏覽代碼

Meshes, FFD and skinning for spine-lua.

NathanSweet 11 年之前
父節點
當前提交
81ae526a7b

+ 2 - 2
spine-corona/examples/spineboy.lua

@@ -12,8 +12,8 @@ function skeleton:createImage (attachment)
 	-- Customize where images are loaded.
 	-- Customize where images are loaded.
 	return display.newImage("examples/spineboy/images/" .. attachment.name .. ".png")
 	return display.newImage("examples/spineboy/images/" .. attachment.name .. ".png")
 end
 end
-skeleton.group.x = 150
-skeleton.group.y = 325
+skeleton.group.x = display.contentWidth * 0.5
+skeleton.group.y = display.contentHeight * 0.9
 skeleton.flipX = false
 skeleton.flipX = false
 skeleton.flipY = false
 skeleton.flipY = false
 skeleton.debug = true -- Omit or set to false to not draw debug lines on top of the images.
 skeleton.debug = true -- Omit or set to false to not draw debug lines on top of the images.

+ 3 - 1
spine-corona/spine-corona/spine.lua

@@ -29,7 +29,7 @@
 -------------------------------------------------------------------------------
 -------------------------------------------------------------------------------
 
 
 spine = {}
 spine = {}
- 
+
 spine.utils = require "spine-lua.utils"
 spine.utils = require "spine-lua.utils"
 spine.SkeletonJson = require "spine-lua.SkeletonJson"
 spine.SkeletonJson = require "spine-lua.SkeletonJson"
 spine.SkeletonData = require "spine-lua.SkeletonData"
 spine.SkeletonData = require "spine-lua.SkeletonData"
@@ -37,6 +37,8 @@ spine.BoneData = require "spine-lua.BoneData"
 spine.SlotData = require "spine-lua.SlotData"
 spine.SlotData = require "spine-lua.SlotData"
 spine.Skin = require "spine-lua.Skin"
 spine.Skin = require "spine-lua.Skin"
 spine.RegionAttachment = require "spine-lua.RegionAttachment"
 spine.RegionAttachment = require "spine-lua.RegionAttachment"
+spine.MeshAttachment = require "spine-lua.MeshAttachment"
+spine.SkinnedMeshAttachment = require "spine-lua.SkinnedMeshAttachment"
 spine.Skeleton = require "spine-lua.Skeleton"
 spine.Skeleton = require "spine-lua.Skeleton"
 spine.Bone = require "spine-lua.Bone"
 spine.Bone = require "spine-lua.Bone"
 spine.Slot = require "spine-lua.Slot"
 spine.Slot = require "spine-lua.Slot"

+ 22 - 24
spine-lua/Animation.lua

@@ -356,32 +356,30 @@ function Animation.ColorTimeline.new ()
 		local frames = self.frames
 		local frames = self.frames
 		if time < frames[0] then return end -- Time is before first frame.
 		if time < frames[0] then return end -- Time is before first frame.
 
 
-		local slot = skeleton.slots[self.slotIndex]
-
+		local r, g, b, a
 		if time >= frames[#frames - 4] then -- Time is after last frame.
 		if time >= frames[#frames - 4] then -- Time is after last frame.
-			local r = frames[#frames - 3]
-			local g = frames[#frames - 2]
-			local b = frames[#frames - 1]
-			local a = frames[#frames]
-			slot:setColor(r, g, b, a)
-			return
+			r = frames[#frames - 3]
+			g = frames[#frames - 2]
+			b = frames[#frames - 1]
+			a = frames[#frames]
+		else
+			-- Interpolate between the last frame and the current frame.
+			local frameIndex = binarySearch(frames, time, 5)
+			local lastFrameR = frames[frameIndex - 4]
+			local lastFrameG = frames[frameIndex - 3]
+			local lastFrameB = frames[frameIndex - 2]
+			local lastFrameA = frames[frameIndex - 1]
+			local frameTime = frames[frameIndex]
+			local percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime)
+			if percent < 0 then percent = 0 elseif percent > 255 then percent = 255 end
+			percent = self:getCurvePercent(frameIndex / 5 - 1, percent)
+
+			r = lastFrameR + (frames[frameIndex + FRAME_R] - lastFrameR) * percent
+			g = lastFrameG + (frames[frameIndex + FRAME_G] - lastFrameG) * percent
+			b = lastFrameB + (frames[frameIndex + FRAME_B] - lastFrameB) * percent
+			a = lastFrameA + (frames[frameIndex + FRAME_A] - lastFrameA) * percent
 		end
 		end
-
-		-- Interpolate between the last frame and the current frame.
-		local frameIndex = binarySearch(frames, time, 5)
-		local lastFrameR = frames[frameIndex - 4]
-		local lastFrameG = frames[frameIndex - 3]
-		local lastFrameB = frames[frameIndex - 2]
-		local lastFrameA = frames[frameIndex - 1]
-		local frameTime = frames[frameIndex]
-		local percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime)
-		if percent < 0 then percent = 0 elseif percent > 255 then percent = 255 end
-		percent = self:getCurvePercent(frameIndex / 5 - 1, percent)
-
-		local r = lastFrameR + (frames[frameIndex + FRAME_R] - lastFrameR) * percent
-		local g = lastFrameG + (frames[frameIndex + FRAME_G] - lastFrameG) * percent
-		local b = lastFrameB + (frames[frameIndex + FRAME_B] - lastFrameB) * percent
-		local a = lastFrameA + (frames[frameIndex + FRAME_A] - lastFrameA) * percent
+		local slot = skeleton.slots[self.slotIndex]
 		if alpha < 1 then
 		if alpha < 1 then
 			slot:setColor(slot.r + (r - slot.r) * alpha, slot.g + (g - slot.g) * alpha, slot.b + (b - slot.b) * alpha, slot.a + (a - slot.a) * alpha)
 			slot:setColor(slot.r + (r - slot.r) * alpha, slot.g + (g - slot.g) * alpha, slot.b + (b - slot.b) * alpha, slot.a + (a - slot.a) * alpha)
 		else
 		else

+ 4 - 4
spine-lua/AnimationState.lua

@@ -75,11 +75,11 @@ function AnimationState.new (data)
 		for i = 0, self.trackCount do
 		for i = 0, self.trackCount do
 			local current = self.tracks[i]
 			local current = self.tracks[i]
 			if current then
 			if current then
-				local trackDelta = delta * current.timeScale
-				current.time = current.time + trackDelta
+				current.time = current.time + delta * current.timeScale
 				if current.previous then
 				if current.previous then
-					current.previous.time = current.previous.time + trackDelta
-					current.mixTime = current.mixTime + trackDelta
+					local previousDelta = delta * current.previous.timeScale
+					current.previous.time = current.previous.time + previousDelta
+					current.mixTime = current.mixTime + previousDelta
 				end
 				end
 
 
 				local next = current.next
 				local next = current.next

+ 1 - 1
spine-lua/AnimationStateData.lua

@@ -34,8 +34,8 @@ function AnimationStateData.new (skeletonData)
 	if not skeletonData then error("skeletonData cannot be nil", 2) end
 	if not skeletonData then error("skeletonData cannot be nil", 2) end
 
 
 	local self = {
 	local self = {
-		animationToMixTime = {},
 		skeletonData = skeletonData,
 		skeletonData = skeletonData,
+		animationToMixTime = {},
 		defaultMix = 0
 		defaultMix = 0
 	}
 	}
 
 

+ 15 - 11
spine-lua/AttachmentLoader.lua

@@ -32,20 +32,24 @@ local AttachmentType = require "spine-lua.AttachmentType"
 local RegionAttachment = require "spine-lua.RegionAttachment"
 local RegionAttachment = require "spine-lua.RegionAttachment"
 local BoundingBoxAttachment = require "spine-lua.BoundingBoxAttachment"
 local BoundingBoxAttachment = require "spine-lua.BoundingBoxAttachment"
 
 
-local AttachmentLoader = {
-	failed = {}
-}
+local AttachmentLoader = {}
 function AttachmentLoader.new ()
 function AttachmentLoader.new ()
 	local self = {}
 	local self = {}
 
 
-	function self:newAttachment (type, name)
-		if type == AttachmentType.region then
-			return RegionAttachment.new(name)
-		end
-		if type == AttachmentType.boundingbox then
-			return BoundingBoxAttachment.new(name)
-		end
-		error("Unknown attachment type: " .. type .. " (" .. name .. ")")
+	function self:newRegionAttachment (skin, name, path)
+		return RegionAttachment.new(name)
+	end
+
+	function self:newMeshAttachment (skin, name, path)
+		return MeshAttachment.new(name)
+	end
+
+	function self:newSkinningMeshAttachment (skin, name, path)
+		return SkinningMeshAttachment.new(name)
+	end
+
+	function self:newBoundingBoxAttachment (skin, name)
+		return BoundingBoxAttachment.new(name)
 	end
 	end
 
 
 	return self
 	return self

+ 3 - 1
spine-lua/AttachmentType.lua

@@ -30,6 +30,8 @@
 
 
 local AttachmentType = {
 local AttachmentType = {
 	region = 0,
 	region = 0,
-	boundingbox = 1
+	boundingbox = 1,
+	mesh = 2,
+	skinnedmesh = 3
 }
 }
 return AttachmentType
 return AttachmentType

+ 93 - 0
spine-lua/MeshAttachment.lua

@@ -0,0 +1,93 @@
+-------------------------------------------------------------------------------
+-- Spine Runtimes Software License
+-- Version 2.1
+-- 
+-- Copyright (c) 2013, Esoteric Software
+-- All rights reserved.
+-- 
+-- You are granted a perpetual, non-exclusive, non-sublicensable and
+-- non-transferable license to install, execute and perform the Spine Runtimes
+-- Software (the "Software") solely for internal use. Without the written
+-- permission of Esoteric Software (typically granted by licensing Spine), you
+-- may not (a) modify, translate, adapt or otherwise create derivative works,
+-- improvements of the Software or develop new applications using the Software
+-- or (b) remove, delete, alter or obscure any trademarks or any copyright,
+-- trademark, patent or other intellectual property or proprietary rights
+-- notices on or in the Software, including any copy thereof. Redistributions
+-- in binary or source form must include this license and terms.
+-- 
+-- THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+-- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+-- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+-- OR BUSINESS INTERRUPTION) 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 THIS SOFTWARE, 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 = 0, regionV2 = 0, 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 = 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.length
+		if #slot.attachmentVertices == verticesCount then vertices = slot.attachmentVertices 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

+ 58 - 2
spine-lua/RegionAttachment.lua

@@ -36,9 +36,65 @@ function RegionAttachment.new (name)
 	
 	
 	local self = {
 	local self = {
 		name = name,
 		name = name,
-		type = AttachmentType.region
+		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 = null,
+		rendererObject = null,
+		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
+		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
 	return self
 end
 end
 return RegionAttachment
 return RegionAttachment

+ 24 - 4
spine-lua/Skeleton.lua

@@ -43,7 +43,10 @@ function Skeleton.new (skeletonData)
 		slotsByName = {},
 		slotsByName = {},
 		drawOrder = {},
 		drawOrder = {},
 		r = 1, g = 1, b = 1, a = 1,
 		r = 1, g = 1, b = 1, a = 1,
-		x = 0, y = 0
+		x = 0, y = 0,
+		skin = nil,
+		flipX = false, flipY = false,
+		time = 0
 	}
 	}
 
 
 	function self:updateWorldTransform ()
 	function self:updateWorldTransform ()
@@ -65,6 +68,7 @@ function Skeleton.new (skeletonData)
 
 
 	function self:setSlotsToSetupPose ()
 	function self:setSlotsToSetupPose ()
 		for i,slot in ipairs(self.slots) do
 		for i,slot in ipairs(self.slots) do
+			self.drawOrder[i] = slot
 			slot:setToSetupPose()
 			slot:setToSetupPose()
 		end
 		end
 	end
 	end
@@ -90,7 +94,7 @@ function Skeleton.new (skeletonData)
 		local newSkin
 		local newSkin
 		if skinName then
 		if skinName then
 			newSkin = self.data:findSkin(skinName)
 			newSkin = self.data:findSkin(skinName)
-			if not newSkin then error("Skin not found: " .. skinName, 2) end
+			if not newSkin then error("Skin not found = " .. skinName, 2) end
 			if self.skin then
 			if self.skin then
 				-- Attach all attachments from the new skin if the corresponding attachment from the old skin is currently attached.
 				-- Attach all attachments from the new skin if the corresponding attachment from the old skin is currently attached.
 				for k,v in pairs(self.skin.attachments) do
 				for k,v in pairs(self.skin.attachments) do
@@ -103,6 +107,15 @@ function Skeleton.new (skeletonData)
 						if newAttachment then slot:setAttachment(newAttachment) end
 						if newAttachment then slot:setAttachment(newAttachment) end
 					end
 					end
 				end
 				end
+			else
+				-- No previous skin, attach setup pose attachments.
+				for i,slot in ipairs(self.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
 		end
 		end
 		self.skin = newSkin
 		self.skin = newSkin
@@ -112,7 +125,7 @@ function Skeleton.new (skeletonData)
 		if not slotName then error("slotName cannot be nil.", 2) end
 		if not slotName then error("slotName cannot be nil.", 2) end
 		if not attachmentName then error("attachmentName cannot be nil.", 2) end
 		if not attachmentName then error("attachmentName cannot be nil.", 2) end
 		local slotIndex = skeletonData.slotNameIndices[slotName]
 		local slotIndex = skeletonData.slotNameIndices[slotName]
-		if slotIndex == -1 then error("Slot not found: " .. slotName, 2) end
+		if slotIndex == -1 then error("Slot not found = " .. slotName, 2) end
 		if self.skin then
 		if self.skin then
 			local attachment = self.skin:getAttachment(slotIndex, attachmentName)
 			local attachment = self.skin:getAttachment(slotIndex, attachmentName)
 			if attachment then return attachment end
 			if attachment then return attachment end
@@ -135,13 +148,20 @@ function Skeleton.new (skeletonData)
 				return
 				return
 			end
 			end
 		end
 		end
-		error("Slot not found: " .. slotName, 2)
+		error("Slot not found = " .. slotName, 2)
 	end
 	end
 
 
 	function self:update (delta)
 	function self:update (delta)
 		self.time = self.time + delta
 		self.time = self.time + delta
 	end
 	end
 
 
+	function self:setColor (r, g, b, a)
+		self.r = r
+		self.g = g
+		self.b = b
+		self.a = a
+	end
+
 	for i,boneData in ipairs(skeletonData.bones) do
 	for i,boneData in ipairs(skeletonData.bones) do
 		local parent
 		local parent
 		if boneData.parent then parent = self.bones[spine.utils.indexOf(skeletonData.bones, boneData.parent)] end
 		if boneData.parent then parent = self.bones[spine.utils.indexOf(skeletonData.bones, boneData.parent)] end

+ 2 - 1
spine-lua/SkeletonData.lua

@@ -36,7 +36,8 @@ function SkeletonData.new ()
 		slotNameIndices = {},
 		slotNameIndices = {},
 		skins = {},
 		skins = {},
 		events = {},
 		events = {},
-		animations = {}
+		animations = {},
+		defaultSkin = nil
 	}
 	}
 
 
 	function self:findBone (boneName)
 	function self:findBone (boneName)

+ 248 - 78
spine-lua/SkeletonJson.lua

@@ -75,8 +75,16 @@ function SkeletonJson.new (attachmentLoader)
 			boneData.x = (boneMap["x"] or 0) * self.scale
 			boneData.x = (boneMap["x"] or 0) * self.scale
 			boneData.y = (boneMap["y"] or 0) * self.scale
 			boneData.y = (boneMap["y"] or 0) * self.scale
 			boneData.rotation = (boneMap["rotation"] or 0)
 			boneData.rotation = (boneMap["rotation"] or 0)
-			boneData.scaleX = (boneMap["scaleX"] or 1)
-			boneData.scaleY = (boneMap["scaleY"] or 1)
+			if boneMap["scaleX"] ~= nil then
+				boneData.scaleX = boneMap["scaleX"]
+			else
+				boneData.scaleX = 1
+			end
+			if boneMap["scaleY"] ~= nil then
+				boneData.scaleY = boneMap["scaleY"]
+			else
+				boneData.scaleY = 1
+			end
 			if boneMap["inheritScale"] == false then
 			if boneMap["inheritScale"] == false then
 				boneData.inheritScale = false
 				boneData.inheritScale = false
 			else
 			else
@@ -124,7 +132,7 @@ function SkeletonJson.new (attachmentLoader)
 				for slotName,slotMap in pairs(skinMap) do
 				for slotName,slotMap in pairs(skinMap) do
 					local slotIndex = skeletonData.slotNameIndices[slotName]
 					local slotIndex = skeletonData.slotNameIndices[slotName]
 					for attachmentName,attachmentMap in pairs(slotMap) do
 					for attachmentName,attachmentMap in pairs(slotMap) do
-						local attachment = readAttachment(attachmentName, attachmentMap, self.scale)
+						local attachment = readAttachment(attachmentName, attachmentMap)
 						if attachment then
 						if attachment then
 							skin:addAttachment(slotIndex, attachmentName, attachment)
 							skin:addAttachment(slotIndex, attachmentName, attachment)
 						end
 						end
@@ -159,35 +167,171 @@ function SkeletonJson.new (attachmentLoader)
 		return skeletonData
 		return skeletonData
 	end
 	end
 
 
-	readAttachment = function (name, map, scale)
+	readAttachment = function (name, map)
 		name = map["name"] or name
 		name = map["name"] or name
-		local attachment
+
 		local type = AttachmentType[map["type"] or "region"]
 		local type = AttachmentType[map["type"] or "region"]
-		attachment = attachmentLoader:newAttachment(type, name)
-		if not attachment then return nil end
+		local path = map["path"] or name
 
 
+		local scale = self.scale
 		if type == AttachmentType.region then
 		if type == AttachmentType.region then
-			attachment.x = (map["x"] or 0) * scale
-			attachment.y = (map["y"] or 0) * scale
-			attachment.scaleX = (map["scaleX"] or 1)
-			attachment.scaleY = (map["scaleY"] or 1)
-			attachment.rotation = (map["rotation"] or 0)
-			attachment.width = map["width"] * scale
-			attachment.height = map["height"] * scale
+			local region = attachmentLoader:newRegionAttachment(type, name, path)
+			if not region then return nil end
+			region.x = (map["x"] or 0) * scale
+			region.y = (map["y"] or 0) * scale
+			if map["scaleX"] ~= nil then
+				region.scaleX = map["scaleX"]
+			else
+				region.scaleX = 1
+			end
+			if map["scaleY"] ~= nil then
+				region.scaleY = map["scaleY"]
+			else
+				region.scaleY = 1
+			end
+			region.rotation = (map["rotation"] or 0)
+			region.width = map["width"] * scale
+			region.height = map["height"] * scale
+			
+			local color = map["color"]
+			if color then
+				region.r = tonumber(color:sub(1, 2), 16) / 255
+				region.g = tonumber(color:sub(3, 4), 16) / 255
+				region.b = tonumber(color:sub(5, 6), 16) / 255
+				region.a = tonumber(color:sub(7, 8), 16) / 255
+			end
+
+			region:updateOffset()
+			return region
+
+		elseif type == AttachmentType.mesh then
+			local mesh = attachmentLoader:newMeshAttachment(skin, name, path)
+			if not mesh then return null end
+			mesh.path = path 
+			mesh.vertices = getFloatArray(map, "vertices", scale)
+			mesh.triangles = getIntArray(map, "triangles")
+			mesh.regionUVs = getFloatArray(map, "uvs", 1)
+			mesh:updateUVs()
+
+			local color = map["color"]
+			if color then
+				mesh.r = tonumber(color:sub(1, 2), 16) / 255
+				mesh.g = tonumber(color:sub(3, 4), 16) / 255
+				mesh.b = tonumber(color:sub(5, 6), 16) / 255
+				mesh.a = tonumber(color:sub(7, 8), 16) / 255
+			end
+
+			mesh.hullLength = (map["hull"] or 0) * 2
+			if map["edges"] then mesh.edges = getIntArray(map, "edges") end
+			mesh.width = (map["width"] or 0) * scale
+			mesh.height = (map["height"] or 0) * scale
+			return mesh
+
+		elseif type == AttachmentType.skinnedmesh then
+			local mesh = self.attachmentLoader.newSkinnedMeshAttachment(skin, name, path)
+			if not mesh then return null end
+			mesh.path = path
+
+			local uvs = getFloatArray(map, "uvs", 1)
+			vertices = getFloatArray(map, "vertices", 1)
+			local weights = {}
+			local bones = {}
+			for i = 1, vertices do
+				local boneCount = vertices[i]
+				i = i + 1
+				table.insert(bones, boneCount)
+				for ii = 1, i + boneCount * 4 do
+					table.insert(bones, vertices[i])
+					table.insert(weights, vertices[i + 1] * scale)
+					table.insert(weights, vertices[i + 2] * scale)
+					table.insert(weights, vertices[i + 3])
+					i = i + 4
+				end
+			end
+			mesh.bones = bones
+			mesh.weights = weights
+			mesh.triangles = getIntArray(map, "triangles")
+			mesh.regionUVs = uvs
+			mesh:updateUVs()
+
+			local color = map["color"]
+			if color then
+				mesh.r = tonumber(color:sub(1, 2), 16) / 255
+				mesh.g = tonumber(color:sub(3, 4), 16) / 255
+				mesh.b = tonumber(color:sub(5, 6), 16) / 255
+				mesh.a = tonumber(color:sub(7, 8), 16) / 255
+			end
+
+			mesh.hullLength = (map["hull"] or 0) * 2
+			if map["edges"] then mesh.edges = getIntArray(map, "edges") end
+			mesh.width = (map["width"] or 0) * scale
+			mesh.height = (map["height"] or 0) * scale
+			return mesh
+
 		elseif type == AttachmentType.boundingbox then
 		elseif type == AttachmentType.boundingbox then
+			local box = attachmentLoader:newBoundingBoxAttachment(type, name)
+			if not box then return nil end
 			local vertices = map["vertices"]
 			local vertices = map["vertices"]
 			for i,point in ipairs(vertices) do
 			for i,point in ipairs(vertices) do
-				table.insert(attachment.vertices, vertices[i] * scale)
+				table.insert(box.vertices, vertices[i] * scale)
 			end
 			end
+			return box
 		end
 		end
 
 
-		return attachment
+		error("Unknown attachment type: " .. type .. " (" .. name .. ")")
 	end
 	end
 
 
 	readAnimation = function (name, map, skeletonData)
 	readAnimation = function (name, map, skeletonData)
 		local timelines = {}
 		local timelines = {}
 		local duration = 0
 		local duration = 0
 
 
+		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()
+						timeline.slotIndex = slotIndex
+
+						local frameIndex = 0
+						for i,valueMap in ipairs(values) do
+							local color = valueMap["color"]
+							timeline:setFrame(
+								frameIndex, valueMap["time"], 
+								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(timeline, frameIndex, valueMap)
+							frameIndex = frameIndex + 1
+						end
+						table.insert(timelines, timeline)
+						duration = math.max(duration, timeline:getDuration())
+
+					elseif timelineName == "attachment" then
+						local timeline = Animation.AttachmentTimeline.new()
+						timeline.slotName = slotName
+
+						local frameIndex = 0
+						for i,valueMap in ipairs(values) do
+							local attachmentName = valueMap["name"]
+							if not attachmentName then attachmentName = nil end
+							timeline:setFrame(frameIndex, valueMap["time"], attachmentName)
+							frameIndex = frameIndex + 1
+						end
+						table.insert(timelines, timeline)
+						duration = math.max(duration, timeline:getDuration())
+
+					else
+						error("Invalid frame type for a slot: " .. timelineName .. " (" .. slotName .. ")")
+					end
+				end
+			end
+		end
+
 		local bonesMap = map["bones"]
 		local bonesMap = map["bones"]
 		if bonesMap then
 		if bonesMap then
 			for boneName,timelineMap in pairs(bonesMap) do
 			for boneName,timelineMap in pairs(bonesMap) do
@@ -199,12 +343,11 @@ function SkeletonJson.new (attachmentLoader)
 						local timeline = Animation.RotateTimeline.new()
 						local timeline = Animation.RotateTimeline.new()
 						timeline.boneIndex = boneIndex
 						timeline.boneIndex = boneIndex
 
 
-						local keyframeIndex = 0
+						local frameIndex = 0
 						for i,valueMap in ipairs(values) do
 						for i,valueMap in ipairs(values) do
-							local time = valueMap["time"]
-							timeline:setFrame(keyframeIndex, time, valueMap["angle"])
-							readCurve(timeline, keyframeIndex, valueMap)
-							keyframeIndex = keyframeIndex + 1
+							timeline:setFrame(frameIndex, valueMap["time"], valueMap["angle"])
+							readCurve(timeline, frameIndex, valueMap)
+							frameIndex = frameIndex + 1
 						end
 						end
 						table.insert(timelines, timeline)
 						table.insert(timelines, timeline)
 						duration = math.max(duration, timeline:getDuration())
 						duration = math.max(duration, timeline:getDuration())
@@ -220,14 +363,13 @@ function SkeletonJson.new (attachmentLoader)
 						end
 						end
 						timeline.boneIndex = boneIndex
 						timeline.boneIndex = boneIndex
 
 
-						local keyframeIndex = 0
+						local frameIndex = 0
 						for i,valueMap in ipairs(values) do
 						for i,valueMap in ipairs(values) do
-							local time = valueMap["time"]
 							local x = (valueMap["x"] or 0) * timelineScale
 							local x = (valueMap["x"] or 0) * timelineScale
 							local y = (valueMap["y"] or 0) * timelineScale
 							local y = (valueMap["y"] or 0) * timelineScale
-							timeline:setFrame(keyframeIndex, time, x, y)
-							readCurve(timeline, keyframeIndex, valueMap)
-							keyframeIndex = keyframeIndex + 1
+							timeline:setFrame(frameIndex, valueMap["time"], x, y)
+							readCurve(timeline, frameIndex, valueMap)
+							frameIndex = frameIndex + 1
 						end
 						end
 						table.insert(timelines, timeline)
 						table.insert(timelines, timeline)
 						duration = math.max(duration, timeline:getDuration())
 						duration = math.max(duration, timeline:getDuration())
@@ -239,73 +381,71 @@ function SkeletonJson.new (attachmentLoader)
 			end
 			end
 		end
 		end
 
 
-		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()
+		local ffd = map["ffd"]
+		if ffd then
+			for skinName,slotMap in pairs(ffd) do
+				local skin = skeletonData.findSkin(skinName)
+				for slotName,meshMap in pairs(slotMap) do
+					local slotIndex = skeletonData.findSlotIndex(slotName)
+					for meshName,values in pairs(meshMap) do
+						local timeline = Animation.FfdTimeline.new()
+						local attachment = skin:getAttachment(slotIndex, meshName)
+						if not attachment then error("FFD attachment not found: " .. meshName) end
 						timeline.slotIndex = slotIndex
 						timeline.slotIndex = slotIndex
+						timeline.attachment = attachment
 
 
-						local keyframeIndex = 0
-						for i,valueMap in ipairs(values) do
-							local time = valueMap["time"]
-							local color = valueMap["color"]
-							timeline:setFrame(
-								keyframeIndex, time, 
-								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(timeline, keyframeIndex, valueMap)
-							keyframeIndex = keyframeIndex + 1
+						local isMesh = attachment.type == AttachmentType.mesh
+						local vertexCount
+						if isMesh then
+							vertexCount = attachment.vertices.length
+						else
+							vertexCount = attachment.weights.length / 3 * 2
 						end
 						end
-						table.insert(timelines, timeline)
-						duration = math.max(duration, timeline:getDuration())
-
-					elseif timelineName == "attachment" then
-						local timeline = Animation.AttachmentTimeline.new()
-						timeline.slotName = slotName
 
 
 						local frameIndex = 0
 						local frameIndex = 0
 						for i,valueMap in ipairs(values) do
 						for i,valueMap in ipairs(values) do
-							local time = valueMap["time"]
-							local attachmentName = valueMap["name"]
-							if not attachmentName then attachmentName = nil end
-							timeline:setFrame(frameIndex, time, attachmentName)
+							local vertices
+							if not valueMap["vertices"] then
+								if isMesh then
+									vertices = attachment.vertices
+								else
+									vertices = {}
+									vertices.length = vertexCount
+								end
+							else
+								local verticesValue = valueMap["vertices"]
+								local vertices = {}
+								local start = valueMap["offset"] or 0
+								if scale == 1 then
+									for ii = 1, #verticesValue do
+										vertices[ii + start] = verticesValue[ii]
+									end
+								else
+									for ii = 1, #verticesValue do
+										vertices[ii + start] = verticesValue[ii] * scale
+									end
+								end
+								if isMesh then
+									local meshVertices = attachment.vertices
+									for ii = 1, vertexCount do
+										vertices[ii] = vertices[ii] + meshVertices[ii]
+									end
+								elseif #verticesValue < vertexCount then
+									vertices[vertexCount] = 0
+								end
+							end
+
+							timeline:setFrame(frameIndex, valueMap["time"], vertices)
+							readCurve(timeline, frameIndex, valueMap)
 							frameIndex = frameIndex + 1
 							frameIndex = frameIndex + 1
 						end
 						end
 						table.insert(timelines, timeline)
 						table.insert(timelines, timeline)
 						duration = math.max(duration, timeline:getDuration())
 						duration = math.max(duration, timeline:getDuration())
-
-					else
-						error("Invalid frame type for a slot: " .. timelineName .. " (" .. slotName .. ")")
 					end
 					end
 				end
 				end
 			end
 			end
 		end
 		end
 
 
-		local events = map["events"]
-		if events then
-			local timeline = Animation.EventTimeline.new(#events)
-			local frameIndex = 0
-			for i,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(eventData)
-				event.intValue = eventMap["int"] or eventData.intValue
-				event.floatValue = eventMap["float"] or eventData.floatValue
-				event.stringValue = eventMap["string"] or eventData.stringValue
-				timeline:setFrame(frameIndex, eventMap["time"], event)
-				frameIndex = frameIndex + 1
-			end
-			table.insert(timelines, timeline)
-			duration = math.max(duration, timeline:getDuration())
-		end
-
 		local drawOrderValues = map["draworder"]
 		local drawOrderValues = map["draworder"]
 		if drawOrderValues then
 		if drawOrderValues then
 			local timeline = Animation.DrawOrderTimeline.new(#drawOrderValues)
 			local timeline = Animation.DrawOrderTimeline.new(#drawOrderValues)
@@ -353,6 +493,36 @@ function SkeletonJson.new (attachmentLoader)
 			duration = math.max(duration, timeline:getDuration())
 			duration = math.max(duration, timeline:getDuration())
 		end
 		end
 
 
+		local events = map["events"]
+		if events then
+			local timeline = Animation.EventTimeline.new(#events)
+			local frameIndex = 0
+			for i,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(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
+				timeline:setFrame(frameIndex, eventMap["time"], event)
+				frameIndex = frameIndex + 1
+			end
+			table.insert(timelines, timeline)
+			duration = math.max(duration, timeline:getDuration())
+		end
+
 		table.insert(skeletonData.animations, Animation.new(name, timelines, duration))
 		table.insert(skeletonData.animations, Animation.new(name, timelines, duration))
 	end
 	end
 
 

+ 131 - 0
spine-lua/SkinnedMeshAttachment.lua

@@ -0,0 +1,131 @@
+-------------------------------------------------------------------------------
+-- Spine Runtimes Software License
+-- Version 2.1
+-- 
+-- Copyright (c) 2013, Esoteric Software
+-- All rights reserved.
+-- 
+-- You are granted a perpetual, non-exclusive, non-sublicensable and
+-- non-transferable license to install, execute and perform the Spine Runtimes
+-- Software (the "Software") solely for internal use. Without the written
+-- permission of Esoteric Software (typically granted by licensing Spine), you
+-- may not (a) modify, translate, adapt or otherwise create derivative works,
+-- improvements of the Software or develop new applications using the Software
+-- or (b) remove, delete, alter or obscure any trademarks or any copyright,
+-- trademark, patent or other intellectual property or proprietary rights
+-- notices on or in the Software, including any copy thereof. Redistributions
+-- in binary or source form must include this license and terms.
+-- 
+-- THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+-- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+-- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+-- OR BUSINESS INTERRUPTION) 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 THIS SOFTWARE, EVEN IF
+-- ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local AttachmentType = require "spine-lua.AttachmentType"
+
+local SkinnedMeshAttachment = {}
+function SkinnedMeshAttachment.new (name)
+	if not name then error("name cannot be nil", 2) end
+	
+	local self = {
+		name = name,
+		type = AttachmentType.mesh,
+		bones = nil,
+		weights = 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 = 0, regionV2 = 0, 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 skeletonBones = slot.skeleton.bones
+		local weights = self.weights
+		local bones = self.bones
+
+		local w, v, b, f = 0, 0, 0, 0
+		local	n = bones.length
+		local wx, wy, bone, vx, vy, weight
+		if #slot.attachmentVertices == 0 then
+			while v < n do
+				wx = 0
+				wy = 0
+				local nn = bones[v] + v
+				v = v + 1
+				while v <= nn do
+					bone = skeletonBones[bones[v]]
+					vx = weights[b]
+					vy = weights[b + 1]
+					weight = weights[b + 2]
+					wx = wx + (vx * bone.m00 + vy * bone.m01 + bone.worldX) * weight
+					wy = wy + (vx * bone.m10 + vy * bone.m11 + bone.worldY) * weight
+					v = v + 1
+					b = b + 3
+				end
+				worldVertices[w] = wx + x
+				worldVertices[w + 1] = wy + y
+				w = w + 2
+			end
+		else
+			local ffd = slot.attachmentVertices
+			while v < n do
+				wx = 0
+				wy = 0
+				local nn = bones[v] + v
+				v = v + 1
+				while v <= nn do
+					bone = skeletonBones[bones[v]]
+					vx = weights[b] + ffd[f]
+					vy = weights[b + 1] + ffd[f + 1]
+					weight = weights[b + 2]
+					wx = wx + (vx * bone.m00 + vy * bone.m01 + bone.worldX) * weight
+					wy = wy + (vx * bone.m10 + vy * bone.m11 + bone.worldY) * weight
+					v = v + 1
+					b = b + 3
+					f = f + 2
+				end
+				worldVertices[w] = wx + x
+				worldVertices[w + 1] = wy + y
+				w = w + 2
+			end
+		end
+	end
+
+	return self
+end
+return MeshAttachment

+ 4 - 1
spine-lua/Slot.lua

@@ -38,7 +38,10 @@ function Slot.new (slotData, skeleton, bone)
 		data = slotData,
 		data = slotData,
 		skeleton = skeleton,
 		skeleton = skeleton,
 		bone = bone,
 		bone = bone,
-		r = 1, g = 1, b = 1, a = 1
+		r = 1, g = 1, b = 1, a = 1,
+		attachment = nil,
+		attachmentTime = 0,
+		attachmentVertices = nil
 	}
 	}
 
 
 	function self:setColor (r, g, b, a)
 	function self:setColor (r, g, b, a)

+ 4 - 2
spine-lua/SlotData.lua

@@ -36,7 +36,9 @@ function SlotData.new (name, boneData)
 	local self = {
 	local self = {
 		name = name,
 		name = name,
 		boneData = boneData,
 		boneData = boneData,
-		r = 1, g = 1, b = 1, a = 1
+		r = 1, g = 1, b = 1, a = 1,
+		attachmentName = nil,
+		additiveBlending = false
 	}
 	}
 
 
 	function self:setColor (r, g, b, a)
 	function self:setColor (r, g, b, a)
@@ -46,6 +48,6 @@ function SlotData.new (name, boneData)
 		self.a = a
 		self.a = a
 	end
 	end
 
 
-	return self;
+	return self
 end
 end
 return SlotData
 return SlotData