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. -- 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.x = display.contentWidth * 0.5
skeleton.group.y = 325 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.

View File

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

View File

@ -356,17 +356,13 @@ 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] r = frames[#frames - 3]
local g = frames[#frames - 2] g = frames[#frames - 2]
local b = frames[#frames - 1] b = frames[#frames - 1]
local a = frames[#frames] a = frames[#frames]
slot:setColor(r, g, b, a) else
return
end
-- Interpolate between the last frame and the current frame. -- Interpolate between the last frame and the current frame.
local frameIndex = binarySearch(frames, time, 5) local frameIndex = binarySearch(frames, time, 5)
local lastFrameR = frames[frameIndex - 4] local lastFrameR = frames[frameIndex - 4]
@ -378,10 +374,12 @@ function Animation.ColorTimeline.new ()
if percent < 0 then percent = 0 elseif percent > 255 then percent = 255 end if percent < 0 then percent = 0 elseif percent > 255 then percent = 255 end
percent = self:getCurvePercent(frameIndex / 5 - 1, percent) percent = self:getCurvePercent(frameIndex / 5 - 1, percent)
local r = lastFrameR + (frames[frameIndex + FRAME_R] - lastFrameR) * percent r = lastFrameR + (frames[frameIndex + FRAME_R] - lastFrameR) * percent
local g = lastFrameG + (frames[frameIndex + FRAME_G] - lastFrameG) * percent g = lastFrameG + (frames[frameIndex + FRAME_G] - lastFrameG) * percent
local b = lastFrameB + (frames[frameIndex + FRAME_B] - lastFrameB) * percent b = lastFrameB + (frames[frameIndex + FRAME_B] - lastFrameB) * percent
local a = lastFrameA + (frames[frameIndex + FRAME_A] - lastFrameA) * percent a = lastFrameA + (frames[frameIndex + FRAME_A] - lastFrameA) * percent
end
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

View File

@ -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 + delta * current.timeScale
current.time = current.time + trackDelta
if current.previous then if current.previous then
current.previous.time = current.previous.time + trackDelta local previousDelta = delta * current.previous.timeScale
current.mixTime = current.mixTime + trackDelta current.previous.time = current.previous.time + previousDelta
current.mixTime = current.mixTime + previousDelta
end end
local next = current.next 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 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
} }

View File

@ -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 = { local AttachmentLoader = {}
failed = {}
}
function AttachmentLoader.new () function AttachmentLoader.new ()
local self = {} local self = {}
function self:newAttachment (type, name) function self:newRegionAttachment (skin, name, path)
if type == AttachmentType.region then
return RegionAttachment.new(name) return RegionAttachment.new(name)
end end
if type == AttachmentType.boundingbox then
return BoundingBoxAttachment.new(name) function self:newMeshAttachment (skin, name, path)
return MeshAttachment.new(name)
end end
error("Unknown attachment type: " .. type .. " (" .. name .. ")")
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

View File

@ -30,6 +30,8 @@
local AttachmentType = { local AttachmentType = {
region = 0, region = 0,
boundingbox = 1 boundingbox = 1,
mesh = 2,
skinnedmesh = 3
} }
return AttachmentType 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 = { 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

View File

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

View File

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

View File

@ -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) if boneMap["scaleX"] ~= nil then
boneData.scaleY = (boneMap["scaleY"] or 1) 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"]
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 if type == AttachmentType.region then
attachment.x = (map["x"] or 0) * scale local region = attachmentLoader:newRegionAttachment(type, name, path)
attachment.y = (map["y"] or 0) * scale if not region then return nil end
attachment.scaleX = (map["scaleX"] or 1) region.x = (map["x"] or 0) * scale
attachment.scaleY = (map["scaleY"] or 1) region.y = (map["y"] or 0) * scale
attachment.rotation = (map["rotation"] or 0) if map["scaleX"] ~= nil then
attachment.width = map["width"] * scale region.scaleX = map["scaleX"]
attachment.height = map["height"] * scale 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(frameIndex, valueMap["time"], valueMap["angle"])
timeline:setFrame(keyframeIndex, time, valueMap["angle"]) readCurve(timeline, frameIndex, valueMap)
readCurve(timeline, keyframeIndex, valueMap) frameIndex = frameIndex + 1
keyframeIndex = keyframeIndex + 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) timeline:setFrame(frameIndex, valueMap["time"], x, y)
readCurve(timeline, keyframeIndex, valueMap) readCurve(timeline, frameIndex, valueMap)
keyframeIndex = keyframeIndex + 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())
@ -239,72 +381,70 @@ function SkeletonJson.new (attachmentLoader)
end end
end end
local slotsMap = map["slots"] local ffd = map["ffd"]
if slotsMap then if ffd then
for slotName,timelineMap in pairs(slotsMap) do for skinName,slotMap in pairs(ffd) do
local slotIndex = skeletonData.slotNameIndices[slotName] local skin = skeletonData.findSkin(skinName)
for slotName,meshMap in pairs(slotMap) do
for timelineName,values in pairs(timelineMap) do local slotIndex = skeletonData.findSlotIndex(slotName)
if timelineName == "color" then for meshName,values in pairs(meshMap) do
local timeline = Animation.ColorTimeline.new() 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 local isMesh = attachment.type == AttachmentType.mesh
for i,valueMap in ipairs(values) do local vertexCount
local time = valueMap["time"] if isMesh then
local color = valueMap["color"] vertexCount = attachment.vertices.length
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
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)
frameIndex = frameIndex + 1
end
table.insert(timelines, timeline)
duration = math.max(duration, timeline:getDuration())
else else
error("Invalid frame type for a slot: " .. timelineName .. " (" .. slotName .. ")") vertexCount = attachment.weights.length / 3 * 2
end
local frameIndex = 0
for i,valueMap in ipairs(values) do
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
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
end end
local events = map["events"] timeline:setFrame(frameIndex, valueMap["time"], vertices)
if events then readCurve(timeline, frameIndex, valueMap)
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 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())
end end
end
end
end
local drawOrderValues = map["draworder"] local drawOrderValues = map["draworder"]
if drawOrderValues then if drawOrderValues then
@ -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

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

View File

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