mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2025-12-21 01:36:02 +08:00
376 lines
13 KiB
Lua
Executable File
376 lines
13 KiB
Lua
Executable File
-------------------------------------------------------------------------------
|
|
-- Spine Runtimes Software License
|
|
-- Version 2
|
|
--
|
|
-- 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, 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 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 EventData = require "spine-lua.EventData"
|
|
local Event = require "spine-lua.Event"
|
|
local AttachmentType = require "spine-lua.AttachmentType"
|
|
|
|
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)
|
|
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) / 255,
|
|
tonumber(color:sub(3, 4), 16) / 255,
|
|
tonumber(color:sub(5, 6), 16) / 255,
|
|
tonumber(color:sub(7, 8), 16) / 255
|
|
)
|
|
end
|
|
|
|
slotData.attachmentName = slotMap["attachment"]
|
|
slotData.additiveBlending = slotMap["additive"]
|
|
|
|
table.insert(skeletonData.slots, slotData)
|
|
skeletonData.slotNameIndices[slotData.name] = #skeletonData.slots
|
|
end
|
|
end
|
|
|
|
-- Skins.
|
|
if root["skins"] then
|
|
for skinName,skinMap in pairs(root["skins"]) 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
|
|
|
|
-- Events.
|
|
if root["events"] then
|
|
for eventName,eventMap in pairs(root["events"]) do
|
|
local eventData = EventData.new(eventName)
|
|
eventData.intValue = eventMap["int"] or 0
|
|
eventData.floatValue = eventMap["float"] or 0
|
|
eventData.stringValue = eventMap["string"]
|
|
table.insert(skeletonData.events, eventData)
|
|
end
|
|
end
|
|
|
|
-- Animations.
|
|
if root["animations"] then
|
|
for animationName,animationMap in pairs(root["animations"]) do
|
|
readAnimation(animationName, animationMap, skeletonData)
|
|
end
|
|
end
|
|
|
|
return skeletonData
|
|
end
|
|
|
|
readAttachment = function (name, map, scale)
|
|
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
|
|
|
|
if type == AttachmentType.region then
|
|
attachment.x = (map["x"] or 0) * scale
|
|
attachment.y = (map["y"] or 0) * scale
|
|
attachment.scaleX = (map["scaleX"] or 1)
|
|
attachment.scaleY = (map["scaleY"] or 1)
|
|
attachment.rotation = (map["rotation"] or 0)
|
|
attachment.width = map["width"] * scale
|
|
attachment.height = map["height"] * scale
|
|
elseif type == AttachmentType.boundingbox then
|
|
local vertices = map["vertices"]
|
|
for i,point in ipairs(vertices) do
|
|
table.insert(attachment.vertices, vertices[i] * scale)
|
|
end
|
|
end
|
|
|
|
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:setFrame(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:setFrame(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: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 == TIMELINE_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
|
|
error("Invalid frame type for a slot: " .. timelineName .. " (" .. slotName .. ")")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local events = map["events"]
|
|
if events then
|
|
local timeline = Animation.EventTimeline.new(#events)
|
|
local frameIndex = 0
|
|
for i,eventMap in ipairs(events) do
|
|
local eventData = skeletonData:findEvent(eventMap["name"])
|
|
if not eventData then error("Event not found: " + eventMap["name"]) end
|
|
local event = Event.new(eventData)
|
|
event.intValue = eventMap["int"] or eventData.intValue
|
|
event.floatValue = eventMap["float"] or eventData.floatValue
|
|
event.stringValue = eventMap["string"] or eventData.stringValue
|
|
timeline:setFrame(frameIndex, eventMap["time"], event)
|
|
frameIndex = frameIndex + 1
|
|
end
|
|
table.insert(timelines, timeline)
|
|
duration = math.max(duration, timeline:getDuration())
|
|
end
|
|
|
|
local drawOrderValues = map["draworder"]
|
|
if drawOrderValues then
|
|
local timeline = Animation.DrawOrderTimeline.new(#drawOrderValues)
|
|
local slotCount = #skeletonData.slots
|
|
local frameIndex = 0
|
|
for i,drawOrderMap in ipairs(drawOrderValues) do
|
|
local drawOrder = nil
|
|
local offsets = drawOrderMap["offsets"]
|
|
if offsets then
|
|
drawOrder = {}
|
|
local unchanged = {}
|
|
local originalIndex = 1
|
|
local unchangedIndex = 1
|
|
for ii,offsetMap in ipairs(offsets) do
|
|
local slotIndex = skeletonData:findSlotIndex(offsetMap["slot"])
|
|
if slotIndex == -1 then error("Slot not found: " + offsetMap["slot"]) end
|
|
-- 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(frameIndex, drawOrderMap["time"], drawOrder)
|
|
frameIndex = frameIndex + 1
|
|
end
|
|
table.insert(timelines, timeline)
|
|
duration = math.max(duration, timeline:getDuration())
|
|
end
|
|
|
|
table.insert(skeletonData.animations, Animation.new(name, timelines, duration))
|
|
end
|
|
|
|
readCurve = function (timeline, frameIndex, valueMap)
|
|
local curve = valueMap["curve"]
|
|
if not curve then return end
|
|
if curve == "stepped" then
|
|
timeline:setStepped(frameIndex)
|
|
else
|
|
timeline:setCurve(frameIndex, curve[1], curve[2], curve[3], curve[4])
|
|
end
|
|
end
|
|
|
|
return self
|
|
end
|
|
return SkeletonJson
|