123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542 |
- -------------------------------------------------------------------------------
- -- Spine Runtimes Software License v2.5
- --
- -- Copyright (c) 2013-2016, 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 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 develop new applications using the Spine Runtimes or otherwise
- -- create derivative works or improvements of the Spine Runtimes 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, BUSINESS INTERRUPTION, OR LOSS OF
- -- USE, DATA, OR PROFITS) 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 math_min = math.min
- local math_max = math.max
- 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 = {},
- transformConstraints = {},
- pathConstraints = {},
- _updateCache = {},
- updateCacheReset = {},
- skin = nil,
- color = Color.newWith(1, 1, 1, 1),
- time = 0,
- scaleX = 1, scaleY = 1,
- x = 0, y = 0
- }
- setmetatable(self, Skeleton)
- for _,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 _,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 _, ikConstraintData in ipairs(data.ikConstraints) do
- table_insert(self.ikConstraints, IkConstraint.new(ikConstraintData, self))
- end
- for _, transformConstraintData in ipairs(data.transformConstraints) do
- table_insert(self.transformConstraints, TransformConstraint.new(transformConstraintData, self))
- end
- for _, 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
- self.updateCacheReset = {}
- local bones = self.bones
- for _, bone in ipairs(bones) do
- bone.sorted = false
- end
- local ikConstraints = self.ikConstraints
- local transformConstraints = self.transformConstraints
- local pathConstraints = self.pathConstraints
- local ikCount = #ikConstraints
- local transformCount = #transformConstraints
- local pathCount = #pathConstraints
- local constraintCount = ikCount + transformCount + pathCount
-
- local i = 0
- while i < constraintCount do
- local found = false
- local ii = 1
- while ii <= ikCount do
- local constraint = ikConstraints[ii]
- if constraint.data.order == i then
- self:sortIkConstraint(constraint)
- found = true
- break
- end
- ii = ii + 1
- end
-
- if not found then
- ii = 1
- while ii <= transformCount do
- local constraint = transformConstraints[ii]
- if constraint.data.order == i then
- self:sortTransformConstraint(constraint)
- found = true
- break
- end
- ii = ii + 1
- end
- end
-
- if not found then
- ii = 1
- while ii <= pathCount do
- local constraint = pathConstraints[ii]
- if constraint.data.order == i then
- self:sortPathConstraint(constraint)
- break
- end
- ii = ii + 1
- end
- end
-
- i = i + 1
- end
-
- for _, bone in ipairs(self.bones) do
- self:sortBone(bone)
- end
- end
- function Skeleton:sortIkConstraint (constraint)
- local target = constraint.target
- self:sortBone(target)
-
- local constrained = constraint.bones
- local parent = constrained[1]
- self:sortBone(parent)
-
- if #constrained > 1 then
- local child = constrained[#constrained]
- local contains = false
- for _, updatable in ipairs(self._updateCache) do
- if updatable == child then
- contains = true
- break
- end
- end
- if not contains then table_insert(self.updateCacheReset, child) end
- end
-
- table_insert(self._updateCache, constraint)
-
- self:sortReset(parent.children)
- constrained[#constrained].sorted = true
- end
- function Skeleton:sortPathConstraint(constraint)
- 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 not (self.data.defaultSkin == self.skin) then
- self:sortPathConstraintAttachment(self.data.defaultSkin, slotIndex, slotBone)
- end
- for _,skin in ipairs(self.data.skins) do
- self:sortPathConstraintAttachment(skin, slotIndex, slotBone)
- end
-
- local attachment = slot.attachment
- if attachment and attachment.type == AttachmentType.path then self:sortPathConstraintAttachmentWith(attachment, slotBone) end
-
- local constrained = constraint.bones
- for _,bone in ipairs(constrained) do
- self:sortBone(bone)
- end
-
- table_insert(self._updateCache, constraint)
-
- for _,bone in ipairs(constrained) do
- self:sortReset(bone.children)
- end
-
- for _,bone in ipairs(constrained) do
- bone.sorted = true
- end
- end
- function Skeleton:sortTransformConstraint(constraint)
- self:sortBone(constraint.target)
-
- local constrained = constraint.bones
- if constraint.data.local_ then
- for _,bone in ipairs(constrained) do
- local child = constrained[#constrained]
- local contains = false
- self:sortBone(child.parent)
- for _,updatable in ipairs(self._updateCache) do
- if updatable == child then
- contains = true
- break
- end
- end
- if not contains then table_insert(self.updateCacheReset, child) end
- end
- else
- for _,bone in ipairs(constrained) do
- self:sortBone(bone)
- end
- end
-
- table_insert(self._updateCache, constraint)
-
- for _,bone in ipairs(constrained) do
- self:sortReset(bone.children)
- end
-
- for _,bone in ipairs(constrained) do
- bone.sorted = true
- end
- end
- function Skeleton:sortPathConstraintAttachment(skin, slotIndex, slotBone)
- local attachments = skin.attachments[slotIndex]
- if not attachments then return end
- for _,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 _, 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 updateCacheReset = self.updateCacheReset
- for _,bone in ipairs(updateCacheReset) do
- bone.ax = bone.x
- bone.ay = bone.y
- bone.arotation = bone.rotation
- bone.ascaleX = bone.scaleX
- bone.ascaleY = bone.scaleY
- bone.ashearX = bone.shearX
- bone.ashearY = bone.shearY
- bone.appliedValid = true
- end
-
- local updateCache = self._updateCache
- for _, updatable in ipairs(updateCache) do
- updatable:update()
- end
- end
- function Skeleton:setToSetupPose ()
- self:setBonesToSetupPose()
- self:setSlotsToSetupPose()
- end
- function Skeleton:setBonesToSetupPose ()
- for _,bone in ipairs(self.bones) do
- bone:setToSetupPose()
- end
- for _,ikConstraint in ipairs(self.ikConstraints) do
- ikConstraint.mix = ikConstraint.data.mix
- ikConstraint.bendDirection = ikConstraint.data.bendDirection
- ikConstraint.compress = ikConstraint.data.compress
- ikConstraint.stretch = ikConstraint.data.stretch
- end
- local transformConstraints = self.transformConstraints
- for _, 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 _, 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 _,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 ikConstraints = self.ikConstraints
- for _, 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 _, 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 _, 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 _, slot in ipairs(drawOrder) do
- local vertices = {}
- local attachment = slot.attachment
- if attachment then
- if attachment.type == AttachmentType.region then
- attachment:computeWorldVertices(slot.bone, vertices, 0, 2)
- elseif attachment.type == AttachmentType.mesh then
- attachment:computeWorldVertices(slot, 0, attachment.worldVerticesLength, vertices, 0, 2)
- end
- end
- if #vertices > 0 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 + 2
- 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.color.r = r
- self.color.g = g
- self.color.b = b
- self.color.a = a
- end
- return Skeleton
|