Meshes, FFD and skinning for spine-lua.

This commit is contained in:
NathanSweet 2014-05-23 12:37:55 +02:00
parent c1a9b502bd
commit 81ae526a7b
15 changed files with 615 additions and 133 deletions

View File

@ -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.

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

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

View File

@ -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

View File

@ -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

View File

@ -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

View File

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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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