diff --git a/spine-corona/examples/spineboy.lua b/spine-corona/examples/spineboy.lua index 9edba5571..2df365143 100644 --- a/spine-corona/examples/spineboy.lua +++ b/spine-corona/examples/spineboy.lua @@ -12,8 +12,8 @@ function skeleton:createImage (attachment) -- Customize where images are loaded. return display.newImage("examples/spineboy/images/" .. attachment.name .. ".png") 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.flipY = false skeleton.debug = true -- Omit or set to false to not draw debug lines on top of the images. diff --git a/spine-corona/spine-corona/spine.lua b/spine-corona/spine-corona/spine.lua index 91780fb15..da29acaa5 100644 --- a/spine-corona/spine-corona/spine.lua +++ b/spine-corona/spine-corona/spine.lua @@ -29,7 +29,7 @@ ------------------------------------------------------------------------------- spine = {} - + spine.utils = require "spine-lua.utils" spine.SkeletonJson = require "spine-lua.SkeletonJson" spine.SkeletonData = require "spine-lua.SkeletonData" @@ -37,6 +37,8 @@ spine.BoneData = require "spine-lua.BoneData" spine.SlotData = require "spine-lua.SlotData" spine.Skin = require "spine-lua.Skin" 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.Bone = require "spine-lua.Bone" spine.Slot = require "spine-lua.Slot" diff --git a/spine-lua/Animation.lua b/spine-lua/Animation.lua index f35df18f1..79299ae37 100644 --- a/spine-lua/Animation.lua +++ b/spine-lua/Animation.lua @@ -356,32 +356,30 @@ function Animation.ColorTimeline.new () local frames = self.frames 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. - 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 - - -- 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 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 diff --git a/spine-lua/AnimationState.lua b/spine-lua/AnimationState.lua index 1243ea730..f067c5512 100644 --- a/spine-lua/AnimationState.lua +++ b/spine-lua/AnimationState.lua @@ -75,11 +75,11 @@ function AnimationState.new (data) for i = 0, self.trackCount do local current = self.tracks[i] if current then - local trackDelta = delta * current.timeScale - current.time = current.time + trackDelta + current.time = current.time + delta * current.timeScale 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 local next = current.next diff --git a/spine-lua/AnimationStateData.lua b/spine-lua/AnimationStateData.lua index 9d34e5deb..bff03c1dc 100644 --- a/spine-lua/AnimationStateData.lua +++ b/spine-lua/AnimationStateData.lua @@ -34,8 +34,8 @@ function AnimationStateData.new (skeletonData) if not skeletonData then error("skeletonData cannot be nil", 2) end local self = { - animationToMixTime = {}, skeletonData = skeletonData, + animationToMixTime = {}, defaultMix = 0 } diff --git a/spine-lua/AttachmentLoader.lua b/spine-lua/AttachmentLoader.lua index aa03e34cc..33b85ed17 100644 --- a/spine-lua/AttachmentLoader.lua +++ b/spine-lua/AttachmentLoader.lua @@ -32,20 +32,24 @@ local AttachmentType = require "spine-lua.AttachmentType" local RegionAttachment = require "spine-lua.RegionAttachment" local BoundingBoxAttachment = require "spine-lua.BoundingBoxAttachment" -local AttachmentLoader = { - failed = {} -} +local AttachmentLoader = {} function AttachmentLoader.new () 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 return self diff --git a/spine-lua/AttachmentType.lua b/spine-lua/AttachmentType.lua index 379ed3e0f..0defa2c52 100644 --- a/spine-lua/AttachmentType.lua +++ b/spine-lua/AttachmentType.lua @@ -30,6 +30,8 @@ local AttachmentType = { region = 0, - boundingbox = 1 + boundingbox = 1, + mesh = 2, + skinnedmesh = 3 } return AttachmentType diff --git a/spine-lua/MeshAttachment.lua b/spine-lua/MeshAttachment.lua new file mode 100644 index 000000000..7ddc8ad5e --- /dev/null +++ b/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 diff --git a/spine-lua/RegionAttachment.lua b/spine-lua/RegionAttachment.lua index dd7672740..631f33555 100644 --- a/spine-lua/RegionAttachment.lua +++ b/spine-lua/RegionAttachment.lua @@ -36,9 +36,65 @@ function RegionAttachment.new (name) local self = { 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 end return RegionAttachment diff --git a/spine-lua/Skeleton.lua b/spine-lua/Skeleton.lua index 2530e6f2d..1e30a5f86 100644 --- a/spine-lua/Skeleton.lua +++ b/spine-lua/Skeleton.lua @@ -43,7 +43,10 @@ function Skeleton.new (skeletonData) slotsByName = {}, drawOrder = {}, 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 () @@ -65,6 +68,7 @@ function Skeleton.new (skeletonData) function self:setSlotsToSetupPose () for i,slot in ipairs(self.slots) do + self.drawOrder[i] = slot slot:setToSetupPose() end end @@ -90,7 +94,7 @@ function Skeleton.new (skeletonData) local newSkin if skinName then 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 -- 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 @@ -103,6 +107,15 @@ function Skeleton.new (skeletonData) if newAttachment then slot:setAttachment(newAttachment) 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 self.skin = newSkin @@ -112,7 +125,7 @@ function Skeleton.new (skeletonData) if not slotName then error("slotName cannot be nil.", 2) end if not attachmentName then error("attachmentName cannot be nil.", 2) end 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 local attachment = self.skin:getAttachment(slotIndex, attachmentName) if attachment then return attachment end @@ -135,13 +148,20 @@ function Skeleton.new (skeletonData) return end end - error("Slot not found: " .. slotName, 2) + error("Slot not found = " .. slotName, 2) end function self:update (delta) self.time = self.time + delta 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 local parent if boneData.parent then parent = self.bones[spine.utils.indexOf(skeletonData.bones, boneData.parent)] end diff --git a/spine-lua/SkeletonData.lua b/spine-lua/SkeletonData.lua index ce17892fe..ddff41548 100644 --- a/spine-lua/SkeletonData.lua +++ b/spine-lua/SkeletonData.lua @@ -36,7 +36,8 @@ function SkeletonData.new () slotNameIndices = {}, skins = {}, events = {}, - animations = {} + animations = {}, + defaultSkin = nil } function self:findBone (boneName) diff --git a/spine-lua/SkeletonJson.lua b/spine-lua/SkeletonJson.lua index abd346b71..7d1dba817 100755 --- a/spine-lua/SkeletonJson.lua +++ b/spine-lua/SkeletonJson.lua @@ -75,8 +75,16 @@ function SkeletonJson.new (attachmentLoader) boneData.x = (boneMap["x"] or 0) * self.scale boneData.y = (boneMap["y"] or 0) * self.scale 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 boneData.inheritScale = false else @@ -124,7 +132,7 @@ function SkeletonJson.new (attachmentLoader) for slotName,slotMap in pairs(skinMap) do local slotIndex = skeletonData.slotNameIndices[slotName] for attachmentName,attachmentMap in pairs(slotMap) do - local attachment = readAttachment(attachmentName, attachmentMap, self.scale) + local attachment = readAttachment(attachmentName, attachmentMap) if attachment then skin:addAttachment(slotIndex, attachmentName, attachment) end @@ -159,35 +167,171 @@ function SkeletonJson.new (attachmentLoader) return skeletonData end - readAttachment = function (name, map, scale) + readAttachment = function (name, map) name = map["name"] or name - local attachment - local type = AttachmentType[map["type"] or "region"] - attachment = attachmentLoader:newAttachment(type, name) - if not attachment then return nil end + local type = AttachmentType[map["type"] or "region"] + local path = map["path"] or name + + local scale = self.scale 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 + local box = attachmentLoader:newBoundingBoxAttachment(type, name) + if not box then return nil end local vertices = map["vertices"] for i,point in ipairs(vertices) do - table.insert(attachment.vertices, vertices[i] * scale) + table.insert(box.vertices, vertices[i] * scale) end + return box end - return attachment + error("Unknown attachment type: " .. type .. " (" .. name .. ")") end readAnimation = function (name, map, skeletonData) local timelines = {} 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"] if bonesMap then for boneName,timelineMap in pairs(bonesMap) do @@ -199,12 +343,11 @@ function SkeletonJson.new (attachmentLoader) local timeline = Animation.RotateTimeline.new() timeline.boneIndex = boneIndex - local keyframeIndex = 0 + local frameIndex = 0 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 table.insert(timelines, timeline) duration = math.max(duration, timeline:getDuration()) @@ -220,14 +363,13 @@ function SkeletonJson.new (attachmentLoader) end timeline.boneIndex = boneIndex - local keyframeIndex = 0 + local frameIndex = 0 for i,valueMap in ipairs(values) do - local time = valueMap["time"] local x = (valueMap["x"] 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 table.insert(timelines, timeline) duration = math.max(duration, timeline:getDuration()) @@ -239,73 +381,71 @@ function SkeletonJson.new (attachmentLoader) 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.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 - 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 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 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 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"] if drawOrderValues then local timeline = Animation.DrawOrderTimeline.new(#drawOrderValues) @@ -353,6 +493,36 @@ function SkeletonJson.new (attachmentLoader) duration = math.max(duration, timeline:getDuration()) 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)) end diff --git a/spine-lua/SkinnedMeshAttachment.lua b/spine-lua/SkinnedMeshAttachment.lua new file mode 100644 index 000000000..1f4163939 --- /dev/null +++ b/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 diff --git a/spine-lua/Slot.lua b/spine-lua/Slot.lua index f7dcb74ba..c8c993760 100644 --- a/spine-lua/Slot.lua +++ b/spine-lua/Slot.lua @@ -38,7 +38,10 @@ function Slot.new (slotData, skeleton, bone) data = slotData, skeleton = skeleton, 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) diff --git a/spine-lua/SlotData.lua b/spine-lua/SlotData.lua index 1d4f896dd..70db649de 100644 --- a/spine-lua/SlotData.lua +++ b/spine-lua/SlotData.lua @@ -36,7 +36,9 @@ function SlotData.new (name, boneData) local self = { name = name, 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) @@ -46,6 +48,6 @@ function SlotData.new (name, boneData) self.a = a end - return self; + return self end return SlotData