mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2025-12-20 17:26:01 +08:00
278 lines
9.6 KiB
Lua
278 lines
9.6 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"]
|
|
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
|
|
|
|
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
|