mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2025-12-21 01:36:02 +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.
926 lines
30 KiB
Lua
926 lines
30 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.
|
|
-------------------------------------------------------------------------------
|
|
|
|
-- FIXME
|
|
-- All the indexing in this file is zero based. We use zlen()
|
|
-- instead of the # operator. Initialization of number arrays
|
|
-- is performed via utils.newNumberArrayZero. This needs
|
|
-- to be rewritten using one-based indexing for better performance
|
|
|
|
local utils = require "spine-lua.utils"
|
|
local AttachmentType = require "spine-lua.attachments.AttachmentType"
|
|
|
|
local function zlen(array)
|
|
return #array + 1
|
|
end
|
|
|
|
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, lastTime, time, loop, events)
|
|
if not skeleton then error("skeleton cannot be nil.", 2) end
|
|
|
|
if loop and duration > 0 then
|
|
time = time % self.duration
|
|
if lastTime > 0 then lastTime = lastTime % self.duration end
|
|
end
|
|
|
|
for i,timeline in ipairs(self.timelines) do
|
|
timeline:apply(skeleton, lastTime, time, events, 1)
|
|
end
|
|
end
|
|
|
|
function self:mix (skeleton, lastTime, time, loop, events, alpha)
|
|
if not skeleton then error("skeleton cannot be nil.", 2) end
|
|
|
|
if loop and duration > 0 then
|
|
time = time % self.duration
|
|
if lastTime > 0 then lastTime = lastTime % self.duration end
|
|
end
|
|
|
|
for i,timeline in ipairs(self.timelines) do
|
|
timeline:apply(skeleton, lastTime, time, events, alpha)
|
|
end
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
local function binarySearch (values, target, step)
|
|
local low = 0
|
|
local high = math.floor(zlen(values) / 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 binarySearch1 (values, target)
|
|
local low = 0
|
|
local high = math.floor(zlen(values) - 2)
|
|
if high == 0 then return 1 end
|
|
local current = math.floor(high / 2)
|
|
while true do
|
|
if values[current + 1] <= target then
|
|
low = current + 1
|
|
else
|
|
high = current
|
|
end
|
|
if low == high then return low + 1 end
|
|
current = math.floor((low + high) / 2)
|
|
end
|
|
end
|
|
|
|
local function linearSearch (values, target, step)
|
|
local i = 0
|
|
local last = zlen(values) - step
|
|
while i <= last do
|
|
if (values[i] > target) then return i end
|
|
i = i + step
|
|
end
|
|
return -1
|
|
end
|
|
|
|
Animation.CurveTimeline = {}
|
|
function Animation.CurveTimeline.new (frameCount)
|
|
local LINEAR = 0
|
|
local STEPPED = 1
|
|
local BEZIER = 2
|
|
local BEZIER_SIZE = 10 * 2 - 1
|
|
|
|
local self = {
|
|
curves = utils.newNumberArrayZero((frameCount - 1) * BEZIER_SIZE) -- type, x, y, ...
|
|
}
|
|
|
|
function self:getFrameCount ()
|
|
return math.floor(zlen(self.curves) / BEZIER_SIZE) + 1
|
|
end
|
|
|
|
function self:setStepped (frameIndex)
|
|
self.curves[frameIndex * BEZIER_SIZE] = STEPPED
|
|
end
|
|
|
|
function self:getCurveType (frameIndex)
|
|
local index = frameIndex * BEZIER_SIZE
|
|
if index == zlen(self.curves) then return LINEAR end
|
|
local type = self.curves[index]
|
|
if type == LINEAR then return LINEAR end
|
|
if type == STEPPED then return STEPPED end
|
|
return BEZIER
|
|
end
|
|
|
|
function self:setCurve (frameIndex, cx1, cy1, cx2, cy2)
|
|
local tmpx = (-cx1 * 2 + cx2) * 0.03
|
|
local tmpy = (-cy1 * 2 + cy2) * 0.03
|
|
local dddfx = ((cx1 - cx2) * 3 + 1) * 0.006
|
|
local dddfy = ((cy1 - cy2) * 3 + 1) * 0.006
|
|
local ddfx = tmpx * 2 + dddfx
|
|
local ddfy = tmpy * 2 + dddfy
|
|
local dfx = cx1 * 0.3 + tmpx + dddfx * 0.16666667
|
|
local dfy = cy1 * 0.3 + tmpy + dddfy * 0.16666667
|
|
|
|
local i = frameIndex * BEZIER_SIZE
|
|
local curves = self.curves
|
|
curves[i] = BEZIER
|
|
i = i + 1
|
|
|
|
local x = dfx
|
|
local y = dfy
|
|
local n = i + BEZIER_SIZE - 1
|
|
while i < n do
|
|
curves[i] = x
|
|
curves[i + 1] = y
|
|
dfx = dfx + ddfx
|
|
dfy = dfy + ddfy
|
|
ddfx = ddfx + dddfx
|
|
ddfy = ddfy + dddfy
|
|
x = x + dfx
|
|
y = y + dfy
|
|
i = i + 2
|
|
end
|
|
end
|
|
|
|
function self:getCurvePercent (frameIndex, percent)
|
|
percent = utils.clamp(percent, 0, 1)
|
|
local curves = self.curves
|
|
local i = frameIndex * BEZIER_SIZE
|
|
local type = curves[i]
|
|
if type == LINEAR then return percent end
|
|
if type == STEPPED then return 0 end
|
|
i = i + 1
|
|
local x
|
|
local n = i + BEZIER_SIZE - 1
|
|
local start = i
|
|
while i < n do
|
|
x = curves[i]
|
|
if x >= percent then
|
|
local prevX, prevY
|
|
if i == start then
|
|
prevX = 0
|
|
prevY = 0
|
|
else
|
|
prevX = curves[i - 2]
|
|
prevY = curves[i - 1]
|
|
end
|
|
return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX)
|
|
end
|
|
i = i + 2
|
|
end
|
|
local y = curves[i - 1]
|
|
return y + (1 - y) * (percent - x) / (1 - x) -- Last point is 1,1.
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
Animation.RotateTimeline = {}
|
|
Animation.RotateTimeline.ENTRIES = 2
|
|
function Animation.RotateTimeline.new (frameCount)
|
|
local ENTRIES = Animation.RotateTimeline.ENTRIES
|
|
local PREV_TIME = -2
|
|
local PREV_ROTATION = -1
|
|
local ROTATION = 1
|
|
|
|
local self = Animation.CurveTimeline.new(frameCount)
|
|
self.boneIndex = -1
|
|
self.frames = utils.newNumberArrayZero(frameCount * 2)
|
|
|
|
function self:setFrame (frameIndex, time, degrees)
|
|
frameIndex = frameIndex * 2
|
|
self.frames[frameIndex] = time
|
|
self.frames[frameIndex + ROTATION] = degrees
|
|
end
|
|
|
|
function self:apply (skeleton, lastTime, time, firedEvents, 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[zlen(frames) - ENTRIES] then -- Time is after last frame.
|
|
local amount = bone.data.rotation + frames[zlen(frames) + PREV_ROTATION] - 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 frame = binarySearch(frames, time, ENTRIES)
|
|
local prevRotation = frames[frame + PREV_ROTATION]
|
|
local frameTime = frames[frame]
|
|
local percent = self:getCurvePercent((math.floor(frame / 2)) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
|
|
|
|
local amount = frames[frame + ROTATION] - prevRotation
|
|
while amount > 180 do
|
|
amount = amount - 360
|
|
end
|
|
while amount < -180 do
|
|
amount = amount + 360
|
|
end
|
|
amount = bone.data.rotation + (prevRotation + 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 = {}
|
|
Animation.TranslateTimeline.ENTRIES = 3
|
|
function Animation.TranslateTimeline.new (frameCount)
|
|
local ENTRIES = Animation.TranslateTimeline.ENTRIES
|
|
local PREV_TIME = -3
|
|
local PREV_X = -2
|
|
local PREV_Y = -1
|
|
local X = 1
|
|
local Y = 2
|
|
|
|
local self = Animation.CurveTimeline.new(frameCount)
|
|
self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
|
|
self.boneIndex = -1
|
|
|
|
function self:setFrame (frameIndex, time, x, y)
|
|
frameIndex = frameIndex * ENTRIES
|
|
self.frames[frameIndex] = time
|
|
self.frames[frameIndex + X] = x
|
|
self.frames[frameIndex + Y] = y
|
|
end
|
|
|
|
function self:apply (skeleton, lastTime, time, firedEvents, 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[zlen(frames) - ENTRIES] then -- Time is after last frame.
|
|
bone.x = bone.x + (bone.data.x + frames[zlen(frames) + PREV_X] - bone.x) * alpha
|
|
bone.y = bone.y + (bone.data.y + frames[zlen(frames) + PREV_Y] - bone.y) * alpha
|
|
return
|
|
end
|
|
|
|
-- Interpolate between the last frame and the current frame.
|
|
local frame = binarySearch(frames, time, ENTRIES)
|
|
local prevX = frames[frame + PREV_X]
|
|
local prevY = frames[frame + PREV_Y]
|
|
local frameTime = frames[frame]
|
|
local percent = self:getCurvePercent(math.floor(frame / ENTRIES) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
|
|
|
|
bone.x = bone.x + (bone.data.x + prevX + (frames[frame + X] - prevX) * percent - bone.x) * alpha
|
|
bone.y = bone.y + (bone.data.y + prevY + (frames[frame + Y] - prevY) * percent - bone.y) * alpha
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
Animation.ScaleTimeline = {}
|
|
Animation.ScaleTimeline.ENTRIES = Animation.TranslateTimeline.ENTRIES
|
|
function Animation.ScaleTimeline.new (frameCount)
|
|
local ENTRIES = Animation.ScaleTimeline.ENTRIES
|
|
local PREV_TIME = -3
|
|
local PREV_X = -2
|
|
local PREV_Y = -1
|
|
local X = 1
|
|
local Y = 2
|
|
|
|
local self = Animation.TranslateTimeline.new(frameCount)
|
|
|
|
function self:apply (skeleton, lastTime, time, firedEvents, 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[zlen(frames) - ENTRIES] then -- Time is after last frame.
|
|
bone.scaleX = bone.scaleX + (bone.data.scaleX * frames[zlen(frames) + PREV_X] - bone.scaleX) * alpha
|
|
bone.scaleY = bone.scaleY + (bone.data.scaleY * frames[zlen(frames) + PREV_Y] - bone.scaleY) * alpha
|
|
return
|
|
end
|
|
|
|
-- Interpolate between the last frame and the current frame.
|
|
local frame = binarySearch(frames, time, ENTRIES)
|
|
local prevX = frames[frame + PREV_X]
|
|
local prevY = frames[frame + PREV_Y]
|
|
local frameTime = frames[frame]
|
|
local percent = self:getCurvePercent(math.floor(frame / ENTRIES) - 1,
|
|
1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
|
|
|
|
bone.scaleX = bone.scaleX + (bone.data.scaleX * (prevX + (frames[frame + X] - prevX) * percent) - bone.scaleX) * alpha
|
|
bone.scaleY = bone.scaleY + (bone.data.scaleY * (prevY + (frames[frame + Y] - prevY) * percent) - bone.scaleY) * alpha
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
Animation.ShearTimeline = {}
|
|
Animation.ShearTimeline.ENTRIES = Animation.TranslateTimeline.ENTRIES
|
|
function Animation.ShearTimeline.new (frameCount)
|
|
local ENTRIES = Animation.ShearTimeline.ENTRIES
|
|
local PREV_TIME = -3
|
|
local PREV_X = -2
|
|
local PREV_Y = -1
|
|
local X = 1
|
|
local Y = 2
|
|
|
|
local self = Animation.TranslateTimeline.new(frameCount)
|
|
|
|
function self:apply (skeleton, lastTime, time, firedEvents, 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[zlen(frames) - ENTRIES] then -- Time is after last frame.
|
|
bone.shearX = bone.shearX + (bone.data.shearX * frames[zlen(frames) + PREV_X] - bone.shearX) * alpha
|
|
bone.shearY = bone.shearY + (bone.data.shearY * frames[zlen(frames) + PREV_Y] - bone.shearY) * alpha
|
|
return
|
|
end
|
|
|
|
-- Interpolate between the last frame and the current frame.
|
|
local frame = binarySearch(frames, time, ENTRIES)
|
|
local prevX = frames[frame + PREV_X]
|
|
local prevY = frames[frame + PREV_Y]
|
|
local frameTime = frames[frame]
|
|
local percent = self:getCurvePercent(math.floor(frame / ENTRIES) - 1,
|
|
1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
|
|
|
|
bone.shearX = bone.shearX + (bone.data.shearX + (prevX + (frames[frame + X] - prevX) * percent) - bone.shearX) * alpha
|
|
bone.shearY = bone.shearY + (bone.data.shearY + (prevY + (frames[frame + Y] - prevY) * percent) - bone.shearY) * alpha
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
Animation.ColorTimeline = {}
|
|
Animation.ColorTimeline.ENTRIES = 5
|
|
function Animation.ColorTimeline.new (frameCount)
|
|
local ENTRIES = Animation.ColorTimeline.ENTRIES
|
|
local PREV_TIME = -5
|
|
local PREV_R = -4
|
|
local PREV_G = -3
|
|
local PREV_B = -2
|
|
local PREV_A = -1
|
|
local R = 1
|
|
local G = 2
|
|
local B = 3
|
|
local A = 4
|
|
|
|
local self = Animation.CurveTimeline.new(frameCount)
|
|
self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
|
|
self.slotIndex = -1
|
|
|
|
function self:setFrame (frameIndex, time, r, g, b, a)
|
|
frameIndex = frameIndex * ENTRIES
|
|
self.frames[frameIndex] = time
|
|
self.frames[frameIndex + R] = r
|
|
self.frames[frameIndex + G] = g
|
|
self.frames[frameIndex + B] = b
|
|
self.frames[frameIndex + A] = a
|
|
end
|
|
|
|
function self:apply (skeleton, lastTime, time, firedEvents, alpha)
|
|
local frames = self.frames
|
|
if time < frames[0] then return end -- Time is before first frame.
|
|
|
|
local r, g, b, a
|
|
if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
|
|
local i = zlen(frames)
|
|
r = frames[i + PREV_R]
|
|
g = frames[i + PREV_G]
|
|
b = frames[i + PREV_B]
|
|
a = frames[i + PREV_A]
|
|
else
|
|
-- Interpolate between the last frame and the current frame.
|
|
local frame = binarySearch(frames, time, ENTRIES)
|
|
r = frames[frame + PREV_R]
|
|
g = frames[frame + PREV_G]
|
|
b = frames[frame + PREV_B]
|
|
a = frames[frame + PREV_A]
|
|
local frameTime = frames[frame]
|
|
local percent = self:getCurvePercent(math.floor(frame / ENTRIES) - 1,
|
|
1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
|
|
|
|
r = r + (frames[frame + R] - r) * percent
|
|
g = g + (frames[frame + G] - g) * percent
|
|
b = b + (frames[frame + B] - b) * percent
|
|
a = a + (frames[frame + A] - a) * percent
|
|
end
|
|
local color = skeleton.slots[self.slotIndex].color
|
|
if alpha < 1 then
|
|
color:add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha)
|
|
else
|
|
color:set(r, g, b, a)
|
|
end
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
Animation.AttachmentTimeline = {}
|
|
function Animation.AttachmentTimeline.new (frameCount)
|
|
local self = {
|
|
frames = utils.newNumberArrayZero(frameCount), -- time, ...
|
|
attachmentNames = {},
|
|
slotName = nil
|
|
}
|
|
|
|
function self:getFrameCount ()
|
|
return zlen(self.frames)
|
|
end
|
|
|
|
function self:setFrame (frameIndex, time, attachmentName)
|
|
self.frames[frameIndex] = time
|
|
self.attachmentNames[frameIndex] = attachmentName
|
|
end
|
|
|
|
function self:apply (skeleton, lastTime, time, firedEvents, alpha)
|
|
local frames = self.frames
|
|
if time < frames[0] then return end
|
|
|
|
local frameIndex = 0
|
|
if time >= frames[zlen(frames) - 1] then
|
|
frameIndex = zlen(frames) - 1
|
|
else
|
|
frameIndex = binarySearch(frames, time, 1) - 1
|
|
end
|
|
|
|
local attachmentName = self.attachmentNames[frameIndex]
|
|
local slot = skeleton.slotsByName[self.slotName]
|
|
if attachmentName then
|
|
if not slot.attachment then
|
|
slot:setAttachment(skeleton:getAttachment(self.slotName, attachmentName))
|
|
elseif slot.attachment.name ~= attachmentName then
|
|
slot:setAttachment(skeleton:getAttachment(self.slotName, attachmentName))
|
|
end
|
|
else
|
|
slot:setAttachment(nil)
|
|
end
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
Animation.EventTimeline = {}
|
|
function Animation.EventTimeline.new (frameCount)
|
|
local self = {
|
|
frames = utils.newNumberArrayZero(frameCount),
|
|
events = {}
|
|
}
|
|
|
|
function self:getFrameCount ()
|
|
return zlen(self.frames)
|
|
end
|
|
|
|
function self:setFrame (frameIndex, event)
|
|
self.frames[frameIndex] = event.time
|
|
self.events[frameIndex] = event
|
|
end
|
|
|
|
-- Fires events for frames > lastTime and <= time.
|
|
function self:apply (skeleton, lastTime, time, firedEvents, alpha)
|
|
if not firedEvents then return end
|
|
|
|
local frames = self.frames
|
|
local frameCount = zlen(frames)
|
|
|
|
if lastTime > time then -- Fire events after last time for looped animations.
|
|
self:apply(skeleton, lastTime, 999999, firedEvents, alpha)
|
|
lastTime = -1
|
|
elseif lastTime >= frames[frameCount - 1] then -- Last time is after last frame.
|
|
return
|
|
end
|
|
if time < frames[0] then return end -- Time is before first frame.
|
|
|
|
local frame
|
|
if lastTime < frames[0] then
|
|
frame = 0
|
|
else
|
|
frame = binarySearch1(frames, lastTime)
|
|
local frame = frames[frame]
|
|
while frame > 0 do -- Fire multiple events with the same frame.
|
|
if frames[frame - 1] ~= frame then break end
|
|
frame = frame - 1
|
|
end
|
|
end
|
|
local events = self.events
|
|
while frame < frameCount and time >= frames[frame] do
|
|
table.insert(firedEvents, events[frame])
|
|
frame = frame + 1
|
|
end
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
Animation.DrawOrderTimeline = {}
|
|
function Animation.DrawOrderTimeline.new (frameCount)
|
|
local self = {
|
|
frames = utils.newNumberArrayZero(frameCount),
|
|
drawOrders = {}
|
|
}
|
|
|
|
function self:getFrameCount ()
|
|
return zlen(self.frames)
|
|
end
|
|
|
|
function self:setFrame (frameIndex, time, drawOrder)
|
|
self.frames[frameIndex] = time
|
|
self.drawOrders[frameIndex] = drawOrder
|
|
end
|
|
|
|
function self:apply (skeleton, lastTime, time, firedEvents, alpha)
|
|
local frames = self.frames
|
|
if time < frames[0] then return end -- Time is before first frame.
|
|
|
|
local frame
|
|
if time >= frames[zlen(frames) - 1] then -- Time is after last frame.
|
|
frame = zlen(frames) - 1
|
|
else
|
|
frame = binarySearch1(frames, time) - 1
|
|
end
|
|
|
|
local drawOrder = skeleton.drawOrder
|
|
local slots = skeleton.slots
|
|
local drawOrderToSetupIndex = self.drawOrders[frame]
|
|
if not drawOrderToSetupIndex then
|
|
for i,slot in ipairs(slots) do
|
|
drawOrder[i] = slots[i]
|
|
end
|
|
else
|
|
for i,setupIndex in ipairs(drawOrderToSetupIndex) do
|
|
drawOrder[i] = skeleton.slots[setupIndex]
|
|
end
|
|
end
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
Animation.DeformTimeline = {}
|
|
function Animation.DeformTimeline.new (frameCount)
|
|
|
|
local self = Animation.CurveTimeline.new(frameCount)
|
|
self.frames = utils.newNumberArrayZero(frameCount)
|
|
self.frameVertices = utils.newNumberArrayZero(frameCount)
|
|
self.slotIndex = -1
|
|
self.attachment = nil
|
|
|
|
function self:setFrame (frameIndex, time, vertices)
|
|
self.frames[frameIndex] = time
|
|
self.frameVertices[frameIndex] = vertices
|
|
end
|
|
|
|
function self:apply (skeleton, lastTime, time, firedEvents, alpha)
|
|
local slot = skeleton.slots[self.slotIndex]
|
|
local slotAttachment = slot.attachment
|
|
if not slotAttachment then return end
|
|
if not (slotAttachment.type == AttachmentType.mesh or slotAttachment.type == AttachmentType.linkedmesh or slotAttachment.type == AttachmentType.path) then return end
|
|
if not slotAttachment:applyDeform(self.attachment) then return end
|
|
|
|
local frames = self.frames
|
|
if time < frames[0] then return end -- Time is before first frame.
|
|
|
|
local frameVertices = self.frameVertices
|
|
local vertexCount = #(frameVertices[0])
|
|
|
|
local verticesArray = slot.attachmentVertices
|
|
if (#verticesArray ~= vertexCount) then alpha = 1 end -- Don't mix from uninitialized slot vertices.
|
|
local vertices = utils.setArraySize(verticesArray, vertexCount)
|
|
|
|
if time >= frames[zlen(frames) - 1] then
|
|
local lastVertices = frameVertices[zlen(frames) - 1]
|
|
if alpha < 1 then
|
|
local i = 1
|
|
while i <= vertexCount do
|
|
vertices[i] = vertices[i] + (lastVertices[i] - vertices[i]) * alpha
|
|
i = i + 1
|
|
end
|
|
else
|
|
local i = 1
|
|
while i <= vertexCount do
|
|
vertices[i] = lastVertices[i]
|
|
i = i + 1
|
|
end
|
|
end
|
|
return;
|
|
end
|
|
|
|
-- Interpolate between the previous frame and the current frame.
|
|
local frame = binarySearch(frames, time, 1)
|
|
local prevVertices = frameVertices[frame - 1]
|
|
local nextVertices = frameVertices[frame]
|
|
local frameTime = frames[frame]
|
|
local percent = self:getCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime))
|
|
|
|
if alpha < 1 then
|
|
local i = 1
|
|
while i <= vertexCount do
|
|
local prev = prevVertices[i]
|
|
vertices[i] = vertices[i] + (prev + (nextVertices[i] - prev) * percent - vertices[i]) * alpha
|
|
i = i + 1
|
|
end
|
|
else
|
|
local i = 1
|
|
while i <= vertexCount do
|
|
local prev = prevVertices[i]
|
|
vertices[i] = prev + (nextVertices[i] - prev) * percent
|
|
i = i + 1
|
|
end
|
|
end
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
Animation.IkConstraintTimeline = {}
|
|
Animation.IkConstraintTimeline.ENTRIES = 3
|
|
function Animation.IkConstraintTimeline.new (frameCount)
|
|
local ENTRIES = Animation.IkConstraintTimeline.ENTRIES
|
|
local PREV_TIME = -3
|
|
local PREV_MIX = -2
|
|
local PREV_BEND_DIRECTION = -1
|
|
local MIX = 1
|
|
local BEND_DIRECTION = 2
|
|
|
|
local self = Animation.CurveTimeline.new(frameCount)
|
|
self.frames = utils.newNumberArrayZero(frameCount * ENTRIES) -- time, mix, bendDirection, ...
|
|
self.ikConstraintIndex = -1
|
|
|
|
function self:setFrame (frameIndex, time, mix, bendDirection)
|
|
frameIndex = frameIndex * ENTRIES
|
|
self.frames[frameIndex] = time
|
|
self.frames[frameIndex + MIX] = mix
|
|
self.frames[frameIndex + BEND_DIRECTION] = bendDirection
|
|
end
|
|
|
|
function self:apply (skeleton, lastTime, time, firedEvents, alpha)
|
|
local frames = self.frames
|
|
if time < frames[0] then return end -- Time is before first frame.
|
|
|
|
local constraint = skeleton.ikConstraints[self.ikConstraintIndex]
|
|
|
|
if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
|
|
constraint.mix = constraint.mix + (frames[zlen(frames) + PREV_MIX] - constraint.mix) * alpha
|
|
constraint.bendDirection = frames[zlen(frames) + PREV_BEND_DIRECTION]
|
|
return
|
|
end
|
|
|
|
-- Interpolate between the previous frame and the current frame.
|
|
local frame = binarySearch(frames, time, ENTRIES)
|
|
local mix = frames[frame + PREV_MIX]
|
|
local frameTime = frames[frame]
|
|
local percent = self:getCurvePercent(math.floor(frame / ENTRIES) - 1,
|
|
1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
|
|
|
|
constraint.mix = constraint.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha
|
|
constraint.bendDirection = math.floor(frames[frame + PREV_BEND_DIRECTION])
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
Animation.TransformConstraintTimeline = {}
|
|
Animation.TransformConstraintTimeline.ENTRIES = 5
|
|
function Animation.TransformConstraintTimeline.new (frameCount)
|
|
local ENTRIES = Animation.TransformConstraintTimeline.ENTRIES
|
|
local PREV_TIME = -5
|
|
local PREV_ROTATE = -4
|
|
local PREV_TRANSLATE = -3
|
|
local PREV_SCALE = -2
|
|
local PREV_SHEAR = -1
|
|
local ROTATE = 1
|
|
local TRANSLATE = 2
|
|
local SCALE = 3
|
|
local SHEAR = 4
|
|
|
|
local self = Animation.CurveTimeline.new(frameCount)
|
|
self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
|
|
self.transformConstraintIndex = -1
|
|
|
|
function self:setFrame (frameIndex, time, rotateMix, translateMix, scaleMix, shearMix)
|
|
frameIndex = frameIndex * ENTRIES
|
|
self.frames[frameIndex] = time
|
|
self.frames[frameIndex + ROTATE] = rotateMix
|
|
self.frames[frameIndex + TRANSLATE] = translateMix
|
|
self.frames[frameIndex + SCALE] = scaleMix
|
|
self.frames[frameIndex + SHEAR] = shearMix
|
|
end
|
|
|
|
function self:apply (skeleton, lastTime, time, firedEvents, alpha)
|
|
local frames = self.frames
|
|
if time < frames[0] then return end -- Time is before first frame.
|
|
|
|
local constraint = skeleton.transformConstraints[self.transformConstraintIndex]
|
|
|
|
if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
|
|
local i = zlen(frames)
|
|
constraint.rotateMix = constraintMix.rotateMix + (frames[i + PREV_ROTATE] - constraint.rotateMix) * alpha
|
|
constraint.translateMix = constraintMix.translateMix + (frames[i + PREV_TRANSLATE] - constraint.translateMix) * alpha
|
|
constraint.scaleMix = constraintMix.scaleMix + (frames[i + PREV_SCALE] - constraint.scaleMix) * alpha
|
|
constraint.shearMix = constraintMix.shearMix + (frames[i + PREV_SHEAR] - constraint.shearMix) * alpha
|
|
return
|
|
end
|
|
|
|
-- Interpolate between the last frame and the current frame.
|
|
local frame = binarySearch(frames, time, ENTRIES)
|
|
local frameTime = frames[frame]
|
|
local percent = self:getCurvePercent(math.floor(frame / ENTRIES) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
|
|
|
|
local rotate = frames[frame + PREV_ROTATE]
|
|
local translate = frames[frame + PREV_TRANSLATE]
|
|
local scale = frames[frame + PREV_SCALE]
|
|
local shear = frames[frame + PREV_SHEAR]
|
|
constraint.rotateMix = constraint.rotateMix + (rotate + (frames[frame + ROTATE] - rotate) * percent - constraint.rotateMix) * alpha
|
|
constraint.translateMix = constraint.translateMix + (translate + (frames[frame + TRANSLATE] - translate) * percent - constraint.translateMix) * alpha
|
|
constraint.scaleMix = constraint.scaleMix + (scale + (frames[frame + SCALE] - scale) * percent - constraint.scaleMix) * alpha
|
|
constraint.shearMix = constraint.shearMix + (shear + (frames[frame + SHEAR] - shear) * percent - constraint.shearMix) * alpha
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
Animation.PathConstraintPositionTimeline = {}
|
|
Animation.PathConstraintPositionTimeline.ENTRIES = 2
|
|
function Animation.PathConstraintPositionTimeline.new (frameCount)
|
|
local ENTRIES = Animation.PathConstraintPositionTimeline.ENTRIES
|
|
local PREV_TIME = -2
|
|
local PREV_VALUE = -1
|
|
local VALUE = 1
|
|
|
|
local self = Animation.CurveTimeline.new(frameCount)
|
|
self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
|
|
self.pathConstraintIndex = -1
|
|
|
|
function self:setFrame (frameIndex, time, value)
|
|
frameIndex = frameIndex * ENTRIES
|
|
self.frames[frameIndex] = time
|
|
self.frames[frameIndex + VALUE] = value
|
|
end
|
|
|
|
function self:apply (skeleton, lastTime, time, firedEvents, alpha)
|
|
local frames = self.frames
|
|
if (time < frames[0]) then return end -- Time is before first frame.
|
|
|
|
local constraint = skeleton.pathConstraints[self.pathConstraintIndex]
|
|
|
|
if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
|
|
local i = zlen(frames)
|
|
constraint.position = constraint.position + (frames[i + PREV_VALUE] - constraint.position) * alpha
|
|
return
|
|
end
|
|
|
|
-- Interpolate between the previous frame and the current frame.
|
|
local frame = binarySearch(frames, time, ENTRIES)
|
|
local position = frames[frame + PREV_VALUE]
|
|
local frameTime = frames[frame]
|
|
local percent = self:getCurvePercent(math.floor(frame / ENTRIES) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
|
|
|
|
constraint.position = constraint.position + (position + (frames[frame + VALUE] - position) * percent - constraint.position) * alpha
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
Animation.PathConstraintSpacingTimeline = {}
|
|
Animation.PathConstraintSpacingTimeline.ENTRIES = 2
|
|
function Animation.PathConstraintSpacingTimeline.new (frameCount)
|
|
local ENTRIES = Animation.PathConstraintSpacingTimeline.ENTRIES
|
|
local PREV_TIME = -2
|
|
local PREV_VALUE = -1
|
|
local VALUE = 1
|
|
|
|
local self = Animation.CurveTimeline.new(frameCount)
|
|
self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
|
|
self.pathConstraintIndex = -1
|
|
|
|
function self:setFrame (frameIndex, time, value)
|
|
frameIndex = frameIndex * ENTRIES
|
|
self.frames[frameIndex] = time
|
|
self.frames[frameIndex + VALUE] = value
|
|
end
|
|
|
|
function self:apply (skeleton, lastTime, time, firedEvents, alpha)
|
|
local frames = self.frames
|
|
if (time < frames[0]) then return end -- Time is before first frame.
|
|
|
|
local constraint = skeleton.pathConstraints[self.pathConstraintIndex]
|
|
|
|
if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
|
|
local i = zlen(frames)
|
|
constraint.spacing = constraint.spacing + (frames[i + PREV_VALUE] - constraint.spacing) * alpha
|
|
return
|
|
end
|
|
|
|
-- Interpolate between the previous frame and the current frame.
|
|
local frame = binarySearch(frames, time, ENTRIES)
|
|
local spacing = frames[frame + PREV_VALUE]
|
|
local frameTime = frames[frame]
|
|
local percent = self:getCurvePercent(math.floor(frame / ENTRIES) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
|
|
|
|
constraint.spacing = constraint.spacing + (spacing + (frames[frame + VALUE] - spacing) * percent - constraint.spacing) * alpha
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
Animation.PathConstraintMixTimeline = {}
|
|
Animation.PathConstraintMixTimeline.ENTRIES = 3
|
|
function Animation.PathConstraintMixTimeline.new (frameCount)
|
|
local ENTRIES = Animation.PathConstraintMixTimeline.ENTRIES
|
|
local PREV_TIME = -3
|
|
local PREV_ROTATE = -2
|
|
local PREV_TRANSLATE = -1
|
|
local ROTATE = 1
|
|
local TRANSLATE = 2
|
|
|
|
local self = Animation.CurveTimeline.new(frameCount)
|
|
self.frames = utils.newNumberArrayZero(frameCount * ENTRIES)
|
|
self.pathConstraintIndex = -1
|
|
|
|
function self:setFrame (frameIndex, time, rotateMix, translateMix)
|
|
frameIndex = frameIndex * ENTRIES
|
|
self.frames[frameIndex] = time
|
|
self.frames[frameIndex + ROTATE] = rotateMix
|
|
self.frames[frameIndex + TRANSLATE] = translateMix
|
|
end
|
|
|
|
function self:apply (skeleton, lastTime, time, firedEvents, alpha)
|
|
local frames = self.frames
|
|
if (time < frames[0]) then return end -- Time is before first frame.
|
|
|
|
local constraint = skeleton.pathConstraints[self.pathConstraintIndex]
|
|
|
|
if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame.
|
|
local i = zlen(frames)
|
|
constraint.rotateMix = constraint.rotateMix + (frames[i + PREV_ROTATE] - constraint.rotateMix) * alpha
|
|
constraint.translateMix = constraint.translateMix + (frames[i + PREV_TRANSLATE] - constraint.translateMix) * alpha
|
|
return
|
|
end
|
|
|
|
-- Interpolate between the previous frame and the current frame.
|
|
local frame = binarySearch(frames, time, ENTRIES)
|
|
local rotate = frames[frame + PREV_ROTATE]
|
|
local translate = frames[frame + PREV_TRANSLATE]
|
|
local frameTime = frames[frame]
|
|
local percent = self:getCurvePercent(math.floor(frame / ENTRIES) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
|
|
|
|
constraint.rotateMix = constraint.rotateMix + (rotate + (frames[frame + ROTATE] - rotate) * percent - constraint.rotateMix) * alpha
|
|
constraint.translateMix = constraint.translateMix + (translate + (frames[frame + TRANSLATE] - translate) * percent - constraint.translateMix) * alpha
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
return Animation
|