spine-runtimes/spine-lua/Animation.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

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