spine-runtimes/spine-lua/Skeleton.lua
Mario Zechner 5b1814cff3 spine-lua, spine-love, spine-corona update to 3.4.02 (#722)
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.
2016-10-11 16:33:25 +02:00

477 lines
14 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 Bone = require "spine-lua.Bone"
local Slot = require "spine-lua.Slot"
local IkConstraint = require "spine-lua.IkConstraint"
local PathConstraint = require "spine-lua.PathConstraint"
local TransformConstraint = require "spine-lua.TransformConstraint"
local AttachmentLoader = require "spine-lua.AttachmentLoader"
local AttachmentType = require "spine-lua.attachments.AttachmentType"
local Color = require "spine-lua.Color"
local setmetatable = setmetatable
local ipairs = ipairs
local table_insert = table.insert
local Skeleton = {}
Skeleton.__index = Skeleton
function Skeleton.new (data)
if not data then error("data cannot be nil", 2) end
local self = {
data = data,
bones = {},
slots = {},
slotsByName = {},
drawOrder = {},
ikConstraints = {}, ikConstaintsSorted = {},
transformConstraints = {},
pathConstraints = {},
_updateCache = {},
skin = nil,
color = Color.newWith(1, 1, 1, 1),
time = 0,
flipX = false, flipY = false,
x = 0, y = 0
}
setmetatable(self, Skeleton)
for i,boneData in ipairs(data.bones) do
local bone = nil
if boneData.parent == nil then
bone = Bone.new(boneData, self, nil)
else
local parent = self.bones[boneData.parent.index]
bone = Bone.new(boneData, self, parent)
table_insert(parent.children, bone)
end
table_insert(self.bones, bone)
end
for i,slotData in ipairs(data.slots) do
local bone = self.bones[slotData.boneData.index]
local slot = Slot.new(slotData, bone)
table_insert(self.slots, slot)
self.slotsByName[slot.data.name] = slot
table_insert(self.drawOrder, slot)
end
for i,ikConstraintData in ipairs(data.ikConstraints) do
table_insert(self.ikConstraints, IkConstraint.new(ikConstraintData, self))
end
for i, transformConstraintData in ipairs(data.transformConstraints) do
table_insert(self.transformConstraints, TransformConstraint.new(transformConstraintData, self))
end
for i, pathConstraintData in ipairs(data.pathConstraints) do
table_insert(self.pathConstraints, PathConstraint.new(pathConstraintData, self))
end
self:updateCache()
return self
end
-- Caches information about bones and IK constraints. Must be called if bones or IK constraints are added or removed.
function Skeleton:updateCache ()
local updateCache = {}
self._updateCache = updateCache
local bones = self.bones
for i, bone in ipairs(bones) do
bone.sorted = false
end
local ikConstraints = {}
self.ikConstraintsSorted = ikConstraints
for i, constraint in ipairs(self.ikConstraints) do
table_insert(ikConstraints, constraint)
end
local level = 0
for i, ik in ipairs(ikConstraints) do
local bone = ik.bones[1].parent
level = 0
while bone do
bone = bone.parent
level = level + 1
end
ik.level = level
end
local i = 1
local ikCount = #ikConstraints
while i < ikCount do
local ik = ikConstraints[i + 1]
local level = ik.level
local ii = i - 1
while ii >= 0 do
local other = ikConstraints[ii + 1]
if other.level < level then break end
ikConstraints[ii + 1 + 1] = other
ii = ii - 1
end
ikConstraints[ii + 1 + 1] = ik
i = i + 1
end
for i, constraint in ipairs(ikConstraints) do
local target = constraint.target
self:sortBone(target)
local constrained = constraint.bones
local parent = constrained[1]
self:sortBone(parent)
table_insert(updateCache, constraint)
self:sortReset(parent.children)
constrained[#constrained].sorted = true
end
-- path constraints
local pathConstraints = self.pathConstraints
for i,constraint in ipairs(pathConstraints) do
local slot = constraint.target
local slotIndex = slot.data.index
local slotBone = slot.bone
if self.skin then self:sortPathConstraintAttachment(self.skin, slotIndex, slotBone) end
if self.data.defaultSkin and self.data.defaultSkin ~= self.skin then self:sortPathConstraintAttachment(self.data.defaultSkin, slotIndex, slotBone) end
for i,skin in ipairs(self.data.skins) do
self:sortPathConstraintAttachment(skin, slotIndex, slotBone)
end
local attachment = slot.attachment
if attachment.type == AttachmentType.path then self:sortPathConstraintAttachmentWith(attachment, slotBone) end
local constrained = constraint.bones
for i,c in ipairs(constrained) do
self:sortBone(c)
end
table_insert(updateCache, constraint)
for i,c in ipairs(constrained) do
self:sortReset(c.children)
end
for i,c in ipairs(constrained) do
c.sorted = true
end
end
-- transform constraints
local transformConstraints = self.transformConstraints
for i, constraint in ipairs(transformConstraints) do
self:sortBone(constraint.target)
local constrained = constraint.bones
for i,c in ipairs(constrained) do
self:sortBone(c)
end
table_insert(updateCache, constraint)
for i,c in ipairs(constrained) do
self:sortReset(c.children)
end
for i,c in ipairs(constrained) do
c.sorted = true
end
end
for i, bone in ipairs(self.bones) do
self:sortBone(bone)
end
end
function Skeleton:sortPathConstraintAttachment(skin, slotIndex, slotBone)
local attachments = skin.attachments[slotIndex]
if not attachments then return end
for key,attachment in pairs(attachments) do
self:sortPathConstraintAttachmentWith(attachment, slotBone)
end
end
function Skeleton:sortPathConstraintAttachmentWith(attachment, slotBone)
if attachment.type ~= AttachmentType.path then return end
local pathBones = attachment.bones
if not pathBones then
self:sortBone(slotBone)
else
local bones = self.bones
local i = 0
local n = #pathBones
while i < n do
local boneCount = pathBones[i + 1]
i = i + 1
local nn = i + boneCount
while i < nn do
self:sortBone(bones[pathBones[i + 1]])
i = i + 1
end
end
end
end
function Skeleton:sortBone(bone)
if bone.sorted then return end
local parent = bone.parent
if parent then self:sortBone(parent) end
bone.sorted = true
table_insert(self._updateCache, bone)
end
function Skeleton:sortReset(bones)
for i, bone in ipairs(bones) do
if bone.sorted then self:sortReset(bone.children) end
bone.sorted = false
end
end
-- Updates the world transform for each bone and applies IK constraints.
function Skeleton:updateWorldTransform ()
local updateCache = self._updateCache
for i, updatable in ipairs(updateCache) do
updatable:update()
end
end
function Skeleton:setToSetupPose ()
self:setBonesToSetupPose()
self:setSlotsToSetupPose()
end
function Skeleton:setBonesToSetupPose ()
for i,bone in ipairs(self.bones) do
bone:setToSetupPose()
end
for i,ikConstraint in ipairs(self.ikConstraints) do
ikConstraint.bendDirection = ikConstraint.data.bendDirection
ikConstraint.mix = ikConstraint.data.mix
end
local transformConstraints = self.transformConstraints
for i, constraint in ipairs(transformConstraints) do
local data = constraint.data
constraint.rotateMix = data.rotateMix
constraint.translateMix = data.translateMix
constraint.scaleMix = data.scaleMix
constraint.shearMix = data.shearMix
end
local pathConstraints = self.pathConstraints
for i, constraint in ipairs(pathConstraints) do
local data = constraint.data
constraint.position = data.position
constraint.spacing = data.spacing
constraint.rotateMix = data.rotateMix
constraint.translateMix = data.translateMix
end
end
function Skeleton:setSlotsToSetupPose ()
for i,slot in ipairs(self.slots) do
self.drawOrder[i] = slot
slot:setToSetupPose()
end
end
function Skeleton:getRootBone ()
return self.bones[1]
end
function Skeleton:findBone (boneName)
if not boneName then error("boneName cannot be nil.", 2) end
for i,bone in ipairs(self.bones) do
if bone.data.name == boneName then return bone end
end
return nil
end
function Skeleton:findBoneIndex(boneName)
if not boneName then error("boneName cannot be nil.", 2) end
for i,bone in ipairs(self.bones) do
if bone.data.name == boneName then return i end
end
return -1
end
function Skeleton:findSlot (slotName)
if not slotName then error("slotName cannot be nil.", 2) end
return self.slotsByName[slotName]
end
function Skeleton:findSlotIndex(slotName)
if not slotName then error("slotName cannot be nil.", 2) end
for i, slot in ipairs(self.slots) do
if slot.data.name == slotName then return i end
end
return -1
end
-- Sets the skin used to look up attachments before looking in the {@link SkeletonData#getDefaultSkin() default skin}.
-- Attachments from the new skin are attached if the corresponding attachment from the old skin was attached. If there was
-- no old skin, each slot's setup mode attachment is attached from the new skin.
function Skeleton:setSkin (skinName)
local skin = self.data:findSkin(skinName)
if not skin then error("Skin not found: " .. skinName, 2) end
self:setSkinByReference(skin)
end
function Skeleton:setSkinByReference(newSkin)
if newSkin then
if self.skin then
newSkin:attachAll(self, self.skin)
else
local slots = self.slots
for i, slot in ipairs(slots) do
local name = slot.data.attachmentName
if name then
local attachment = newSkin:getAttachment(i, name)
if attachment then
slot:setAttachment(attachment)
end
end
end
end
end
self.skin = newSkin
end
function Skeleton:getAttachment (slotName, attachmentName)
return self:getAttachmentByIndex(self.data.slotNameIndices[slotName], attachmentName)
end
function Skeleton:getAttachmentByIndex (slotIndex, attachmentName)
if self.skin then
local attachment = self.skin:getAttachment(slotIndex, attachmentName)
if attachment then return attachment end
end
if self.data.defaultSkin then
return self.data.defaultSkin:getAttachment(slotIndex, attachmentName)
end
return nil
end
function Skeleton:setAttachment (slotName, attachmentName)
if not slotName then error("slotName cannot be nil.", 2) end
for i,slot in ipairs(self.slots) do
if slot.data.name == slotName then
local attachment = nil
if attachmentName then
attachment = self:getAttachmentByIndex(i, attachmentName)
if not attachment then error("Attachment not found: " .. attachmentName .. ", for slot: " .. slotName, 2) end
end
slot:setAttachment(attachment)
return
end
end
error("Slot not found: " .. slotName, 2)
end
function Skeleton:findIkConstraint(constraintName)
if not constraintName then error("constraintName cannot be null.", 2) end
local ikConstaints = self.ikConstraints
for i, ikConstraint in ipairs(ikConstraints) do
if ikConstraint.data.name == constraintName then return ikConstraint end
end
return nil
end
function Skeleton:findTransformConstraint(constraintName)
if not constraintName then error("constraintName cannot be null.", 2) end
local transformConstraints = self.transformConstraints
for i, transformConstraint in ipairs(transformConstraints) do
if transformConstraint.data.name == constraintName then return transformConstraint end
end
return nil
end
function Skeleton:findPathConstraint(constraintName)
if not constraintName then error("constraintName cannot be null.", 2) end
local pathConstraints = self.pathConstraints
for i, pathConstraint in ipairs(pathConstraints) do
if pathConstraint.data.name == constraintName then return pathConstraint end
end
return nil
end
function Skeleton:getBounds(offset, size)
if not offset then error("offset cannot be null.", 2) end
if not size then error("size cannot be null.", 2) end
local drawOrder = self.drawOrder;
local minX = 99999999
local minY = 99999999
local maxX = -99999999
local maxY = -99999999
for i, slot in ipairs(drawOrder) do
local vertices = nil
local attachment = slot.attachment
if attachment.type == AttachmentType.region or attachment.type == AttachmentType.mesh then
vertices = attachment:updateWorldVertices(slot, false);
end
if vertices then
local nn = #vertices
local ii = 1
while ii <= nn do
local x = vertices[ii]
local y = vertices[ii + 1]
minX = math_min(minX, x)
minY = math_min(minY, y)
maxX = math_max(maxX, x)
maxY = math_max(maxY, y)
ii = ii + 8
end
end
end
offset[1] = minX
offset[2] = minY
size[1] = maxX - minX
size[2] = maxY - minY
end
function Skeleton:update (delta)
self.time = self.time + delta
end
function Skeleton:setColor (r, g, b, a)
self.r = r
self.g = g
self.b = b
self.a = a
end
return Skeleton