mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-18 04:51:47 +08:00
There are no longer separate animation files, they are now inside the skeleton file. This means there is just one file to manage, which is cleaner. Now that animations are stored in SkeletonData, they can be looked up by name which leads to cleaner runtime APIs. cocos2d and cocos2d-x runtimes got a cleaner ObjC/C++ API.
427 lines
13 KiB
Lua
427 lines
13 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 utils = require "spine.utils"
|
|
|
|
local Animation = {}
|
|
function Animation.new (name, timelines, duration)
|
|
if not timelines then error("timelines cannot be nil", 2) end
|
|
|
|
local self = {
|
|
name = name,
|
|
timelines = timelines,
|
|
duration = duration
|
|
}
|
|
|
|
function self:apply (skeleton, time, loop)
|
|
if not skeleton then error("skeleton cannot be nil.", 2) end
|
|
|
|
if loop and duration then time = time % duration end
|
|
|
|
for i,timeline in ipairs(self.timelines) do
|
|
timeline:apply(skeleton, time, 1)
|
|
end
|
|
end
|
|
|
|
function self:mix (skeleton, time, loop, alpha)
|
|
if not skeleton then error("skeleton cannot be nil.", 2) end
|
|
|
|
if loop and duration then time = time % duration end
|
|
|
|
for i,timeline in ipairs(self.timelines) do
|
|
timeline:apply(skeleton, time, alpha)
|
|
end
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
local function binarySearch (values, target, step)
|
|
local low = 0
|
|
local high = math.floor((#values + 1) / step - 2)
|
|
if high == 0 then return step end
|
|
local current = math.floor(high / 2)
|
|
while true do
|
|
if values[(current + 1) * step] <= target then
|
|
low = current + 1
|
|
else
|
|
high = current
|
|
end
|
|
if low == high then return (low + 1) * step end
|
|
current = math.floor((low + high) / 2)
|
|
end
|
|
end
|
|
|
|
local function linearSearch (values, target, step)
|
|
for i = 0, #values, step do
|
|
if (values[i] > target) then return i end
|
|
end
|
|
return -1
|
|
end
|
|
|
|
Animation.CurveTimeline = {}
|
|
function Animation.CurveTimeline.new ()
|
|
local LINEAR = 0
|
|
local STEPPED = -1
|
|
local BEZIER_SEGMENTS = 10
|
|
|
|
local self = {
|
|
curves = {}
|
|
}
|
|
|
|
function self:setLinear (keyframeIndex)
|
|
self.curves[keyframeIndex * 6] = LINEAR
|
|
end
|
|
|
|
function self:setStepped (keyframeIndex)
|
|
self.curves[keyframeIndex * 6] = STEPPED
|
|
end
|
|
|
|
function self:setCurve (keyframeIndex, cx1, cy1, cx2, cy2)
|
|
local subdiv_step = 1 / BEZIER_SEGMENTS
|
|
local subdiv_step2 = subdiv_step * subdiv_step
|
|
local subdiv_step3 = subdiv_step2 * subdiv_step
|
|
local pre1 = 3 * subdiv_step
|
|
local pre2 = 3 * subdiv_step2
|
|
local pre4 = 6 * subdiv_step2
|
|
local pre5 = 6 * subdiv_step3
|
|
local tmp1x = -cx1 * 2 + cx2
|
|
local tmp1y = -cy1 * 2 + cy2
|
|
local tmp2x = (cx1 - cx2) * 3 + 1
|
|
local tmp2y = (cy1 - cy2) * 3 + 1
|
|
local i = keyframeIndex * 6
|
|
local curves = self.curves
|
|
curves[i] = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv_step3
|
|
curves[i + 1] = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv_step3
|
|
curves[i + 2] = tmp1x * pre4 + tmp2x * pre5
|
|
curves[i + 3] = tmp1y * pre4 + tmp2y * pre5
|
|
curves[i + 4] = tmp2x * pre5
|
|
curves[i + 5] = tmp2y * pre5
|
|
end
|
|
|
|
function self:getCurvePercent (keyframeIndex, percent)
|
|
local curveIndex = keyframeIndex * 6
|
|
local curves = self.curves
|
|
local dfx = curves[curveIndex]
|
|
if not dfx then return percent end -- linear
|
|
if dfx == STEPPED then return 0 end
|
|
local dfy = curves[curveIndex + 1]
|
|
local ddfx = curves[curveIndex + 2]
|
|
local ddfy = curves[curveIndex + 3]
|
|
local dddfx = curves[curveIndex + 4]
|
|
local dddfy = curves[curveIndex + 5]
|
|
local x = dfx
|
|
local y = dfy
|
|
local i = BEZIER_SEGMENTS - 2
|
|
while true do
|
|
if x >= percent then
|
|
local lastX = x - dfx
|
|
local lastY = y - dfy
|
|
return lastY + (y - lastY) * (percent - lastX) / (x - lastX)
|
|
end
|
|
if i == 0 then break end
|
|
i = i - 1
|
|
dfx = dfx + ddfx
|
|
dfy = dfy + ddfy
|
|
ddfx = ddfx + dddfx
|
|
ddfy = ddfy + dddfy
|
|
x = x + dfx
|
|
y = y + dfy
|
|
end
|
|
return y + (1 - y) * (percent - x) / (1 - x) -- Last point is 1,1.
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
Animation.RotateTimeline = {}
|
|
function Animation.RotateTimeline.new ()
|
|
local LAST_FRAME_TIME = -2
|
|
local FRAME_VALUE = 1
|
|
|
|
local self = Animation.CurveTimeline.new()
|
|
self.frames = {}
|
|
self.boneIndex = -1
|
|
|
|
function self:getDuration ()
|
|
return self.frames[#self.frames - 1]
|
|
end
|
|
|
|
function self:getKeyframeCount ()
|
|
return (#self.frames + 1) / 2
|
|
end
|
|
|
|
function self:setKeyframe (keyframeIndex, time, value)
|
|
keyframeIndex = keyframeIndex * 2
|
|
self.frames[keyframeIndex] = time
|
|
self.frames[keyframeIndex + 1] = value
|
|
end
|
|
|
|
function self:apply (skeleton, time, alpha)
|
|
local frames = self.frames
|
|
if time < frames[0] then return end -- Time is before first frame.
|
|
|
|
local bone = skeleton.bones[self.boneIndex]
|
|
|
|
if time >= frames[#frames - 1] then -- Time is after last frame.
|
|
local amount = bone.data.rotation + frames[#frames] - bone.rotation
|
|
while amount > 180 do
|
|
amount = amount - 360
|
|
end
|
|
while amount < -180 do
|
|
amount = amount + 360
|
|
end
|
|
bone.rotation = bone.rotation + amount * alpha
|
|
return
|
|
end
|
|
|
|
-- Interpolate between the last frame and the current frame.
|
|
local frameIndex = binarySearch(frames, time, 2)
|
|
local lastFrameValue = frames[frameIndex - 1]
|
|
local frameTime = frames[frameIndex]
|
|
local percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime)
|
|
if percent < 0 then percent = 0 elseif percent > 1 then percent = 1 end
|
|
percent = self:getCurvePercent(frameIndex / 2 - 1, percent)
|
|
|
|
local amount = frames[frameIndex + FRAME_VALUE] - lastFrameValue
|
|
while amount > 180 do
|
|
amount = amount - 360
|
|
end
|
|
while amount < -180 do
|
|
amount = amount + 360
|
|
end
|
|
amount = bone.data.rotation + (lastFrameValue + amount * percent) - bone.rotation
|
|
while amount > 180 do
|
|
amount = amount - 360
|
|
end
|
|
while amount < -180 do
|
|
amount = amount + 360
|
|
end
|
|
bone.rotation = bone.rotation + amount * alpha
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
Animation.TranslateTimeline = {}
|
|
function Animation.TranslateTimeline.new ()
|
|
local LAST_FRAME_TIME = -3
|
|
local FRAME_X = 1
|
|
local FRAME_Y = 2
|
|
|
|
local self = Animation.CurveTimeline.new()
|
|
self.frames = {}
|
|
self.boneIndex = -1
|
|
|
|
function self:getDuration ()
|
|
return self.frames[#self.frames - 2]
|
|
end
|
|
|
|
function self:getKeyframeCount ()
|
|
return (#self.frames + 1) / 3
|
|
end
|
|
|
|
function self:setKeyframe (keyframeIndex, time, x, y)
|
|
keyframeIndex = keyframeIndex * 3
|
|
self.frames[keyframeIndex] = time
|
|
self.frames[keyframeIndex + 1] = x
|
|
self.frames[keyframeIndex + 2] = y
|
|
end
|
|
|
|
function self:apply (skeleton, time, alpha)
|
|
local frames = self.frames
|
|
if time < frames[0] then return end -- Time is before first frame.
|
|
|
|
local bone = skeleton.bones[self.boneIndex]
|
|
|
|
if time >= frames[#frames - 2] then -- Time is after last frame.
|
|
bone.x = bone.x + (bone.data.x + frames[#frames - 1] - bone.x) * alpha
|
|
bone.y = bone.y + (bone.data.y + frames[#frames] - bone.y) * alpha
|
|
return
|
|
end
|
|
|
|
-- Interpolate between the last frame and the current frame.
|
|
local frameIndex = binarySearch(frames, time, 3)
|
|
local lastFrameX = frames[frameIndex - 2]
|
|
local lastFrameY = frames[frameIndex - 1]
|
|
local frameTime = frames[frameIndex]
|
|
local percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime)
|
|
if percent < 0 then percent = 0 elseif percent > 1 then percent = 1 end
|
|
percent = self:getCurvePercent(frameIndex / 3 - 1, percent)
|
|
|
|
bone.x = bone.x + (bone.data.x + lastFrameX + (frames[frameIndex + FRAME_X] - lastFrameX) * percent - bone.x) * alpha
|
|
bone.y = bone.y + (bone.data.y + lastFrameY + (frames[frameIndex + FRAME_Y] - lastFrameY) * percent - bone.y) * alpha
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
Animation.ScaleTimeline = {}
|
|
function Animation.ScaleTimeline.new ()
|
|
local LAST_FRAME_TIME = -3
|
|
local FRAME_X = 1
|
|
local FRAME_Y = 2
|
|
|
|
local self = Animation.TranslateTimeline.new()
|
|
|
|
function self:apply (skeleton, time, alpha)
|
|
local frames = self.frames
|
|
if time < frames[0] then return end -- Time is before first frame.
|
|
|
|
local bone = skeleton.bones[self.boneIndex]
|
|
|
|
if time >= frames[#frames - 2] then -- Time is after last frame.
|
|
bone.scaleX = bone.scaleX + (bone.data.scaleX - 1 + frames[#frames - 1] - bone.scaleX) * alpha
|
|
bone.scaleY = bone.scaleY + (bone.data.scaleY - 1 + frames[#frames] - bone.scaleY) * alpha
|
|
return
|
|
end
|
|
|
|
-- Interpolate between the last frame and the current frame.
|
|
local frameIndex = binarySearch(frames, time, 3)
|
|
local lastFrameX = frames[frameIndex - 2]
|
|
local lastFrameY = frames[frameIndex - 1]
|
|
local frameTime = frames[frameIndex]
|
|
local percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime)
|
|
if percent < 0 then percent = 0 elseif percent > 1 then percent = 1 end
|
|
percent = self:getCurvePercent(frameIndex / 3 - 1, percent)
|
|
|
|
bone.scaleX = bone.scaleX + (bone.data.scaleX - 1 + lastFrameX + (frames[frameIndex + FRAME_X] - lastFrameX) * percent - bone.scaleX) * alpha
|
|
bone.scaleY = bone.scaleY + (bone.data.scaleY - 1 + lastFrameY + (frames[frameIndex + FRAME_Y] - lastFrameY) * percent - bone.scaleY) * alpha
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
Animation.ColorTimeline = {}
|
|
function Animation.ColorTimeline.new ()
|
|
local LAST_FRAME_TIME = -5
|
|
local FRAME_R = 1
|
|
local FRAME_G = 2
|
|
local FRAME_B = 3
|
|
local FRAME_A = 4
|
|
|
|
local self = Animation.CurveTimeline.new()
|
|
self.frames = {}
|
|
self.slotIndex = -1
|
|
|
|
function self:getDuration ()
|
|
return self.frames[#self.frames - 4]
|
|
end
|
|
|
|
function self:getKeyframeCount ()
|
|
return (#self.frames + 1) / 5
|
|
end
|
|
|
|
function self:setKeyframe (keyframeIndex, time, r, g, b, a)
|
|
keyframeIndex = keyframeIndex * 5
|
|
self.frames[keyframeIndex] = time
|
|
self.frames[keyframeIndex + 1] = r
|
|
self.frames[keyframeIndex + 2] = g
|
|
self.frames[keyframeIndex + 3] = b
|
|
self.frames[keyframeIndex + 4] = a
|
|
end
|
|
|
|
function self:apply (skeleton, time, alpha)
|
|
local frames = self.frames
|
|
if time < frames[0] then return end -- Time is before first frame.
|
|
|
|
local slot = skeleton.slots[self.slotIndex]
|
|
|
|
if time >= frames[#frames - 4] then -- Time is after last frame.
|
|
local r = frames[#frames - 3]
|
|
local g = frames[#frames - 2]
|
|
local b = frames[#frames - 1]
|
|
local a = frames[#frames]
|
|
slot:setColor(r, g, b, a)
|
|
return
|
|
end
|
|
|
|
-- Interpolate between the last frame and the current frame.
|
|
local frameIndex = binarySearch(frames, time, 5)
|
|
local lastFrameR = frames[frameIndex - 4]
|
|
local lastFrameG = frames[frameIndex - 3]
|
|
local lastFrameB = frames[frameIndex - 2]
|
|
local lastFrameA = frames[frameIndex - 1]
|
|
local frameTime = frames[frameIndex]
|
|
local percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime)
|
|
if percent < 0 then percent = 0 elseif percent > 255 then percent = 255 end
|
|
percent = self:getCurvePercent(frameIndex / 5 - 1, percent)
|
|
|
|
local r = lastFrameR + (frames[frameIndex + FRAME_R] - lastFrameR) * percent
|
|
local g = lastFrameG + (frames[frameIndex + FRAME_G] - lastFrameG) * percent
|
|
local b = lastFrameB + (frames[frameIndex + FRAME_B] - lastFrameB) * percent
|
|
local a = lastFrameA + (frames[frameIndex + FRAME_A] - lastFrameA) * percent
|
|
if alpha < 1 then
|
|
slot:setColor(slot.r + (r - color.r) * alpha, slot.g + (g - color.g) * alpha, slot.b + (b - color.b) * alpha, slot.a + (a - color.a) * alpha)
|
|
else
|
|
slot:setColor(r, g, b, a)
|
|
end
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
Animation.AttachmentTimeline = {}
|
|
function Animation.AttachmentTimeline.new ()
|
|
local self = Animation.CurveTimeline.new()
|
|
self.frames = {}
|
|
self.attachmentNames = {}
|
|
self.slotName = nil
|
|
|
|
function self:getDuration ()
|
|
return self.frames[#self.frames]
|
|
end
|
|
|
|
function self:getKeyframeCount ()
|
|
return #self.frames + 1
|
|
end
|
|
|
|
function self:setKeyframe (keyframeIndex, time, attachmentName)
|
|
self.frames[keyframeIndex] = time
|
|
self.attachmentNames[keyframeIndex] = attachmentName
|
|
end
|
|
|
|
function self:apply (skeleton, time, alpha)
|
|
local frames = self.frames
|
|
if time < frames[0] then return end -- Time is before first frame.
|
|
|
|
local frameIndex
|
|
if time >= frames[#frames] then -- Time is after last frame.
|
|
frameIndex = #frames
|
|
else
|
|
frameIndex = binarySearch(frames, time, 1) - 1
|
|
end
|
|
|
|
local attachmentName = self.attachmentNames[frameIndex]
|
|
local attachment
|
|
if attachmentName then attachment = skeleton:getAttachment(self.slotName, attachmentName) end
|
|
skeleton:findSlot(self.slotName):setAttachment(attachment)
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
return Animation
|