1198 lines
44 KiB
Lua

-------------------------------------------------------------------------------
-- Spine Runtimes License Agreement
-- Last updated January 1, 2020. Replaces all prior versions.
--
-- Copyright (c) 2013-2020, Esoteric Software LLC
--
-- Integration of the Spine Runtimes into software or otherwise creating
-- derivative works of the Spine Runtimes is permitted under the terms and
-- conditions of Section 2 of the Spine Editor License Agreement:
-- http://esotericsoftware.com/spine-editor-license
--
-- Otherwise, it is permitted to integrate the Spine Runtimes into software
-- or otherwise create derivative works of the Spine Runtimes (collectively,
-- "Products"), provided that each user of the Products must obtain their own
-- Spine Editor license and redistribution of the Products in any form must
-- include this license and copyright notice.
--
-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
-- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
-- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
-- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-------------------------------------------------------------------------------
local table_insert = table.insert
local SkeletonData = require "spine-lua.SkeletonData"
local BoneData = require "spine-lua.BoneData"
local SlotData = require "spine-lua.SlotData"
local Skin = require "spine-lua.Skin"
local AttachmentLoader = require "spine-lua.AttachmentLoader"
local Animation = require "spine-lua.Animation"
local IkConstraintData = require "spine-lua.IkConstraintData"
local IkConstraint = require "spine-lua.IkConstraint"
local PathConstraintData = require "spine-lua.PathConstraintData"
local PathConstraint = require "spine-lua.PathConstraint"
local TransformConstraintData = require "spine-lua.TransformConstraintData"
local TransformConstraint = require "spine-lua.TransformConstraint"
local EventData = require "spine-lua.EventData"
local Event = require "spine-lua.Event"
local AttachmentType = require "spine-lua.attachments.AttachmentType"
local BlendMode = require "spine-lua.BlendMode"
local TransformMode = require "spine-lua.TransformMode"
local utils = require "spine-lua.utils"
local Color = require "spine-lua.Color"
local math_max = math.max
local math_floor = math.floor
local SkeletonJson = {}
function SkeletonJson.new (attachmentLoader)
if not attachmentLoader then attachmentLoader = AttachmentLoader.new() end
local self = {
attachmentLoader = attachmentLoader,
scale = 1,
linkedMeshes = {}
}
local readAttachment
local readAnimation
local readCurve
local readTimeline1
local readTimeline2
local getArray
local getValue
function self:readSkeletonDataFile (fileName, base)
return self:readSkeletonData(utils.readFile(fileName, base))
end
function self:readSkeletonData (jsonText)
local scale = self.scale
local skeletonData = SkeletonData.new(self.attachmentLoader)
local root = utils.readJSON(jsonText)
if not root then error("Invalid JSON: " .. jsonText, 2) end
-- Skeleton.
local skeletonMap = root["skeleton"]
if skeletonMap then
skeletonData.hash = skeletonMap["hash"]
skeletonData.version = skeletonMap["spine"]
skeletonData.x = skeletonMap["x"]
skeletonData.y = skeletonMap["y"]
skeletonData.width = skeletonMap["width"]
skeletonData.height = skeletonMap["height"]
skeletonData.fps = skeletonMap["fps"]
skeletonData.imagesPath = skeletonMap["images"]
end
-- Bones.
for i,boneMap in ipairs(root["bones"]) do
local boneName = boneMap["name"]
local parent
local parentName = boneMap["parent"]
if parentName then
parent = skeletonData:findBone(parentName)
if not parent then error("Parent bone not found: " .. parentName) end
end
local data = BoneData.new(i, boneName, parent)
data.length = getValue(boneMap, "length", 0) * scale
data.x = getValue(boneMap, "x", 0) * scale
data.y = getValue(boneMap, "y", 0) * scale
data.rotation = getValue(boneMap, "rotation", 0)
data.scaleX = getValue(boneMap, "scaleX", 1)
data.scaleY = getValue(boneMap, "scaleY", 1)
data.shearX = getValue(boneMap, "shearX", 0)
data.shearY = getValue(boneMap, "shearY", 0)
data.transformMode = TransformMode[getValue(boneMap, "transform", "normal")]
data.skinRequired = getValue(boneMap, "skin", false)
local color = boneMap["color"]
if color then
data.color = Color.newWith(tonumber(color:sub(1, 2), 16) / 255,
tonumber(color:sub(3, 4), 16) / 255,
tonumber(color:sub(5, 6), 16) / 255,
tonumber(color:sub(7, 8), 16) / 255)
end
table_insert(skeletonData.bones, data)
end
-- Slots.
if root["slots"] then
for i,slotMap in ipairs(root["slots"]) do
local slotName = slotMap["name"]
local boneName = slotMap["bone"]
local boneData = skeletonData:findBone(boneName)
if not boneData then error("Slot bone not found: " .. boneName) end
local data = SlotData.new(i, slotName, boneData)
local color = slotMap["color"]
if color then
data.color:set(tonumber(color:sub(1, 2), 16) / 255,
tonumber(color:sub(3, 4), 16) / 255,
tonumber(color:sub(5, 6), 16) / 255,
tonumber(color:sub(7, 8), 16) / 255)
end
local dark = slotMap["dark"]
if dark then
data.darkColor = Color.newWith(
tonumber(dark:sub(1, 2), 16) / 255,
tonumber(dark:sub(3, 4), 16) / 255,
tonumber(dark:sub(5, 6), 16) / 255, 0)
end
data.attachmentName = getValue(slotMap, "attachment", nil)
data.blendMode = BlendMode[getValue(slotMap, "blend", "normal")]
table_insert(skeletonData.slots, data)
skeletonData.nameToSlot[data.name] = data
end
end
-- IK constraints.
if root["ik"] then
for _,constraintMap in ipairs(root["ik"]) do
local data = IkConstraintData.new(constraintMap["name"])
data.order = getValue(constraintMap, "order", 0)
data.skinRequired = getValue(constraintMap, "skin", false)
for _,boneName in ipairs(constraintMap["bones"]) do
local bone = skeletonData:findBone(boneName)
if not bone then error("IK bone not found: " .. boneName) end
table_insert(data.bones, bone)
end
local targetName = constraintMap["target"]
data.target = skeletonData:findBone(targetName)
if not data.target then error("Target bone not found: " .. targetName) end
data.mix = getValue(constraintMap, "mix", 1)
data.softness = getValue(constraintMap, "softness", 0) * scale
if constraintMap["bendPositive"] == nil or constraintMap["bendPositive"] == true then
data.bendDirection = 1
else
data.bendDirection = -1
end
if constraintMap["compress"] == nil or constraintMap["compress"] == false then data.compress = false else data.compress = true end
if constraintMap["stretch"] == nil or constraintMap["stretch"] == false then data.stretch = false else data.stretch = true end
if constraintMap["uniform"] == nil or constraintMap["uniform"] == false then data.uniform = false else data.uniform = true end
table_insert(skeletonData.ikConstraints, data)
end
end
-- Transform constraints
if root["transform"] then
for _,constraintMap in ipairs(root["transform"]) do
local data = TransformConstraintData.new(constraintMap.name)
data.order = getValue(constraintMap, "order", 0)
data.skinRequired = getValue(constraintMap, "skin", false)
for _,boneName in ipairs(constraintMap.bones) do
local bone = skeletonData:findBone(boneName)
if not bone then error("Transform constraint bone not found: " .. boneName) end
table_insert(data.bones, bone)
end
local targetName = constraintMap.target
data.target = skeletonData:findBone(targetName)
if not data.target then error("Transform constraint target bone not found: " .. (targetName or "none")) end
data.local_ = getValue(constraintMap, "local", false)
data.relative = getValue(constraintMap, "relative", false)
data.offsetRotation = getValue(constraintMap, "rotation", 0)
data.offsetX = getValue(constraintMap, "x", 0) * scale
data.offsetY = getValue(constraintMap, "y", 0) * scale
data.offsetScaleX = getValue(constraintMap, "scaleX", 0)
data.offsetScaleY = getValue(constraintMap, "scaleY", 0)
data.offsetShearY = getValue(constraintMap, "shearY", 0)
data.mixRotate = getValue(constraintMap, "mixRotate", 1)
data.mixX = getValue(constraintMap, "mixX", 1)
data.mixY = getValue(constraintMap, "mixY", data.mixX)
data.mixScaleX = getValue(constraintMap, "mixScaleX", 1)
data.mixScaleY = getValue(constraintMap, "mixScaleY", data.mixScaleX)
data.mixShearY = getValue(constraintMap, "mixShearY", 1)
table_insert(skeletonData.transformConstraints, data)
end
end
-- Path constraints
if root["path"] then
for _,constraintMap in ipairs(root["path"]) do
local data = PathConstraintData.new(constraintMap.name)
data.order = getValue(constraintMap, "order", 0)
data.skinRequired = getValue(constraintMap, "skin", false)
for _,boneName in ipairs(constraintMap.bones) do
local bone = skeletonData:findBone(boneName)
if not bone then error("Path constraint bone not found: " .. boneName) end
table_insert(data.bones, bone)
end
local targetName = constraintMap.target
data.target = skeletonData:findSlot(targetName)
if data.target == nil then error("Path target slot not found: " .. targetName) end
data.positionMode = PathConstraintData.PositionMode[getValue(constraintMap, "positionMode", "percent"):lower()]
data.spacingMode = PathConstraintData.SpacingMode[getValue(constraintMap, "spacingMode", "length"):lower()]
data.rotateMode = PathConstraintData.RotateMode[getValue(constraintMap, "rotateMode", "tangent"):lower()]
data.offsetRotation = getValue(constraintMap, "rotation", 0)
data.position = getValue(constraintMap, "position", 0)
if data.positionMode == PathConstraintData.PositionMode.fixed then data.position = data.position * scale end
data.spacing = getValue(constraintMap, "spacing", 0)
if data.spacingMode == PathConstraintData.SpacingMode.length or data.spacingMode == PathConstraintData.SpacingMode.fixed then data.spacing = data.spacing * scale end
data.mixRotate = getValue(constraintMap, "mixRotate", 1)
data.mixX = getValue(constraintMap, "mixX", 1)
data.mixY = getValue(constraintMap, "mixY", data.mixX)
table_insert(skeletonData.pathConstraints, data)
end
end
-- Skins.
if root["skins"] then
for skinName,skinMap in pairs(root["skins"]) do
local skin = Skin.new(skinMap["name"])
if skinMap["bones"] then
for _, entry in ipairs(skinMap["bones"]) do
local bone = skeletonData:findBone(entry)
if bone == nil then error("Skin bone not found: " .. entry) end
table_insert(skin.bones, bone)
end
end
if skinMap["ik"] then
for _, entry in ipairs(skinMap["ik"]) do
local constraint = skeletonData:findIkConstraint(entry)
if constraint == nil then error("Skin IK constraint not found: " .. entry) end
table_insert(skin.constraints, constraint)
end
end
if skinMap["transform"] then
for _, entry in ipairs(skinMap["transform"]) do
local constraint = skeletonData:findTransformConstraint(entry)
if constraint == nil then error("Skin transform constraint not found: " .. entry) end
table_insert(skin.constraints, constraint)
end
end
if skinMap["path"] then
for _, entry in ipairs(skinMap["path"]) do
local constraint = skeletonData:findPathConstraint(entry)
if constraint == nil then error("Skin path constraint not found: " .. entry) end
table_insert(skin.constraints, constraint)
end
end
for slotName,slotMap in pairs(skinMap.attachments) do
local slotIndex = skeletonData:findSlot(slotName).index
for attachmentName,attachmentMap in pairs(slotMap) do
local attachment = readAttachment(attachmentMap, skin, slotIndex, attachmentName, skeletonData)
if attachment then
skin:setAttachment(slotIndex, attachmentName, attachment)
end
end
end
table_insert(skeletonData.skins, skin)
if skin.name == "default" then skeletonData.defaultSkin = skin end
end
end
-- Linked meshes
for _, linkedMesh in ipairs(self.linkedMeshes) do
local skin = skeletonData.defaultSkin
if linkedMesh.skin then skin = skeletonData:findSkin(linkedMesh.skin) end
if not skin then error("Skin not found: " .. linkedMesh.skin) end
local parent = skin:getAttachment(linkedMesh.slotIndex, linkedMesh.parent)
if not parent then error("Parent mesh not found: " .. linkedMesh.parent) end
if linkedMesh.inheritDeform then
linkedMesh.mesh.deformAttachment = parent
else
linkedMesh.mesh.deformAttachment = linkedMesh.mesh
end
linkedMesh.mesh:setParentMesh(parent)
linkedMesh.mesh:updateUVs()
end
self.linkedMeshes = {}
-- Events.
if root["events"] then
for eventName,eventMap in pairs(root["events"]) do
local data = EventData.new(eventName)
data.intValue = getValue(eventMap, "int", 0)
data.floatValue = getValue(eventMap, "float", 0)
data.stringValue = getValue(eventMap, "string", "")
data.audioPath = getValue(eventMap, "audio", nil)
if data.audioPath ~= nil then
data.volume = getValue(eventMap, "volume", 1)
data.balance = getValue(eventMap, "balance", 0)
end
table_insert(skeletonData.events, data)
end
end
-- Animations.
if root["animations"] then
for animationName,animationMap in pairs(root["animations"]) do
readAnimation(animationMap, animationName, skeletonData)
end
end
return skeletonData
end
readAttachment = function (map, skin, slotIndex, name, skeletonData)
local scale = self.scale
name = getValue(map, "name", name)
local type = AttachmentType[getValue(map, "type", "region")]
local path = getValue(map, "path", name)
if type == AttachmentType.region then
local region = attachmentLoader:newRegionAttachment(skin, name, path)
if not region then return nil end
region.path = path
region.x = getValue(map, "x", 0) * scale
region.y = getValue(map, "y", 0) * scale
region.scaleX = getValue(map, "scaleX", 1)
region.scaleY = getValue(map, "scaleY", 1)
region.rotation = getValue(map, "rotation", 0)
region.width = map.width * scale
region.height = map.height * scale
local color = map["color"]
if color then
region.color:set(tonumber(color:sub(1, 2), 16) / 255,
tonumber(color:sub(3, 4), 16) / 255,
tonumber(color:sub(5, 6), 16) / 255,
tonumber(color:sub(7, 8), 16) / 255)
end
region:updateOffset()
return region
elseif type == AttachmentType.boundingbox then
local box = attachmentLoader:newBoundingBoxAttachment(skin, name)
if not box then return nil end
readVertices(map, box, map.vertexCount * 2)
local color = map.color
if color then
box.color:set(tonumber(color:sub(1, 2), 16) / 255,
tonumber(color:sub(3, 4), 16) / 255,
tonumber(color:sub(5, 6), 16) / 255,
tonumber(color:sub(7, 8), 16) / 255)
end
return box
elseif type == AttachmentType.mesh or type == AttachmentType.linkedmesh then
local mesh = attachmentLoader:newMeshAttachment(skin, name, path)
if not mesh then return nil end
mesh.path = path
local color = map.color
if color then
mesh.color:set(tonumber(color:sub(1, 2), 16) / 255,
tonumber(color:sub(3, 4), 16) / 255,
tonumber(color:sub(5, 6), 16) / 255,
tonumber(color:sub(7, 8), 16) / 255)
end
mesh.width = getValue(map, "width", 0) * scale
mesh.height = getValue(map, "height", 0) * scale
local parent = map.parent
if parent then
table_insert(self.linkedMeshes, {
mesh = mesh,
skin = getValue(map, "skin", nil),
slotIndex = slotIndex,
parent = parent,
inheritDeform = getValue(map, "deform", true)
})
return mesh
end
local uvs = getArray(map, "uvs", 1)
readVertices(map, mesh, #uvs)
mesh.triangles = getArray(map, "triangles", 1)
-- adjust triangle indices by 1, vertices are one-indexed
for i,v in ipairs(mesh.triangles) do
mesh.triangles[i] = v + 1
end
mesh.regionUVs = uvs
mesh:updateUVs()
mesh.hullLength = getValue(map, "hull", 0) * 2
return mesh
elseif type == AttachmentType.path then
local path = self.attachmentLoader:newPathAttachment(skin, name)
if not path then return nil end
path.closed = getValue(map, "closed", false)
path.constantSpeed = getValue(map, "constantSpeed", true)
local vertexCount = map.vertexCount
readVertices(map, path, vertexCount * 2)
local lengths = utils.newNumberArray(vertexCount / 3, 0)
for i,v in ipairs(map.lengths) do
lengths[i] = v * scale
end
path.lengths = lengths
local color = map.color
if color then
path.color:set(tonumber(color:sub(1, 2), 16) / 255,
tonumber(color:sub(3, 4), 16) / 255,
tonumber(color:sub(5, 6), 16) / 255,
tonumber(color:sub(7, 8), 16) / 255)
end
return path
elseif type == AttachmentType.point then
local point = self.attachmentLoader:newPointAttachment(skin, name)
if not point then return nil end
point.x = getValue(map, "x", 0) * scale
point.y = getValue(map, "y", 0) * scale
point.rotation = getValue(map, "rotation", 0)
local color = map.color
if color then
path.color:set(tonumber(color:sub(1, 2), 16) / 255,
tonumber(color:sub(3, 4), 16) / 255,
tonumber(color:sub(5, 6), 16) / 255,
tonumber(color:sub(7, 8), 16) / 255)
end
return point
elseif type == AttachmentType.clipping then
local clip = attachmentLoader:newClippingAttachment(skin, name)
if not clip then return nil end
local _end = getValue(map, "end", nil)
if _end then
local slot = skeletonData:findSlot(_end)
if not slot then error("Clipping end slot not found: " .. _end) end
clip.endSlot = slot
end
readVertices(map, clip, map.vertexCount * 2)
local color = map.color
if color then
clip.color:set(tonumber(color:sub(1, 2), 16) / 255,
tonumber(color:sub(3, 4), 16) / 255,
tonumber(color:sub(5, 6), 16) / 255,
tonumber(color:sub(7, 8), 16) / 255)
end
return clip
end
error("Unknown attachment type: " .. type .. " (" .. name .. ")")
end
readVertices = function (map, attachment, verticesLength)
local scale = self.scale
attachment.worldVerticesLength = verticesLength
local vertices = getArray(map, "vertices", 1)
if verticesLength == #vertices then
if scale ~= 1 then
local i = 0
local n = #vertices
while i < n do
vertices[i + 1] = vertices[i + 1] * scale
i = i + 1
end
end
attachment.vertices = vertices
return
end
local weights = {}
local bones = {}
local i = 0
local n = #vertices
while i < n do
local boneCount = vertices[i + 1]
i = i + 1
table_insert(bones, boneCount)
local nn = i + boneCount * 4
while i < nn do
table_insert(bones, vertices[i + 1] + 1) -- +1 because bones are one-indexed
table_insert(weights, vertices[i + 2] * scale)
table_insert(weights, vertices[i + 3] * scale)
table_insert(weights, vertices[i + 4])
i = i + 4
end
end
attachment.bones = bones
attachment.vertices = weights
end
readAnimation = function (map, name, skeletonData)
local timelines = {}
local scale = self.scale
-- Slot timelines.
local slotsMap = map["slots"]
if slotsMap then
for slotName,slotMap in pairs(slotsMap) do
local slotIndex = skeletonData:findSlot(slotName).index
for timelineName,timelineMap in pairs(slotMap) do
if not timelineMap then
elseif timelineName == "attachment" then
local timeline = Animation.AttachmentTimeline.new(#timelineMap, #timelineMap, slotIndex)
for i,keyMap in ipairs(timelineMap) do
timeline:setFrame(i - 1, getValue(keyMap, "time", 0), keyMap["name"])
end
table_insert(timelines, timeline)
elseif timelineName == "rgba" then
local timeline = Animation.RGBATimeline.new(#timelineMap, #timelineMap * 4, slotIndex)
local keyMap = timelineMap[1]
local time = getValue(keyMap, "time", 0)
local color = keyMap["color"]
local r = tonumber(color:sub(1, 2), 16) / 255
local g = tonumber(color:sub(3, 4), 16) / 255
local b = tonumber(color:sub(5, 6), 16) / 255
local a = tonumber(color:sub(7, 8), 16) / 255
local bezier = 0
for i,keyMap in ipairs(timelineMap) do
local frame = i - 1
timeline:setFrame(frame, time, r, g, b, a)
local nextMap = timelineMap[i + 1]
if not nextMap then
timeline:shrink(bezier)
break
end
local time2 = getValue(nextMap, "time", 0)
color = nextMap["color"]
local nr = tonumber(color:sub(1, 2), 16) / 255
local ng = tonumber(color:sub(3, 4), 16) / 255
local nb = tonumber(color:sub(5, 6), 16) / 255
local na = tonumber(color:sub(7, 8), 16) / 255
local curve = keyMap.curve
if curve then
bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1)
bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1)
bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1)
bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, a, na, 1)
end
time = time2
r = nr
g = ng
b = nb
a = na
end
table_insert(timelines, timeline)
elseif timelineName == "rgb" then
local timeline = Animation.RGBTimeline.new(#timelineMap, #timelineMap * 3, slotIndex)
local keyMap = timelineMap[1]
local time = getValue(keyMap, "time", 0)
local color = keyMap["color"]
local r = tonumber(color:sub(1, 2), 16) / 255
local g = tonumber(color:sub(3, 4), 16) / 255
local b = tonumber(color:sub(5, 6), 16) / 255
local bezier = 0
for i,keyMap in ipairs(timelineMap) do
local frame = i - 1
timeline:setFrame(frame, time, r, g, b)
local nextMap = timelineMap[i + 1]
if not nextMap then
timeline:shrink(bezier)
break
end
local time2 = getValue(nextMap, "time", 0)
color = nextMap["color"]
local nr = tonumber(color:sub(1, 2), 16) / 255
local ng = tonumber(color:sub(3, 4), 16) / 255
local nb = tonumber(color:sub(5, 6), 16) / 255
local curve = keyMap.curve
if curve then
bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1)
bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1)
bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1)
end
time = time2
r = nr
g = ng
b = nb
end
table_insert(timelines, timeline)
elseif timelineName == "alpha" then
table_insert(timelines, readTimeline1(timelineMap, Animation.AlphaTimeline.new(#timelineMap, #timelineMap, slotIndex), 0, 1))
elseif timelineName == "rgba2" then
local timeline = Animation.RGBA2Timeline.new(#timelineMap, #timelineMap * 7, slotIndex)
local keyMap = timelineMap[1]
local time = getValue(keyMap, "time", 0)
local color = keyMap["light"]
local r = tonumber(color:sub(1, 2), 16) / 255
local g = tonumber(color:sub(3, 4), 16) / 255
local b = tonumber(color:sub(5, 6), 16) / 255
local a = tonumber(color:sub(7, 8), 16) / 255
color = keyMap["dark"]
local r2 = tonumber(color:sub(1, 2), 16) / 255
local g2 = tonumber(color:sub(3, 4), 16) / 255
local b2 = tonumber(color:sub(5, 6), 16) / 255
local bezier = 0
for i,keyMap in ipairs(timelineMap) do
local frame = i - 1
timeline:setFrame(frame, time, r, g, b, a, r2, g2, b2)
local nextMap = timelineMap[i + 1]
if not nextMap then
timeline:shrink(bezier)
break
end
local time2 = getValue(nextMap, "time", 0)
color = nextMap["light"]
local nr = tonumber(color:sub(1, 2), 16) / 255
local ng = tonumber(color:sub(3, 4), 16) / 255
local nb = tonumber(color:sub(5, 6), 16) / 255
local na = tonumber(color:sub(7, 8), 16) / 255
color = nextMap["dark"]
local nr2 = tonumber(color:sub(1, 2), 16) / 255
local ng2 = tonumber(color:sub(3, 4), 16) / 255
local nb2 = tonumber(color:sub(5, 6), 16) / 255
local curve = keyMap.curve
if curve then
bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1)
bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1)
bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1)
bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, a, na, 1)
bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, r2, nr2, 1)
bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, g2, ng2, 1)
bezier = readCurve(curve, timeline, bezier, frame, 6, time, time2, b2, nb2, 1)
end
time = time2
r = nr
g = ng
b = nb
a = na
r2 = nr2
g2 = ng2
b2 = nb2
end
table_insert(timelines, timeline)
elseif timelineName == "rgb2" then
local timeline = Animation.RGB2Timeline.new(#timelineMap, #timelineMap * 6, slotIndex)
local keyMap = timelineMap[1]
local time = getValue(keyMap, "time", 0)
local color = keyMap["light"]
local r = tonumber(color:sub(1, 2), 16) / 255
local g = tonumber(color:sub(3, 4), 16) / 255
local b = tonumber(color:sub(5, 6), 16) / 255
color = keyMap["dark"]
local r2 = tonumber(color:sub(1, 2), 16) / 255
local g2 = tonumber(color:sub(3, 4), 16) / 255
local b2 = tonumber(color:sub(5, 6), 16) / 255
local bezier = 0
for i,keyMap in ipairs(timelineMap) do
local frame = i - 1
timeline:setFrame(frame, time, r, g, b, r2, g2, b2)
local nextMap = timelineMap[i + 1]
if not nextMap then
timeline:shrink(bezier)
break
end
local time2 = getValue(nextMap, "time", 0)
color = nextMap["light"]
local nr = tonumber(color:sub(1, 2), 16) / 255
local ng = tonumber(color:sub(3, 4), 16) / 255
local nb = tonumber(color:sub(5, 6), 16) / 255
color = nextMap["dark"]
local nr2 = tonumber(color:sub(1, 2), 16) / 255
local ng2 = tonumber(color:sub(3, 4), 16) / 255
local nb2 = tonumber(color:sub(5, 6), 16) / 255
local curve = keyMap.curve
if curve then
bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1)
bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1)
bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1)
bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, r2, nr2, 1)
bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, g2, ng2, 1)
bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, b2, nb2, 1)
end
time = time2
r = nr
g = ng
b = nb
r2 = nr2
g2 = ng2
b2 = nb2
end
table_insert(timelines, timeline)
else
error("Invalid timeline type for a slot: " .. timelineName .. " (" .. slotName .. ")")
end
end
end
end
-- Bone timelines.
local bonesMap = map["bones"]
if bonesMap then
for boneName,boneMap in pairs(bonesMap) do
local boneIndex = skeletonData:findBone(boneName).index
for timelineName,timelineMap in pairs(boneMap) do
if not timelineMap then
elseif timelineName == "rotate" then
table_insert(timelines, readTimeline1(timelineMap, Animation.RotateTimeline.new(#timelineMap, #timelineMap, boneIndex), 0, 1))
elseif timelineName == "translate" then
local timeline = Animation.TranslateTimeline.new(#timelineMap, #timelineMap * 2, boneIndex)
table_insert(timelines, readTimeline2(timelineMap, timeline, "x", "y", 0, scale))
elseif timelineName == "translatex" then
local timeline = Animation.TranslateXTimeline.new(#timelineMap, #timelineMap, boneIndex)
table_insert(timelines, readTimeline1(timelineMap, timeline, 0, scale))
elseif timelineName == "translatey" then
local timeline = Animation.TranslateYTimeline.new(#timelineMap, #timelineMap, boneIndex)
table_insert(timelines, readTimeline1(timelineMap, timeline, 0, scale))
elseif timelineName == "scale" then
local timeline = Animation.ScaleTimeline.new(#timelineMap, #timelineMap * 2, boneIndex)
table_insert(timelines, readTimeline2(timelineMap, timeline, "x", "y", 1, 1))
elseif timelineName == "scalex" then
local timeline = Animation.ScaleXTimeline.new(#timelineMap, #timelineMap, boneIndex)
table_insert(timelines, readTimeline1(timelineMap, timeline, 1, 1))
elseif timelineName == "scaley" then
local timeline = Animation.ScaleYTimeline.new(#timelineMap, #timelineMap, boneIndex)
table_insert(timelines, readTimeline1(timelineMap, timeline, 1, 1))
elseif timelineName == "shear" then
local timeline = Animation.ShearTimeline.new(#timelineMap, #timelineMap * 2, boneIndex)
table_insert(timelines, readTimeline2(timelineMap, timeline, "x", "y", 0, 1))
elseif timelineName == "shearx" then
local timeline = Animation.ShearXTimeline.new(#timelineMap, #timelineMap, boneIndex)
table_insert(timelines, readTimeline1(timelineMap, timeline, 0, 1))
elseif timelineName == "sheary" then
local timeline = Animation.ShearYTimeline.new(#timelineMap, #timelineMap, boneIndex)
table_insert(timelines, readTimeline1(timelineMap, timeline, 0, 1))
else
error("Invalid timeline type for a bone: " .. timelineName .. " (" .. boneName .. ")")
end
end
end
end
-- IK timelines.
local ik = map["ik"]
if ik then
for constraintName,timelineMap in pairs(ik) do
local keyMap = timelineMap[1]
if keyMap then
local constraintIndex
for i,other in pairs(skeletonData.ikConstraints) do
if other.name == constraintName then
constraintIndex = i
break
end
end
local timeline = Animation.IkConstraintTimeline.new(#timelineMap, #timelineMap * 2, constraintIndex)
local time = getValue(keyMap, "time", 0)
local mix = getValue(keyMap, "mix", 1)
local softness = getValue(keyMap, "softness", 0) * scale
local bezier = 0
for i,keyMap in ipairs(timelineMap) do
local frame = i - 1
local bendPositive = 1
local compress = false
local stretch = false
if keyMap["bendPositive"] == false then bendPositive = -1 end
if keyMap["compress"] ~= nil then compress = keyMap["compress"] end
if keyMap["stretch"] ~= nil then stretch = keyMap["stretch"] end
timeline:setFrame(frame, time, mix, softness, bendPositive, compress, stretch)
local nextMap = timelineMap[i + 1]
if not nextMap then
timeline:shrink(bezier)
break
end
local time2 = getValue(nextMap, "time", 0)
color = nextMap["color"]
local mix2 = getValue(nextMap, "mix", 1)
local softness2 = getValue(nextMap, "softness", 0) * scale
local curve = keyMap.curve
if curve then
bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mix, mix2, 1)
bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, softness, softness2, scale)
end
time = time2
mix = mix2
softness = softness2
end
table_insert(timelines, timeline)
end
end
end
-- Transform constraint timelines.
local transform = map["transform"]
if transform then
for constraintName, timelineMap in pairs(transform) do
local keyMap = timelineMap[1]
if keyMap then
local constraintIndex
for i,other in pairs(skeletonData.transformConstraints) do
if other.name == constraintName then
constraintIndex = i
break
end
end
local timeline = Animation.TransformConstraintTimeline.new(#timelineMap, #timelineMap * 6, constraintIndex)
local time = getValue(keyMap, "time", 0)
local mixRotate = getValue(keyMap, "mixRotate", 0)
local mixX = getValue(keyMap, "mixX", 1)
local mixY = getValue(keyMap, "mixY", mixX)
local mixScaleX = getValue(keyMap, "mixScaleX", 1)
local mixScaleY = getValue(keyMap, "mixScaleY", mixScaleX)
local mixShearY = getValue(keyMap, "mixShearY", 1)
local bezier = 0
for i,keyMap in ipairs(timelineMap) do
local frame = i - 1
timeline:setFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY)
local nextMap = timelineMap[i + 1]
if not nextMap then
timeline:shrink(bezier)
break
end
local time2 = getValue(nextMap, "time", 0)
local mixRotate2 = getValue(nextMap, "mixRotate", 1)
local mixX2 = getValue(nextMap, "mixX", 1)
local mixY2 = getValue(nextMap, "mixY", mixX2)
local mixScaleX2 = getValue(nextMap, "mixScaleX", 1)
local mixScaleY2 = getValue(nextMap, "mixScaleY", mixScaleX2)
local mixShearY2 = getValue(nextMap, "mixShearY", 1)
local curve = keyMap.curve
if curve then
bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mixRotate, mixRotate2, 1)
bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, mixX, mixX2, 1)
bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, mixY, mixY2, 1)
bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, mixScaleX, mixScaleX2, 1)
bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, mixScaleY, mixScaleY2, 1)
bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, mixShearY, mixShearY2, 1)
end
time = time2
mixRotate = mixRotate2
mixX = mixX2
mixY = mixY2
mixScaleX = mixScaleX2
mixScaleY = mixScaleY2
mixScaleX = mixScaleX2
end
table_insert(timelines, timeline)
end
end
end
-- Path constraint timelines.
if map.path then
for constraintName,constraintMap in pairs(map.path) do
local constraint, constraintIndex
for i,other in pairs(skeletonData.pathConstraints) do
if other.name == constraintName then
constraintIndex = i
constraint = other
break
end
end
for timelineName, timelineMap in pairs(constraintMap) do
local keyMap = timelineMap[1]
if keyMap then
if timelineName == "position" then
local timeline = Animation.PathConstraintPositionTimeline.new(#timelineMap, #timelineMap, constraintIndex)
local timelineScale = 1
if constraint.positionMode == PathConstraintData.PositionMode.fixed then timelineScale = scale end
table_insert(timelines, readTimeline1(timelineMap, timeline, 0, timelineScale))
elseif timelineName == "spacing" then
local timeline = Animation.PathConstraintSpacingTimeline.new(#timelineMap, #timelineMap, constraintIndex)
local timelineScale = 1
if data.spacingMode == PathConstraintData.SpacingMode.Length or data.spacingMode == PathConstraintData.SpacingMode.Fixed then timelineScale = scale end
table_insert(timelines, readTimeline1(timelineMap, timeline, 0, timelineScale))
elseif timelineName == "mix" then
local timeline = Animation.PathConstraintMixTimeline.new(#timelineMap, #timelineMap * 3, constraintIndex)
local time = getValue(keyMap, "time", 0)
local mixRotate = getValue(keyMap, "mixRotate", 1)
local mixX = getValue(keyMap, "mixX", 1)
local mixY = getValue(keyMap, "mixY", mixX)
local bezier = 0
for i,keyMap in ipairs(timelineMap) do
local frame = i - 1
timeline:setFrame(frame, time, mixRotate, mixX, mixY)
local nextMap = timelineMap[i + 1]
if not nextMap then
timeline:shrink(bezier)
break
end
local time2 = getValue(nextMap, "time", 0)
local mixRotate2 = getValue(nextMap, "mixRotate", 1)
local mixX2 = getValue(nextMap, "mixX", 1)
local mixY2 = getValue(nextMap, "mixY", mixX2)
local curve = keyMap.curve
if curve then
bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mixRotate, mixRotate2, 1)
bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, mixX, mixX2, 1)
bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, mixY, mixY2, 1)
end
time = time2
mixRotate = mixRotate2
mixX = mixX2
mixY = mixY2
keyMap = nextMap
end
table_insert(timelines, timeline)
end
end
end
end
end
-- Deform timelines.
if map.deform then
for deformName, deformMap in pairs(map.deform) do
local skin = skeletonData:findSkin(deformName)
if not skin then error("Skin not found: " .. deformName) end
for slotName,slotMap in pairs(deformMap) do
local slotIndex = skeletonData:findSlot(slotName).index
for timelineName,timelineMap in pairs(slotMap) do
local keyMap = timelineMap[1]
if keyMap then
local attachment = skin:getAttachment(slotIndex, timelineName)
if not attachment then error("Deform attachment not found: " .. timelineMap.name) end
local weighted = attachment.bones ~= nil
local vertices = attachment.vertices
local deformLength = #vertices
if weighted then deformLength = math_floor(deformLength / 3) * 2 end
local timeline = Animation.DeformTimeline.new(#timelineMap, #timelineMap, slotIndex, attachment)
local time = getValue(keyMap, "time", 0)
local bezier = 0
for i,keyMap in ipairs(timelineMap) do
local frame = i - 1
local deform
local verticesValue = getValue(keyMap, "vertices", nil)
if verticesValue == nil then
if weighted then
deform = utils.newNumberArray(deformLength)
else
deform = vertices
end
else
deform = utils.newNumberArray(deformLength)
local start = getValue(keyMap, "offset", 0) + 1
utils.arrayCopy(verticesValue, 1, deform, start, #verticesValue)
if scale ~= 1 then
local i = start
local n = i + #verticesValue
while i < n do
deform[i] = deform[i] * scale
i = i + 1
end
end
if not weighted then
local i = 1
local n = i + deformLength
while i < n do
deform[i] = deform[i] + vertices[i]
i = i + 1
end
end
end
timeline:setFrame(frame, time, deform)
local nextMap = timelineMap[i + 1]
if not nextMap then
timeline:shrink(bezier)
break
end
local time2 = getValue(nextMap, "time", 0)
local curve = keyMap.curve
if curve then bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, 0, 1, 1) end
time = time2
end
table_insert(timelines, timeline)
end
end
end
end
end
-- Draw order timelines.
if map["drawOrder"] then
local timeline = Animation.DrawOrderTimeline.new(#map["drawOrder"])
local slotCount = #skeletonData.slots
local frame = 0
for _,drawOrderMap in ipairs(map["drawOrder"]) do
local drawOrder
local offsets = drawOrderMap["offsets"]
if offsets then
drawOrder = {}
local unchanged = {}
local originalIndex = 1
local unchangedIndex = 1
for _,offsetMap in ipairs(offsets) do
local slotIndex = skeletonData:findSlot(offsetMap["slot"]).index
-- Collect unchanged items.
while originalIndex ~= slotIndex do
unchanged[unchangedIndex] = originalIndex
unchangedIndex = unchangedIndex + 1
originalIndex = originalIndex + 1
end
-- Set changed items.
drawOrder[originalIndex + offsetMap["offset"]] = originalIndex
originalIndex = originalIndex + 1
end
-- Collect remaining unchanged items.
while originalIndex <= slotCount do
unchanged[unchangedIndex] = originalIndex
unchangedIndex = unchangedIndex + 1
originalIndex = originalIndex + 1
end
-- Fill in unchanged items.
for ii = slotCount, 1, -1 do
if not drawOrder[ii] then
unchangedIndex = unchangedIndex - 1
drawOrder[ii] = unchanged[unchangedIndex]
end
end
end
timeline:setFrame(frame, getValue(drawOrderMap, "time", 0), drawOrder)
frame = frame + 1
end
table_insert(timelines, timeline)
end
-- Event timelines.
local events = map["events"]
if events then
local timeline = Animation.EventTimeline.new(#events)
local frame = 0
for _,eventMap in ipairs(events) do
local eventData = skeletonData:findEvent(eventMap["name"])
if not eventData then error("Event not found: " .. eventMap["name"]) end
local event = Event.new(getValue(eventMap, "time", 0), eventData)
if eventMap["int"] ~= nil then
event.intValue = eventMap["int"]
else
event.intValue = eventData.intValue
end
if eventMap["float"] ~= nil then
event.floatValue = eventMap["float"]
else
event.floatValue = eventData.floatValue
end
if eventMap["string"] ~= nil then
event.stringValue = eventMap["string"]
else
event.stringValue = eventData.stringValue
end
if eventData.audioPath ~= nil then
event.volume = getValue(eventMap, "volume", 1)
event.balance = getValue(eventMap, "balance", 0)
end
timeline:setFrame(frame, event)
frame = frame + 1
end
table_insert(timelines, timeline)
end
local duration = 0
for _,timeline in ipairs(timelines) do
duration = math_max(duration, timeline:getDuration())
end
table_insert(skeletonData.animations, Animation.new(name, timelines, duration))
end
readTimeline1 = function (keys, timeline, defaultValue, scale)
local keyMap = keys[1]
local time = getValue(keyMap, "time", 0)
local value = getValue(keyMap, "value", defaultValue) * scale
local bezier = 0
for i,keyMap in ipairs(keys) do
local frame = i - 1
timeline:setFrame(frame, time, value)
local nextMap = keys[i + 1]
if not nextMap then
timeline:shrink(bezier)
return timeline
end
local time2 = getValue(nextMap, "time", 0)
local value2 = getValue(nextMap, "value", defaultValue) * scale
if keyMap.curve then bezier = readCurve(keyMap.curve, timeline, bezier, frame, 0, time, time2, value, value2, scale) end
time = time2
value = value2
end
end
readTimeline2 = function (keys, timeline, name1, name2, defaultValue, scale)
local keyMap = keys[1]
local time = getValue(keyMap, "time", 0)
local value1 = getValue(keyMap, name1, defaultValue) * scale
local value2 = getValue(keyMap, name2, defaultValue) * scale
local bezier = 0
for i,keyMap in ipairs(keys) do
local frame = i - 1
timeline:setFrame(frame, time, value1, value2)
local nextMap = keys[i + 1]
if not nextMap then
timeline:shrink(bezier)
return timeline
end
local time2 = getValue(nextMap, "time", 0)
local nvalue1 = getValue(nextMap, name1, defaultValue) * scale
local nvalue2 = getValue(nextMap, name2, defaultValue) * scale
local curve = keyMap.curve
if curve then
bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, value1, nvalue1, scale)
bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, value2, nvalue2, scale)
end
time = time2
value1 = nvalue1
value2 = nvalue2
end
end
readCurve = function (curve, timeline, bezier, frame, value, time1, time2, value1, value2, scale)
if curve == "stepped" then
timeline:setStepped(frame)
return bezier
end
local i = value * 4 + 1
local cx1 = curve[i]
local cy1 = curve[i + 1] * scale
local cx2 = curve[i + 2]
local cy2 = curve[i + 3] * scale
timeline:setBezier(bezier, frame, value, time1, value1, cx1, cy1, cx2, cy2, time2, value2)
return bezier + 1
end
getArray = function (map, name, scale)
local list = map[name]
local values = {}
if scale == 1 then
for i = 1, #list do
values[i] = list[i]
end
else
for i = 1, #list do
values[i] = list[i] * scale
end
end
return values
end
getValue = function (map, name, default)
local value = map[name]
if value == nil then return default else return value end
end
return self
end
return SkeletonJson