mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-04 22:34:53 +08:00
The spine-lua API has been updated to be compatible with Spine version 3.4.02 (latest stable). The spine-lua API now supports path constraints, transform constraints, uses the new way we encode meshes etc. There are no API breaking changes, only API additions, such as PathConstraints and TransformConstraints as well as additional methods to Skeleton and similar classes. The internals of the spine-lua API have also been updated to follow Lua best performance practices by localizing heavily and using meta tables for "class methods". The spine-lua API now also loads texture atlases as exported by Spine. All that is required for a consumer is to supply an image loading function for their specific engine/framework. We provide implementations for spine-love and spine-corona. The spine-love API can now render all Spine attachment types, including meshes and linked meshes. The API has changed. Where previously a "class" Skeleton existed with a draw function, the new spine-love API introduces a new SkeletonRenderer. See the example on API usage. The spine-corona API can now also render all Spine attachment types. The API has not changed.
789 lines
28 KiB
Lua
789 lines
28 KiB
Lua
-------------------------------------------------------------------------------
|
|
-- Spine Runtimes Software License
|
|
-- Version 2.3
|
|
--
|
|
-- Copyright (c) 2013-2015, Esoteric Software
|
|
-- All rights reserved.
|
|
--
|
|
-- You are granted a perpetual, non-exclusive, non-sublicensable and
|
|
-- non-transferable license to use, install, execute and perform the Spine
|
|
-- Runtimes Software (the "Software") and derivative works solely for personal
|
|
-- or internal use. Without the written permission of Esoteric Software (see
|
|
-- Section 2 of the Spine Software License Agreement), 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 SOFTWARE 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 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 utils = require "spine-lua.utils"
|
|
|
|
local SkeletonJson = {}
|
|
function SkeletonJson.new (attachmentLoader)
|
|
if not attachmentLoader then attachmentLoader = AttachmentLoader.new() end
|
|
|
|
local self = {
|
|
attachmentLoader = attachmentLoader,
|
|
scale = 1,
|
|
linkedMeshes = {}
|
|
}
|
|
|
|
function self:readSkeletonDataFile (fileName, base)
|
|
return self:readSkeletonData(spine.utils.readFile(fileName, base))
|
|
end
|
|
|
|
local readAttachment
|
|
local readAnimation
|
|
local readCurve
|
|
local getArray
|
|
|
|
local getValue = function (map, name, default)
|
|
local value = map[name]
|
|
if value == nil then return default else return value end
|
|
end
|
|
|
|
function self:readSkeletonData (jsonText)
|
|
local scale = self.scale
|
|
local skeletonData = SkeletonData.new(self.attachmentLoader)
|
|
local root = spine.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.width = skeletonMap["width"]
|
|
skeletonData.height = skeletonMap["height"]
|
|
skeletonData.imagesPath = skeletonMap["images"]
|
|
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 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.inheritRotation = getValue(boneMap, "inheritRotation", true);
|
|
data.inheritScale = getValue(boneMap, "inheritScale", true);
|
|
|
|
table_insert(skeletonData.bones, data)
|
|
end
|
|
|
|
-- Slots.
|
|
if root["slots"] then
|
|
for i,slotMap in ipairs(root["slots"]) do
|
|
local index = i
|
|
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
|
|
|
|
data.attachmentName = getValue(slotMap, "attachment", nil)
|
|
data.blendMode = BlendMode[getValue(slotMap, "blend", "normal")]
|
|
|
|
table_insert(skeletonData.slots, data)
|
|
skeletonData.slotNameIndices[data.name] = #skeletonData.slots
|
|
end
|
|
end
|
|
|
|
-- IK constraints.
|
|
if root["ik"] then
|
|
for i,constraintMap in ipairs(root["ik"]) do
|
|
local data = IkConstraintData.new(constraintMap["name"])
|
|
|
|
for i,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
|
|
|
|
if constraintMap["bendPositive"] == false then data.bendDirection = -1 else data.bendDirection = 1 end
|
|
data.mix = getValue(constraintMap, "mix", 1)
|
|
|
|
table_insert(skeletonData.ikConstraints, data)
|
|
end
|
|
end
|
|
|
|
-- Transform constraints
|
|
if root["transform"] then
|
|
for i,constraintMap in ipairs(root["transform"]) do
|
|
data = TransformConstraintData.new(constraintMap.name)
|
|
|
|
for i,boneName in ipairs(constraintMap.bones) do
|
|
local bone = skeletonData:findBone(boneName)
|
|
if not bone then error("Transform constraint bone not found: " .. boneName, 2) 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: " .. boneName, 2) end
|
|
|
|
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.rotateMix = getValue(constraintMap, "rotateMix", 1);
|
|
data.translateMix = getValue(constraintMap, "translateMix", 1);
|
|
data.scaleMix = getValue(constraintMap, "scaleMix", 1);
|
|
data.shearMix = getValue(constraintMap, "shearMix", 1);
|
|
|
|
table_insert(skeletonData.transformConstraints, data)
|
|
end
|
|
end
|
|
|
|
-- Path constraints
|
|
if root["path"] then
|
|
for i,constraintMap in ipairs(root.path) do
|
|
local data = PathConstraintData.new(constraintMap.name);
|
|
|
|
for i,boneName in ipairs(constraintMap.bones) do
|
|
local bone = skeletonData:findBone(boneName)
|
|
if not bone then error("Path constraint bone not found: " .. boneName, 2) 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, 2) 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.rotateMix = getValue(constraintMap, "rotateMix", 1);
|
|
data.translateMix = getValue(constraintMap, "translateMix", 1);
|
|
|
|
table_insert(skeletonData.pathConstraints, data)
|
|
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(attachmentMap, skin, slotIndex, attachmentName)
|
|
if attachment then
|
|
skin:addAttachment(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 i, 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
|
|
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", nil)
|
|
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)
|
|
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 null 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
|
|
|
|
local parent = map.parent
|
|
if parent then
|
|
mesh.inheritDeform = getValue(map, "deform", true)
|
|
table_insert(self.linkedMeshes, {
|
|
mesh = mesh,
|
|
skin = getValue(map, skin, nil),
|
|
slotIndex = slotIndex,
|
|
parent = parent
|
|
})
|
|
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;
|
|
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 duration = 0
|
|
local scale = self.scale
|
|
|
|
-- Slot timelines
|
|
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(#values)
|
|
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(valueMap, timeline, frameIndex)
|
|
frameIndex = frameIndex + 1
|
|
end
|
|
table_insert(timelines, timeline)
|
|
duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.ColorTimeline.ENTRIES])
|
|
|
|
elseif timelineName == "attachment" then
|
|
local timeline = Animation.AttachmentTimeline.new(#values)
|
|
timeline.slotName = slotName
|
|
|
|
local frameIndex = 0
|
|
for i,valueMap in ipairs(values) do
|
|
local attachmentName = valueMap["name"]
|
|
timeline:setFrame(frameIndex, valueMap["time"], attachmentName)
|
|
frameIndex = frameIndex + 1
|
|
end
|
|
table_insert(timelines, timeline)
|
|
duration = math.max(duration, timeline.frames[timeline:getFrameCount() - 1])
|
|
|
|
else
|
|
error("Invalid frame type for a slot: " .. timelineName .. " (" .. slotName .. ")")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Bone timelines
|
|
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 == "rotate" then
|
|
local timeline = Animation.RotateTimeline.new(#values)
|
|
timeline.boneIndex = boneIndex
|
|
|
|
local frameIndex = 0
|
|
for i,valueMap in ipairs(values) do
|
|
timeline:setFrame(frameIndex, valueMap["time"], valueMap["angle"])
|
|
readCurve(valueMap, timeline, frameIndex)
|
|
frameIndex = frameIndex + 1
|
|
end
|
|
table_insert(timelines, timeline)
|
|
duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.RotateTimeline.ENTRIES])
|
|
|
|
elseif timelineName == "translate" or timelineName == "scale" or timelineName == "shear" then
|
|
local timeline
|
|
local timelineScale = 1
|
|
if timelineName == "scale" then
|
|
timeline = Animation.ScaleTimeline.new(#values)
|
|
elseif timelineName == "shear" then
|
|
timeline = Animation.ShearTimeline.new(#values)
|
|
else
|
|
timeline = Animation.TranslateTimeline.new(#values)
|
|
timelineScale = self.scale
|
|
end
|
|
timeline.boneIndex = boneIndex
|
|
|
|
local frameIndex = 0
|
|
for i,valueMap in ipairs(values) do
|
|
local x = (valueMap["x"] or 0) * timelineScale
|
|
local y = (valueMap["y"] or 0) * timelineScale
|
|
timeline:setFrame(frameIndex, valueMap["time"], x, y)
|
|
readCurve(valueMap, timeline, frameIndex)
|
|
frameIndex = frameIndex + 1
|
|
end
|
|
table_insert(timelines, timeline)
|
|
duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.TranslateTimeline.ENTRIES])
|
|
else
|
|
error("Invalid timeline type for a bone: " .. timelineName .. " (" .. boneName .. ")")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- IK timelines.
|
|
local ik = map["ik"]
|
|
if ik then
|
|
for ikConstraintName,values in pairs(ik) do
|
|
local ikConstraint = skeletonData:findIkConstraint(ikConstraintName)
|
|
local timeline = Animation.IkConstraintTimeline.new(#values)
|
|
for i,other in pairs(skeletonData.ikConstraints) do
|
|
if other == ikConstraint then
|
|
timeline.ikConstraintIndex = i
|
|
break
|
|
end
|
|
end
|
|
local frameIndex = 0
|
|
for i,valueMap in ipairs(values) do
|
|
local mix = 1
|
|
if valueMap["mix"] ~= nil then mix = valueMap["mix"] end
|
|
local bendPositive = 1
|
|
if valueMap["bendPositive"] == false then bendPositive = -1 end
|
|
timeline:setFrame(frameIndex, valueMap["time"], mix, bendPositive)
|
|
readCurve(valueMap, timeline, frameIndex)
|
|
frameIndex = frameIndex + 1
|
|
end
|
|
table_insert(timelines, timeline)
|
|
duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.IkConstraintTimeline.ENTRIES])
|
|
end
|
|
end
|
|
|
|
-- Transform constraint timelines.
|
|
local transform = map["transform"]
|
|
if transform then
|
|
for constraintName, values in pairs(transform) do
|
|
local constraint = skeletonData:findTransformConstraint(constraintName)
|
|
local timeline = Animation.TransformConstraintTimeline.new(#values)
|
|
for i,other in pairs(skeletonData.transformConstraints) do
|
|
if other == constraint then
|
|
timeline.transformConstraintIndex = i
|
|
break
|
|
end
|
|
end
|
|
local frameIndex = 0
|
|
for i,valueMap in ipairs(values) do
|
|
timeline:setFrame(frameIndex, valueMap.time, getValue(valueMap, "rotateMix", 1), getValue(valueMap, "translateMix", 1), getValue(valueMap, "scaleMix", 1), getValue(valueMap, "shearMix", 1))
|
|
readCurve(valueMap, timeline, frameIndex)
|
|
frameIndex = frameIndex + 1
|
|
end
|
|
table_insert(timelines, timeline)
|
|
duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.TransformConstraintTimeline.ENTRIES])
|
|
end
|
|
end
|
|
|
|
-- Path constraint timelines.
|
|
if map.paths then
|
|
for constraintName,constraintMap in pairs(map.paths) do
|
|
local index = skeletonData:findPathConstraintIndex(constraintName)
|
|
if index == -1 then error("Path constraint not found: " .. constraintName, 2) end
|
|
local data = skeletonData.pathConstraints[index]
|
|
for timelineName, timelineMap in pairs(constraintMap) do
|
|
if timelineName == "position" or timelineName == "spacing" then
|
|
local timeline = nil
|
|
local timelineScale = 1
|
|
if timelineName == "spacing" then
|
|
timeline = Animation.PathConstraintSpacingTimeline.new(#timelineMap)
|
|
if data.spacingMode == PathConstraintData.SpacingMode.length or data.spacingMode == PathConstraintData.SpacingMode.fixed then timelineScale = scale end
|
|
else
|
|
timeline = Animation.PathConstraintPositionTimeline.new(#timelineMap)
|
|
if data.positionMode == PathConstraintData.PositionMode.fixed then timelineScale = scale end
|
|
end
|
|
timeline.pathConstraintIndex = index
|
|
local frameIndex = 0
|
|
for i,valueMap in ipairs(timelineMap) do
|
|
timeline:setFrame(frameIndex, valueMap.time, getValue(valueMap, timelineName, 0) * timelineScale)
|
|
readCurve(valueMap, timeline, frameIndex)
|
|
frameIndex = frameIndex + 1
|
|
end
|
|
table_insert(timelines, timeline)
|
|
duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.PathConstraintPositionTimeline.ENTRIES])
|
|
elseif timelineName == "mix" then
|
|
local timeline = Animation.PathConstraintMixTimeline.new(#timelineMap)
|
|
timeline.pathConstraintIndex = index
|
|
local frameIndex = 0
|
|
for i,valueMap in ipairs(timelineMap) do
|
|
timeline:setFrame(frameIndex, valueMap.time, getValue(valueMap, "rotateMix", 1), getValue(valueMap, "translateMix", 1))
|
|
readCurve(valueMap, timeline, frameIndex)
|
|
frameIndex = frameIndex + 1
|
|
end
|
|
table_insert(timelines, timeline)
|
|
duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.PathConstraintMixTimeline.ENTRIES])
|
|
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, 2) end
|
|
for slotName,slotMap in pairs(deformMap) do
|
|
local slotIndex = skeletonData:findSlotIndex(slotName)
|
|
if slotIndex == -1 then error("Slot not found: " .. slotMap.name, 2) end
|
|
for timelineName,timelineMap in pairs(slotMap) do
|
|
local attachment = skin:getAttachment(slotIndex, timelineName)
|
|
if not attachment then error("Deform attachment not found: " .. timelineMap.name, 2) end
|
|
local weighted = attachment.bones ~= nil
|
|
local vertices = attachment.vertices;
|
|
local deformLength = #vertices
|
|
if weighted then deformLength = math.floor(#vertices / 3) * 2 end
|
|
|
|
local timeline = Animation.DeformTimeline.new(#timelineMap)
|
|
timeline.slotIndex = slotIndex
|
|
timeline.attachment = attachment
|
|
|
|
local frameIndex = 0
|
|
for i,valueMap in ipairs(timelineMap) do
|
|
local deform = nil
|
|
local verticesValue = getValue(valueMap, "vertices", nil)
|
|
if verticesValue == nil then
|
|
deform = vertices
|
|
if weighted then deform = utils.newNumberArray(deformLength) end
|
|
else
|
|
deform = utils.newNumberArray(deformLength)
|
|
local start = getValue(valueMap, "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(frameIndex, valueMap.time, deform)
|
|
readCurve(valueMap, timeline, frameIndex)
|
|
frameIndex = frameIndex + 1
|
|
end
|
|
table_insert(timelines, timeline)
|
|
duration = math.max(duration, timeline.frames[timeline:getFrameCount() - 1])
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Draworder timeline.
|
|
local drawOrderValues = map["drawOrder"]
|
|
if not drawOrderValues then drawOrderValues = map["draworder"] end
|
|
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.frames[timeline:getFrameCount() - 1])
|
|
end
|
|
|
|
-- Event timeline.
|
|
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(eventMap["time"], 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, event)
|
|
frameIndex = frameIndex + 1
|
|
end
|
|
table_insert(timelines, timeline)
|
|
duration = math.max(duration, timeline.frames[timeline:getFrameCount() - 1])
|
|
end
|
|
|
|
table_insert(skeletonData.animations, Animation.new(name, timelines, duration))
|
|
end
|
|
|
|
readCurve = function (map, timeline, frameIndex)
|
|
local curve = map["curve"]
|
|
if not curve then return end
|
|
if curve == "stepped" then
|
|
timeline:setStepped(frameIndex)
|
|
elseif #curve > 0 then
|
|
timeline:setCurve(frameIndex, curve[1], curve[2], curve[3], curve[4])
|
|
end
|
|
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
|
|
|
|
return self
|
|
end
|
|
return SkeletonJson
|