spine-runtimes/spine-lua/SkeletonJson.lua
2013-08-16 17:45:16 +02:00

280 lines
9.7 KiB
Lua

-------------------------------------------------------------------------------
-- Copyright (c) 2013, Esoteric Software
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- 1. Redistributions of source code must retain the above copyright notice, this
-- list of conditions and the following disclaimer.
-- 2. Redistributions in binary form must reproduce the above copyright notice,
-- this list of conditions and the following disclaimer in the documentation
-- and/or other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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 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 TIMELINE_SCALE = "scale"
local TIMELINE_ROTATE = "rotate"
local TIMELINE_TRANSLATE = "translate"
local TIMELINE_ATTACHMENT = "attachment"
local TIMELINE_COLOR = "color"
local SkeletonJson = {}
function SkeletonJson.new (attachmentLoader)
if not attachmentLoader then attachmentLoader = AttachmentLoader.new() end
local self = {
attachmentLoader = attachmentLoader,
scale = 1
}
function self:readSkeletonDataFile (fileName, base)
return self:readSkeletonData(spine.utils.readFile(fileName, base))
end
local readAttachment
local readAnimation
local readCurve
function self:readSkeletonData (jsonText)
local skeletonData = SkeletonData.new(self.attachmentLoader)
local root = spine.utils.readJSON(jsonText)
if not root then error("Invalid JSON: " .. jsonText, 2) end
-- Bones.
for i,boneMap in ipairs(root["bones"]) do
local boneName = boneMap["name"]
local parent = nil
local parentName = boneMap["parent"]
if parentName then
parent = skeletonData:findBone(parentName)
if not parent then error("Parent bone not found: " .. parentName) end
end
local boneData = BoneData.new(boneName, parent)
boneData.length = (boneMap["length"] or 0) * self.scale
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)
-- typical 'value or default' will not work here, as in practice the possible values are 'false' or nil,
-- both of which evaluate to false and the default value is true
if boneMap["inheritScale"] == false then boneData.inheritScale = false else boneData.inheritScale = true end
if boneMap["inheritRotation"] == false then boneData.inheritRotation = false else boneData.inheritRotation = true end
table.insert(skeletonData.bones, boneData)
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 slotData = SlotData.new(slotName, boneData)
local color = slotMap["color"]
if color then
slotData:setColor(
tonumber(color:sub(1, 2), 16),
tonumber(color:sub(3, 4), 16),
tonumber(color:sub(5, 6), 16),
tonumber(color:sub(7, 8), 16)
)
end
slotData.attachmentName = slotMap["attachment"]
table.insert(skeletonData.slots, slotData)
skeletonData.slotNameIndices[slotData.name] = #skeletonData.slots
end
end
-- Skins.
local map = root["skins"]
if map then
for skinName,skinMap in pairs(map) do
local skin = Skin.new(skinName)
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)
if attachment then
skin:addAttachment(slotIndex, attachmentName, attachment)
end
end
end
if skin.name == "default" then
skeletonData.defaultSkin = skin
else
table.insert(skeletonData.skins, skin)
end
end
end
-- Animations.
map = root["animations"]
if map then
for animationName,animationMap in pairs(map) do
readAnimation(animationName, animationMap, skeletonData)
end
end
return skeletonData
end
readAttachment = function (name, map, scale)
name = map["name"] or name
local attachment
local type = map["type"] or AttachmentLoader.ATTACHMENT_REGION
attachment = attachmentLoader:newAttachment(type, name)
if not attachment then return nil end
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
return attachment
end
readAnimation = function (name, map, skeletonData)
local timelines = {}
local duration = 0
local bonesMap = map["bones"]
if bonesMap then
for boneName,timelineMap in pairs(bonesMap) do
local boneIndex = skeletonData:findBoneIndex(boneName)
if boneIndex == -1 then error("Bone not found: " .. boneName) end
for timelineName,values in pairs(timelineMap) do
if timelineName == TIMELINE_ROTATE then
local timeline = Animation.RotateTimeline.new()
timeline.boneIndex = boneIndex
local keyframeIndex = 0
for i,valueMap in ipairs(values) do
local time = valueMap["time"]
timeline:setKeyframe(keyframeIndex, time, valueMap["angle"])
readCurve(timeline, keyframeIndex, valueMap)
keyframeIndex = keyframeIndex + 1
end
table.insert(timelines, timeline)
duration = math.max(duration, timeline:getDuration())
elseif timelineName == TIMELINE_TRANSLATE or timelineName == TIMELINE_SCALE then
local timeline
local timelineScale = 1
if timelineName == TIMELINE_SCALE then
timeline = Animation.ScaleTimeline.new()
else
timeline = Animation.TranslateTimeline.new()
timelineScale = self.scale
end
timeline.boneIndex = boneIndex
local keyframeIndex = 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:setKeyframe(keyframeIndex, time, x, y)
readCurve(timeline, keyframeIndex, valueMap)
keyframeIndex = keyframeIndex + 1
end
table.insert(timelines, timeline)
duration = math.max(duration, timeline:getDuration())
else
error("Invalid timeline type for a bone: " .. timelineName .. " (" .. boneName .. ")")
end
end
end
end
local slotsMap = map["slots"]
if slotsMap then
for slotName,timelineMap in pairs(slotsMap) do
local slotIndex = skeletonData.slotNameIndices[slotName]
for timelineName,values in pairs(timelineMap) do
if timelineName == TIMELINE_COLOR then
local timeline = Animation.ColorTimeline.new()
timeline.slotIndex = slotIndex
local keyframeIndex = 0
for i,valueMap in ipairs(values) do
local time = valueMap["time"]
local color = valueMap["color"]
timeline:setKeyframe(
keyframeIndex, time,
tonumber(color:sub(1, 2), 16),
tonumber(color:sub(3, 4), 16),
tonumber(color:sub(5, 6), 16),
tonumber(color:sub(7, 8), 16)
)
readCurve(timeline, keyframeIndex, valueMap)
keyframeIndex = keyframeIndex + 1
end
table.insert(timelines, timeline)
duration = math.max(duration, timeline:getDuration())
elseif timelineName == TIMELINE_ATTACHMENT then
local timeline = Animation.AttachmentTimeline.new()
timeline.slotName = slotName
local keyframeIndex = 0
for i,valueMap in ipairs(values) do
local time = valueMap["time"]
local attachmentName = valueMap["name"]
if not attachmentName then attachmentName = nil end
timeline:setKeyframe(keyframeIndex, time, attachmentName)
keyframeIndex = keyframeIndex + 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
table.insert(skeletonData.animations, Animation.new(name, timelines, duration))
end
readCurve = function (timeline, keyframeIndex, valueMap)
local curve = valueMap["curve"]
if not curve then return end
if curve == "stepped" then
timeline:setStepped(keyframeIndex)
else
timeline:setCurve(keyframeIndex, curve[1], curve[2], curve[3], curve[4])
end
end
return self
end
return SkeletonJson