diff --git a/spine-lua/spine-lua/Animation.lua b/spine-lua/spine-lua/Animation.lua index ae6c74548..b9a127cc8 100644 --- a/spine-lua/spine-lua/Animation.lua +++ b/spine-lua/spine-lua/Animation.lua @@ -1,1612 +1,1611 @@ -------------------------------------------------------------------------------- --- Spine Runtimes License Agreement --- Last updated January 1, 2020. Replaces all prior versions. --- --- Copyright (c) 2013-2020, Esoteric Software LLC --- --- Integration of the Spine Runtimes into software or otherwise creating --- derivative works of the Spine Runtimes is permitted under the terms and --- conditions of Section 2 of the Spine Editor License Agreement: --- http://esotericsoftware.com/spine-editor-license --- --- Otherwise, it is permitted to integrate the Spine Runtimes into software --- or otherwise create derivative works of the Spine Runtimes (collectively, --- "Products"), provided that each user of the Products must obtain their own --- Spine Editor license and redistribution of the Products in any form must --- include this license and copyright notice. --- --- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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 --- THE SPINE RUNTIMES, 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 math_floor = math.floor -local math_abs = math.abs -local math_signum = utils.signum - -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, - timelineIds = {}, - duration = duration - } - - for i,timeline in ipairs(self.timelines) do - self.timelineIds[timeline:getPropertyId()] = true - end - - function self:hasTimeline(id) - return self.timelineIds[id] == true - end - - function self:apply (skeleton, lastTime, time, loop, events, alpha, blend, direction) - 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, blend, direction) - 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 -Animation.binarySearch = binarySearch - -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.MixBlend = { - setup = 0, - first = 1, - replace = 2, - add = 3 -} -local MixBlend = Animation.MixBlend - -Animation.MixDirection = { - _in = 0, out = 1 -} -local MixDirection = Animation.MixDirection - -Animation.TimelineType = { - rotate = 0, translate = 1, scale = 2, shear = 3, - attachment = 4, color = 5, deform = 6, - event = 7, drawOrder = 8, - ikConstraint = 9, transformConstraint = 10, - pathConstraintPosition = 11, pathConstraintSpacing = 12, pathConstraintMix = 13, - twoColor = 14 -} -local TimelineType = Animation.TimelineType -local SHL_24 = 16777216 -local SHL_27 = 134217728 - -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 -Animation.RotateTimeline.PREV_TIME = -2 -Animation.RotateTimeline.PREV_ROTATION = -1 -Animation.RotateTimeline.ROTATION = 1 -function Animation.RotateTimeline.new (frameCount) - local ENTRIES = Animation.RotateTimeline.ENTRIES - local PREV_TIME = Animation.RotateTimeline.PREV_TIME - local PREV_ROTATION = Animation.RotateTimeline.PREV_ROTATION - local ROTATION = Animation.RotateTimeline.ROTATION - - local self = Animation.CurveTimeline.new(frameCount) - self.boneIndex = -1 - self.frames = utils.newNumberArrayZero(frameCount * 2) - self.type = TimelineType.rotate - - function self:getPropertyId () - return TimelineType.rotate * SHL_24 + self.boneIndex - end - - 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, blend, direction) - local frames = self.frames - - local bone = skeleton.bones[self.boneIndex] - if not bone.active then return end - if time < frames[0] then - if blend == MixBlend.setup then - bone.rotation = bone.data.rotation - elseif blend == MixBlend.first then - local r = bone.data.rotation - bone.rotation - bone.rotation = bone.rotation + (r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360) * alpha - end - return - end - - if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame. - local r = frames[zlen(frames) + PREV_ROTATION] - if blend == MixBlend.setup then - bone.rotation = bone.data.rotation + r * alpha - elseif blend == MixBlend.first or blend == MixBlend.replace then - r = r + bone.data.rotation - bone.rotation - r = r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360 -- Wrap within -180 and 180. - bone.rotation = bone.rotation + r * alpha; - elseif blend == MixBlend.add then - bone.rotation = bone.rotation + r * alpha; - end - 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 r = frames[frame + ROTATION] - prevRotation - r = prevRotation + (r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360) * percent - if blend == MixBlend.setup then - bone.rotation = bone.data.rotation + (r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360) * alpha - elseif blend == MixBlend.first or blend == MixBlend.replace then - r = r + bone.data.rotation - bone.rotation; - bone.rotation = bone.rotation + (r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360) * alpha - elseif blend == MixBlend.add then - bone.rotation = bone.rotation + (r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360) * alpha - end - 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 - self.type = TimelineType.translate - - function self:getPropertyId () - return TimelineType.translate * SHL_24 + self.boneIndex - end - - 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, blend, direction) - local frames = self.frames - - local bone = skeleton.bones[self.boneIndex] - if not bone.active then return end - - if time < frames[0] then - if blend == MixBlend.setup then - bone.x = bone.data.x - bone.y = bone.data.y - elseif blend == MixBlend.first then - bone.x = bone.x + (bone.data.x - bone.x) * alpha - bone.y = bone.y + (bone.data.y - bone.y) * alpha - end - return - end - - local x = 0 - local y = 0 - if time >= frames[zlen(frames) - ENTRIES] then -- // Time is after last frame. - x = frames[zlen(frames) + PREV_X]; - y = frames[zlen(frames) + PREV_Y]; - else - -- Interpolate between the previous frame and the current frame. - local frame = binarySearch(frames, time, ENTRIES) - x = frames[frame + PREV_X] - y = 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)); - - x = x + (frames[frame + X] - x) * percent - y = y + (frames[frame + Y] - y) * percent - end - if blend == MixBlend.setup then - bone.x = bone.data.x + x * alpha - bone.y = bone.data.y + y * alpha - elseif blend == MixBlend.first or blend == MixBlend.replace then - bone.x = bone.x + (bone.data.x + x - bone.x) * alpha - bone.y = bone.y + (bone.data.y + y - bone.y) * alpha - elseif blend == MixBlend.add then - bone.x = bone.x + x * alpha - bone.y = bone.y + y * alpha - end - 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) - self.type = TimelineType.scale - - function self:getPropertyId () - return TimelineType.scale * SHL_24 + self.boneIndex - end - - function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction) - local frames = self.frames - - local bone = skeleton.bones[self.boneIndex] - if not bone.active then return end - - if time < frames[0] then - if blend == MixBlend.setup then - bone.scaleX = bone.data.scaleX - bone.scaleY = bone.data.scaleY - elseif blend == MixBlend.first then - bone.scaleX = bone.scaleX + (bone.data.scaleX - bone.scaleX) * alpha - bone.scaleY = bone.scaleY + (bone.data.scaleY - bone.scaleY) * alpha - end - return - end - - local x = 0 - local y = 0 - if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame. - x = frames[zlen(frames) + PREV_X] * bone.data.scaleX - y = frames[zlen(frames) + PREV_Y] * bone.data.scaleY - else - -- Interpolate between the previous frame and the current frame. - local frame = binarySearch(frames, time, ENTRIES) - x = frames[frame + PREV_X] - y = 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)) - - x = (x + (frames[frame + X] - x) * percent) * bone.data.scaleX - y = (y + (frames[frame + Y] - y) * percent) * bone.data.scaleY - end - if alpha == 1 then - if blend == MixBlend.add then - bone.scaleX = bone.scaleX + x - bone.data.scaleX - bone.scaleY = bone.scaleY + y - bone.data.scaleY - else - bone.scaleX = x - bone.scaleY = y - end - else - local bx = 0 - local by = 0 - if direction == MixDirection.out then - if blend == MixBlend.setup then - bx = bone.data.scaleX - by = bone.data.scaleY - bone.scaleX = bx + (math_abs(x) * math_signum(bx) - bx) * alpha - bone.scaleY = by + (math_abs(y) * math_signum(by) - by) * alpha - elseif blend == MixBlend.first or blend == MixBlend.replace then - bx = bone.scaleX - by = bone.scaleY - bone.scaleX = bx + (math_abs(x) * math_signum(bx) - bx) * alpha - bone.scaleY = by + (math_abs(y) * math_signum(by) - by) * alpha - elseif blend == MixBlend.add then - bx = bone.scaleX - by = bone.scaleY - bone.scaleX = bx + (math_abs(x) * math_signum(bx) - bone.data.scaleX) * alpha - bone.scaleY = by + (math_abs(y) * math_signum(by) - bone.data.scaleY) * alpha - end - else - if blend == MixBlend.setup then - bx = math_abs(bone.data.scaleX) * math_signum(x) - by = math_abs(bone.data.scaleY) * math_signum(y) - bone.scaleX = bx + (x - bx) * alpha - bone.scaleY = by + (y - by) * alpha - elseif blend == MixBlend.first or blend == MixBlend.replace then - bx = math_abs(bone.scaleX) * math_signum(x) - by = math_abs(bone.scaleY) * math_signum(y) - bone.scaleX = bx + (x - bx) * alpha - bone.scaleY = by + (y - by) * alpha - elseif blend == MixBlend.add then - bx = math_signum(x) - by = math_signum(y) - bone.scaleX = math_abs(bone.scaleX) * bx + (x - math_abs(bone.data.scaleX) * bx) * alpha - bone.scaleY = math_abs(bone.scaleY) * by + (y - math_abs(bone.data.scaleY) * by) * alpha - end - end - end - 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) - self.type = TimelineType.shear - - function self:getPropertyId () - return TimelineType.shear * SHL_24 + self.boneIndex - end - - function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction) - local frames = self.frames - - local bone = skeleton.bones[self.boneIndex] - if not bone.active then return end - - if time < frames[0] then - if blend == MixBlend.setup then - bone.shearX = bone.data.shearX - bone.shearY = bone.data.shearY - elseif blend == MixBlend.first then - bone.shearX = bone.shearX + (bone.data.shearX - bone.shearX) * alpha - bone.shearY = bone.shearX + (bone.data.shearY - bone.shearY) * alpha - end - return - end - - local x = 0 - local y = 0 - if time >= frames[zlen(frames) - ENTRIES] then -- // Time is after last frame. - x = frames[zlen(frames) + PREV_X] - y = frames[zlen(frames) + PREV_Y] - else - -- Interpolate between the previous frame and the current frame. - local frame = binarySearch(frames, time, ENTRIES) - x = frames[frame + PREV_X] - y = 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)) - - x = x + (frames[frame + X] - x) * percent - y = y + (frames[frame + Y] - y) * percent - end - if blend == MixBlend.setup then - bone.shearX = bone.data.shearX + x * alpha - bone.shearY = bone.data.shearY + y * alpha - elseif blend == MixBlend.first or blend == MixBlend.replace then - bone.shearX = bone.shearX + (bone.data.shearX + x - bone.shearX) * alpha - bone.shearY = bone.shearY + (bone.data.shearY + y - bone.shearY) * alpha - elseif blend == MixBlend.add then - bone.shearX = bone.shearX + x * alpha - bone.shearY = bone.shearY + y * alpha - end - 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 - self.type = TimelineType.color - - function self:getPropertyId () - return TimelineType.color * SHL_24 + self.slotIndex - end - - 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, blend, direction) - local frames = self.frames - local slot = skeleton.slots[self.slotIndex] - if not slot.bone.active then return end - if time < frames[0] then - if blend == MixBlend.setup then - slot.color:setFrom(slot.data.color) - elseif blend == MixBlend.first then - local color = slot.color - local setup = slot.data.color - color:add((setup.r - color.r) * alpha, (setup.g - color.g) * alpha, (setup.b - color.b) * alpha, - (setup.a - color.a) * alpha) - end - return - end - - 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 - if alpha == 1 then - slot.color:set(r, g, b, a) - else - local color = slot.color - if blend == MixBlend.setup then color:setFrom(slot.data.color) end - color:add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha) - end - end - - return self -end - -Animation.TwoColorTimeline = {} -Animation.TwoColorTimeline.ENTRIES = 8 -function Animation.TwoColorTimeline.new (frameCount) - local ENTRIES = Animation.TwoColorTimeline.ENTRIES - local PREV_TIME = -8 - local PREV_R = -7 - local PREV_G = -6 - local PREV_B = -5 - local PREV_A = -4 - local PREV_R2 = -3 - local PREV_G2 = -2 - local PREV_B2 = -1 - local R = 1 - local G = 2 - local B = 3 - local A = 4 - local R2 = 5 - local G2 = 6 - local B2 = 7 - - local self = Animation.CurveTimeline.new(frameCount) - self.frames = utils.newNumberArrayZero(frameCount * ENTRIES) - self.slotIndex = -1 - self.type = TimelineType.twoColor - - function self:getPropertyId () - return TimelineType.twoColor * SHL_24 + self.slotIndex - end - - function self:setFrame (frameIndex, time, r, g, b, a, r2, g2, b2) - 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 - self.frames[frameIndex + R2] = r2 - self.frames[frameIndex + G2] = g2 - self.frames[frameIndex + B2] = b2 - end - - function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction) - local frames = self.frames - local slot = skeleton.slots[self.slotIndex] - if not slot.bone.active then return end - - if time < frames[0] then - if blend == MixBlend.setup then - slot.color:setFrom(slot.data.color) - slot.darkColor:setFrom(slot.data.darkColor) - elseif blend == MixBlend.first then - local light = slot.color - local dark = slot.darkColor - local setupLight = slot.data.color - local setupDark = slot.data.darkColor - light:add((setupLight.r - light.r) * alpha, (setupLight.g - light.g) * alpha, (setupLight.b - light.b) * alpha, - (setupLight.a - light.a) * alpha) - dark:add((setupDark.r - dark.r) * alpha, (setupDark.g - dark.g) * alpha, (setupDark.b - dark.b) * alpha, 0) - end - return - end - - local r, g, b, a, r2, g2, b2 - 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] - r2 = frames[i + PREV_R2] - g2 = frames[i + PREV_G2] - b2 = frames[i + PREV_B2] - 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] - r2 = frames[frame + PREV_R2] - g2 = frames[frame + PREV_G2] - b2 = frames[frame + PREV_B2] - 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 - r2 = r2 + (frames[frame + R2] - r2) * percent - g2 = g2 + (frames[frame + G2] - g2) * percent - b2 = b2 + (frames[frame + B2] - b2) * percent - end - if alpha == 1 then - slot.color:set(r, g, b, a) - slot.darkColor:set(r2, g2, b2, 1) - else - local light = slot.color - local dark = slot.darkColor - if blend == MixBlend.setup then - light:setFrom(slot.data.color) - dark:setFrom(slot.data.darkColor) - end - light:add((r - light.r) * alpha, (g - light.g) * alpha, (b - light.b) * alpha, (a - light.a) * alpha) - dark:add((r2 - dark.r) * alpha, (g2 - dark.g) * alpha, (b2 - dark.b) * alpha, 0) - end - end - - return self -end - -Animation.AttachmentTimeline = {} -function Animation.AttachmentTimeline.new (frameCount) - local self = { - frames = utils.newNumberArrayZero(frameCount), -- time, ... - attachmentNames = {}, - slotIndex = -1, - type = TimelineType.attachment - } - - 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:getPropertyId () - return TimelineType.attachment * SHL_24 + self.slotIndex - end - - function self:setAttachment(skeleton, slot, attachmentName) - attachmentName = slot.data.attachmentName - if not attachmentName then - slot:setAttachment(nil) - else - slot:setAttachment(skeleton:getAttachmentByIndex(self.slotIndex, attachmentName)) - end - end - - function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction) - local slot = skeleton.slots[self.slotIndex] - if not slot.bone.active then return end - local attachmentName - if direction == MixDirection.out then - if blend == MixBlend.setup then - self:setAttachment(skeleton, slot, slot.data.attachmentName) - end - return; - end - - local frames = self.frames - if time < frames[0] then - if blend == MixBlend.setup or blend == MixBlend.first then - self:setAttachment(skeleton, slot, slot.data.attachmentName) - end - return - end - - local frameIndex = 0 - if time >= frames[zlen(frames) - 1] then - frameIndex = zlen(frames) - 1 - else - frameIndex = binarySearch(frames, time, 1) - 1 - end - - attachmentName = self.attachmentNames[frameIndex] - if not attachmentName then - slot:setAttachment(nil) - else - slot:setAttachment(skeleton:getAttachmentByIndex(self.slotIndex, attachmentName)) - 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 - self.type = TimelineType.deform - - function self:getPropertyId () - return TimelineType.deform * SHL_27 + self.attachment.id + self.slotIndex - end - - function self:setFrame (frameIndex, time, vertices) - self.frames[frameIndex] = time - self.frameVertices[frameIndex] = vertices - end - - function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction) - local slot = skeleton.slots[self.slotIndex] - if not slot.bone.active then return end - - 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 or slotAttachment.type == AttachmentType.boundingbox) then return end - if slotAttachment.deformAttachment ~= self.attachment then return end - - local frames = self.frames - local deformArray = slot.deform - if #(deformArray) == 0 then blend = MixBlend.setup end - - local frameVertices = self.frameVertices - local vertexCount = #(frameVertices[0]) - - if time < frames[0] then - local vertexAttachment = slotAttachment; - if blend == MixBlend.setup then - slot.deform = {} - return; - elseif blend == MixBlend.first then - if (alpha == 1) then - slot.deform = {} - return; - end - - local deform = utils.setArraySize(deformArray, vertexCount) - if (vertexAttachment.bones == nil) then - local setupVertices = vertexAttachment.vertices - local i = 1 - while i <= vertexCount do - deform[i] = deform[i] + (setupVertices[i] - deform[i]) * alpha - i = i + 1 - end - else - alpha = 1 - alpha - local i = 1 - while i <= vertexCount do - deform[i] = deform[i] * alpha - i = i + 1 - end - end - end - return - end - - local deform = utils.setArraySize(deformArray, vertexCount) - if time >= frames[zlen(frames) - 1] then -- Time is after last frame. - local lastVertices = frameVertices[zlen(frames) - 1] - if alpha == 1 then - if blend == MixBlend.add then - local vertexAttachment = slotAttachment - if vertexAttachment.bones == nil then - -- Unweighted vertex positions, with alpha. - local setupVertices = vertexAttachment.vertices - local i = 1 - while i <= vertexCount do - deform[i] = deform[i] + lastVertices[i] - setupVertices[i] - i = i + 1 - end - else - -- Weighted deform offsets, with alpha. - local i = 1 - while i <= vertexCount do - deform[i] = deform[i] + lastVertices[i] - i = i + 1 - end - end - else - local i = 1 - while i <= vertexCount do - deform[i] = lastVertices[i] - i = i + 1 - end - end - else - if blend == MixBlend.setup then - local vertexAttachment = slotAttachment - if vertexAttachment.bones == nil then - -- Unweighted vertex positions, with alpha. - local setupVertices = vertexAttachment.vertices - local i = 1 - while i <= vertexCount do - local setup = setupVertices[i] - deform[i] = setup + (lastVertices[i] - setup) * alpha - i = i + 1 - end - else - -- Weighted deform offsets, with alpha. - local i = 1 - while i <= vertexCount do - deform[i] = lastVertices[i] * alpha - i = i + 1 - end - end - elseif blend == MixBlend.first or blend == MixBlend.replace then - local i = 1 - while i <= vertexCount do - deform[i] = deform[i] + (lastVertices[i] - deform[i]) * alpha - i = i + 1 - end - local vertexAttachment = slotAttachment - if vertexAttachment.bones == nil then - local setupVertices = vertexAttachment.vertices - local i = 1 - while i <= vertexCount do - deform[i] = deform[i] + (lastVertices[i] - setupVertices[i]) * alpha - i = i + 1 - end - else - -- Weighted deform offsets, with alpha. - local i = 1 - while i <= vertexCount do - deform[i] = deform[i] + lastVertices[i] * alpha - i = i + 1 - end - end - elseif blend == MixBlend.add then - local vertexAttachment = slotAttachment - if vertexAttachment.bones == nil then - local setupVertices = vertexAttachment.vertices - local i = 1 - while i <= vertexCount do - deform[i] = deform[i] + (lastVertices[i] - setupVertices[i]) * alpha - i = i + 1 - end - else - -- Weighted deform offsets, with alpha. - local i = 1 - while i <= vertexCount do - deform[i] = deform[i] + lastVertices[i] * alpha - i = i + 1 - end - end - 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 - if blend == MixBlend.add then - local vertexAttachment = slotAttachment - if vertexAttachment.bones == nil then - -- Unweighted vertex positions, with alpha. - local setupVertices = vertexAttachment.vertices - local i = 1 - while i <= vertexCount do - local prev = prevVertices[i] - deform[i] = deform[i] + prev + (nextVertices[i] - prev) * precent - setupVertices[i] - i = i + 1 - end - else - -- Weighted deform offsets, with alpha. - local i = 1 - while i <= vertexCount do - local prev = prevVertices[i] - deform[i] = deform[i] + prev + (nextVertices[i] - prev) * percent - i = i + 1 - end - end - else - local i = 1 - while i <= vertexCount do - local prev = prevVertices[i] - deform[i] = prev + (nextVertices[i] - prev) * percent - i = i + 1 - end - end - else - if blend == MixBlend.setup then - local vertexAttachment = slotAttachment - if vertexAttachment.bones == nil then - -- Unweighted vertex positions, with alpha. - local setupVertices = vertexAttachment.vertices - local i = 1 - while i <= vertexCount do - local prev = prevVertices[i] - local setup = setupVertices[i] - deform[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha - i = i + 1 - end - else - -- Weighted deform offsets, with alpha. - local i = 1 - while i <= vertexCount do - local prev = prevVertices[i] - deform[i] = (prev + (nextVertices[i] - prev) * percent) * alpha - i = i + 1 - end - end - elseif blend == MixBlend.first or blend == MixBlend.replace then - local i = 1 - while i <= vertexCount do - local prev = prevVertices[i] - deform[i] = deform[i] + (prev + (nextVertices[i] - prev) * percent - deform[i]) * alpha - i = i + 1 - end - elseif blend == MixBlend.add then - local vertexAttachment = slotAttachment - if vertexAttachment.bones == nil then - local setupVertices = vertexAttachment.vertices - local i = 1 - while i <= vertexCount do - local prev = prevVertices[i] - deform[i] = deform[i] + (prev + (nextVertices[i] - prev) * percent - setupVertices[i]) * alpha - i = i + 1 - end - else - -- Weighted deform offsets, with alpha. - local i = 1 - while i <= vertexCount do - local prev = prevVertices[i] - deform[i] = deform[i] + (prev + (nextVertices[i] - prev) * percent) * alpha - i = i + 1 - end - end - end - end - end - - return self -end - -Animation.EventTimeline = {} -function Animation.EventTimeline.new (frameCount) - local self = { - frames = utils.newNumberArrayZero(frameCount), - events = {}, - type = TimelineType.event - } - - function self:getPropertyId () - return TimelineType.event * SHL_24 - end - - 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, blend, direction) - 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, blend, direction) - 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 = {}, - type = TimelineType.drawOrder - } - - function self:getPropertyId () - return TimelineType.drawOrder * SHL_24 - end - - 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, blend, direction) - local drawOrder = skeleton.drawOrder - local slots = skeleton.slots - if direction == MixDirection.out then - if blend == MixBlend.setup then - for i,slot in ipairs(slots) do - drawOrder[i] = slots[i] - end - end - return; - end - - local frames = self.frames - if time < frames[0] then - if blend == MixBlend.setup or blend == MixBlend.first then - for i,slot in ipairs(slots) do - drawOrder[i] = slots[i] - end - end - return - end - - 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 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.IkConstraintTimeline = {} -Animation.IkConstraintTimeline.ENTRIES = 6 -function Animation.IkConstraintTimeline.new (frameCount) - local ENTRIES = Animation.IkConstraintTimeline.ENTRIES - local PREV_TIME = -6 - local PREV_MIX = -5 - local PREV_SOFTNESS = -4 - local PREV_BEND_DIRECTION = -3 - local PREV_COMPRESS = -2 - local PREV_STRETCH = -1 - local MIX = 1 - local SOFTNESS = 2 - local BEND_DIRECTION = 3 - local COMPRESS = 4 - local STRETCH = 5 - - local self = Animation.CurveTimeline.new(frameCount) - self.frames = utils.newNumberArrayZero(frameCount * ENTRIES) -- time, mix, softness, bendDirection, compress, stretch, ... - self.ikConstraintIndex = -1 - self.type = TimelineType.ikConstraint - - function self:getPropertyId () - return TimelineType.ikConstraint * SHL_24 + self.ikConstraintIndex - end - - function self:setFrame (frameIndex, time, mix, softness, bendDirection, compress, stretch) - frameIndex = frameIndex * ENTRIES - self.frames[frameIndex] = time - self.frames[frameIndex + MIX] = mix - self.frames[frameIndex + SOFTNESS] = softness - self.frames[frameIndex + BEND_DIRECTION] = bendDirection - if (compress) then - self.frames[frameIndex + COMPRESS] = 1 - else - self.frames[frameIndex + COMPRESS] = 0 - end - if (stretch) then - self.frames[frameIndex + STRETCH] = 1 - else - self.frames[frameIndex + STRETCH] = 0 - end - end - - function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction) - local frames = self.frames - - local constraint = skeleton.ikConstraints[self.ikConstraintIndex] - if not constraint.active then return end - if time < frames[0] then - if blend == MixBlend.setup then - constraint.mix = constraint.data.mix - constraint.softness = constraint.data.softness - constraint.bendDirection = constraint.data.bendDirection - constraint.compress = constraint.data.compress - constraint.stretch = constraint.data.stretch - elseif blend == MixBlend.first then - constraint.mix = constraint.mix + (constraint.data.mix - constraint.mix) * alpha - constraint.softness = constraint.softness + (constraint.data.softness - constraint.softness) * alpha - constraint.bendDirection = constraint.data.bendDirection - constraint.compress = constraint.data.compress - constraint.stretch = constraint.data.stretch - end - return - end - - if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame. - if blend == MixBlend.setup then - constraint.mix = constraint.data.mix + (frames[zlen(frames) + PREV_MIX] - constraint.data.mix) * alpha - constraint.softness = constraint.data.softness + (frames[zlen(frames) + PREV_SOFTNESS] - constraint.data.softness) * alpha - if direction == MixDirection.out then - constraint.bendDirection = constraint.data.bendDirection - constraint.compress = constraint.data.compress - constraint.stretch = constraint.data.stretch - else - constraint.bendDirection = math_floor(frames[zlen(frames) + PREV_BEND_DIRECTION]); - if (math_floor(frames[zlen(frames) + PREV_COMPRESS]) == 1) then constraint.compress = true else constraint.compress = false end - if (math_floor(frames[zlen(frames) + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end - end - else - constraint.mix = constraint.mix + (frames[zlen(frames) + PREV_MIX] - constraint.mix) * alpha - constraint.softness = constraint.softness + (frames[zlen(frames) + PREV_SOFTNESS] - constraint.softness) * alpha - if direction == MixDirection._in then - constraint.bendDirection = math_floor(frames[zlen(frames) + PREV_BEND_DIRECTION]) - if (math_floor(frames[zlen(frames) + PREV_COMPRESS]) == 1) then constraint.compress = true else constraint.compress = false end - if (math_floor(frames[zlen(frames) + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end - end - end - return - end - - -- Interpolate between the previous frame and the current frame. - local frame = binarySearch(frames, time, ENTRIES) - local mix = frames[frame + PREV_MIX] - local softness = frames[frame + PREV_SOFTNESS] - local frameTime = frames[frame] - local percent = self:getCurvePercent(math.floor(frame / ENTRIES) - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)) - - if blend == MixBlend.setup then - constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha - constraint.softness = constraint.data.softness + (softness + (frames[frame + SOFTNESS] - softness) * percent - constraint.data.softness) * alpha - if direction == MixDirection.out then - constraint.bendDirection = constraint.data.bendDirection - constraint.compress = constraint.data.compress - constraint.stretch = constraint.data.stretch - else - constraint.bendDirection = math_floor(frames[frame + PREV_BEND_DIRECTION]) - if (math_floor(frames[frame + PREV_COMPRESS]) == 1) then constraint.compress = true else constraint.compress = false end - if (math_floor(frames[frame + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end - end - else - constraint.mix = constraint.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha - constraint.softness = constraint.softness + (softness + (frames[frame + SOFTNESS] - softness) * percent - constraint.softness) * alpha - if direction == MixDirection._in then - constraint.bendDirection = math_floor(frames[frame + PREV_BEND_DIRECTION]) - if (math_floor(frames[frame + PREV_COMPRESS]) == 1) then constraint.compress = true else constraint.compress = false end - if (math_floor(frames[frame + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end - end - end - 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 - self.type = TimelineType.transformConstraint - - function self:getPropertyId () - return TimelineType.transformConstraint * SHL_24 + self.transformConstraintIndex - end - - 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, blend, direction) - local frames = self.frames - - local constraint = skeleton.transformConstraints[self.transformConstraintIndex] - if not constraint.active then return end - - if time < frames[0] then - local data = constraint.data - if blend == MixBlend.setup then - constraint.rotateMix = data.rotateMix - constraint.translateMix = data.translateMix - constraint.scaleMix = data.scaleMix - constraint.shearMix = data.shearMix - elseif blend == MixBlend.first then - constraint.rotateMix = constraint.rotateMix + (data.rotateMix - constraint.rotateMix) * alpha - constraint.translateMix = constraint.translateMix + (data.translateMix - constraint.translateMix) * alpha - constraint.scaleMix = constraint.scaleMix + (data.scaleMix - constraint.scaleMix) * alpha - constraint.shearMix = constraint.shearMix + (data.shearMix - constraint.shearMix) * alpha - end - return - end - - local rotate = 0 - local translate = 0 - local scale = 0 - local shear = 0 - if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame. - local i = zlen(frames) - rotate = frames[i + PREV_ROTATE] - translate = frames[i + PREV_TRANSLATE] - scale = frames[i + PREV_SCALE] - shear = frames[i + PREV_SHEAR] - else - -- Interpolate between the previous frame and the current frame. - local frame = binarySearch(frames, time, ENTRIES) - rotate = frames[frame + PREV_ROTATE] - translate = frames[frame + PREV_TRANSLATE] - scale = frames[frame + PREV_SCALE] - shear = frames[frame + PREV_SHEAR] - local frameTime = frames[frame] - local percent = self:getCurvePercent(math_floor(frame / ENTRIES) - 1, - 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); - - rotate = rotate + (frames[frame + ROTATE] - rotate) * percent - translate = translate + (frames[frame + TRANSLATE] - translate) * percent - scale = scale + (frames[frame + SCALE] - scale) * percent - shear = shear + (frames[frame + SHEAR] - shear) * percent - end - if blend == MixBlend.setup then - local data = constraint.data - constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha - constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha - constraint.scaleMix = data.scaleMix + (scale - data.scaleMix) * alpha - constraint.shearMix = data.shearMix + (shear - data.shearMix) * alpha - else - constraint.rotateMix = constraint.rotateMix + (rotate - constraint.rotateMix) * alpha - constraint.translateMix = constraint.translateMix + (translate - constraint.translateMix) * alpha - constraint.scaleMix = constraint.scaleMix + (scale - constraint.scaleMix) * alpha - constraint.shearMix = constraint.shearMix + (shear - constraint.shearMix) * alpha - end - 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 - self.type = TimelineType.pathConstraintPosition - - function self:getPropertyId () - return TimelineType.pathConstraintPosition * SHL_24 + self.pathConstraintIndex - end - - 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, blend, direction) - local frames = self.frames - - local constraint = skeleton.pathConstraints[self.pathConstraintIndex] - if not constraint.active then return end - - if (time < frames[0]) then - if blend == MixBlend.setup then - constraint.position = constraint.data.position - elseif blend == MixBlend.first then - constraint.position = constraint.position + (constraint.data.position - constraint.position) * alpha - end - return - end - - local position = 0 - if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame. - position = frames[zlen(frames) + PREV_VALUE] - else - -- Interpolate between the previous frame and the current frame. - local frame = binarySearch(frames, time, ENTRIES) - 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)) - - position = position + (frames[frame + VALUE] - position) * percent - end - if blend == MixBlend.setup then - constraint.position = constraint.data.position + (position - constraint.data.position) * alpha - else - constraint.position = constraint.position + (position - constraint.position) * alpha - end - 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 - self.type = TimelineType.pathConstraintSpacing - - function self:getPropertyId () - return TimelineType.pathConstraintSpacing * SHL_24 + self.pathConstraintIndex - end - - 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, blend, direction) - local frames = self.frames - - local constraint = skeleton.pathConstraints[self.pathConstraintIndex] - if not constraint.active then return end - - if (time < frames[0]) then - if blend == MixBlend.setup then - constraint.spacing = constraint.data.spacing - elseif blend == MixBlend.first then - constraint.spacing = constraint.spacing + (constraint.data.spacing - constraint.spacing) * alpha - end - return - end - - local spacing = 0 - if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame. - spacing = frames[zlen(frames) + PREV_VALUE] - else - -- Interpolate between the previous frame and the current frame. - local frame = binarySearch(frames, time, ENTRIES) - 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)) - - spacing = spacing + (frames[frame + VALUE] - spacing) * percent - end - - if blend == MixBlend.setup then - constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha - else - constraint.spacing = constraint.spacing + (spacing - constraint.spacing) * alpha - end - 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 - self.type = TimelineType.pathConstraintMix - - function self:getPropertyId () - return TimelineType.pathConstraintMix * SHL_24 + self.pathConstraintIndex - end - - 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, blend, direction) - local frames = self.frames - - local constraint = skeleton.pathConstraints[self.pathConstraintIndex] - if not constraint.active then return end - - if (time < frames[0]) then - if blend == MixBlend.setup then - constraint.rotateMix = constraint.data.rotateMix - constraint.translateMix = constraint.data.translateMix - elseif blend == MixBlend.first then - constraint.rotateMix = constraint.rotateMix + (constraint.data.rotateMix - constraint.rotateMix) * alpha - constraint.translateMix = constraint.translateMix + (constraint.data.translateMix - constraint.translateMix) * alpha - end - return - end - - local rotate = 0 - local translate = 0 - if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame. - rotate = frames[zlen(frames) + PREV_ROTATE] - translate = frames[zlen(frames) + PREV_TRANSLATE] - else - -- Interpolate between the previous frame and the current frame. - local frame = binarySearch(frames, time, ENTRIES) - rotate = frames[frame + PREV_ROTATE] - 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)) - - rotate = rotate + (frames[frame + ROTATE] - rotate) * percent - translate = translate + (frames[frame + TRANSLATE] - translate) * percent - end - - if blend == MixBlend.setup then - constraint.rotateMix = constraint.data.rotateMix + (rotate - constraint.data.rotateMix) * alpha - constraint.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha - else - constraint.rotateMix = constraint.rotateMix + (rotate - constraint.rotateMix) * alpha - constraint.translateMix = constraint.translateMix + (translate - constraint.translateMix) * alpha - end - end - - return self -end - -return Animation +------------------------------------------------------------------------------- +-- Spine Runtimes License Agreement +-- Last updated January 1, 2020. Replaces all prior versions. +-- +-- Copyright (c) 2013-2020, Esoteric Software LLC +-- +-- Integration of the Spine Runtimes into software or otherwise creating +-- derivative works of the Spine Runtimes is permitted under the terms and +-- conditions of Section 2 of the Spine Editor License Agreement: +-- http://esotericsoftware.com/spine-editor-license +-- +-- Otherwise, it is permitted to integrate the Spine Runtimes into software +-- or otherwise create derivative works of the Spine Runtimes (collectively, +-- "Products"), provided that each user of the Products must obtain their own +-- Spine Editor license and redistribution of the Products in any form must +-- include this license and copyright notice. +-- +-- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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 +-- THE SPINE RUNTIMES, 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 math_floor = math.floor +local math_abs = math.abs +local math_signum = utils.signum + +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, + timelineIds = {}, + duration = duration + } + + for i,timeline in ipairs(self.timelines) do + self.timelineIds[timeline:getPropertyId()] = true + end + + function self:hasTimeline(id) + return self.timelineIds[id] == true + end + + function self:apply (skeleton, lastTime, time, loop, events, alpha, blend, direction) + 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, blend, direction) + 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 +Animation.binarySearch = binarySearch + +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.MixBlend = { + setup = 0, + first = 1, + replace = 2, + add = 3 +} +local MixBlend = Animation.MixBlend + +Animation.MixDirection = { + _in = 0, out = 1 +} +local MixDirection = Animation.MixDirection + +Animation.TimelineType = { + rotate = 0, translate = 1, scale = 2, shear = 3, + attachment = 4, color = 5, deform = 6, + event = 7, drawOrder = 8, + ikConstraint = 9, transformConstraint = 10, + pathConstraintPosition = 11, pathConstraintSpacing = 12, pathConstraintMix = 13, + twoColor = 14 +} +local TimelineType = Animation.TimelineType +local SHL_24 = 16777216 +local SHL_27 = 134217728 + +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 +Animation.RotateTimeline.PREV_TIME = -2 +Animation.RotateTimeline.PREV_ROTATION = -1 +Animation.RotateTimeline.ROTATION = 1 +function Animation.RotateTimeline.new (frameCount) + local ENTRIES = Animation.RotateTimeline.ENTRIES + local PREV_TIME = Animation.RotateTimeline.PREV_TIME + local PREV_ROTATION = Animation.RotateTimeline.PREV_ROTATION + local ROTATION = Animation.RotateTimeline.ROTATION + + local self = Animation.CurveTimeline.new(frameCount) + self.boneIndex = -1 + self.frames = utils.newNumberArrayZero(frameCount * 2) + self.type = TimelineType.rotate + + function self:getPropertyId () + return TimelineType.rotate * SHL_24 + self.boneIndex + end + + 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, blend, direction) + local frames = self.frames + + local bone = skeleton.bones[self.boneIndex] + if not bone.active then return end + if time < frames[0] then + if blend == MixBlend.setup then + bone.rotation = bone.data.rotation + elseif blend == MixBlend.first then + local r = bone.data.rotation - bone.rotation + bone.rotation = bone.rotation + (r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360) * alpha + end + return + end + + if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame. + local r = frames[zlen(frames) + PREV_ROTATION] + if blend == MixBlend.setup then + bone.rotation = bone.data.rotation + r * alpha + elseif blend == MixBlend.first or blend == MixBlend.replace then + r = r + bone.data.rotation - bone.rotation + r = r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360 -- Wrap within -180 and 180. + bone.rotation = bone.rotation + r * alpha + elseif blend == MixBlend.add then + bone.rotation = bone.rotation + r * alpha + end + 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 r = frames[frame + ROTATION] - prevRotation + r = prevRotation + (r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360) * percent + if blend == MixBlend.setup then + bone.rotation = bone.data.rotation + (r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360) * alpha + elseif blend == MixBlend.first or blend == MixBlend.replace then + r = r + bone.data.rotation - bone.rotation + bone.rotation = bone.rotation + (r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360) * alpha + elseif blend == MixBlend.add then + bone.rotation = bone.rotation + (r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360) * alpha + end + 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 + self.type = TimelineType.translate + + function self:getPropertyId () + return TimelineType.translate * SHL_24 + self.boneIndex + end + + 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, blend, direction) + local frames = self.frames + + local bone = skeleton.bones[self.boneIndex] + if not bone.active then return end + + if time < frames[0] then + if blend == MixBlend.setup then + bone.x = bone.data.x + bone.y = bone.data.y + elseif blend == MixBlend.first then + bone.x = bone.x + (bone.data.x - bone.x) * alpha + bone.y = bone.y + (bone.data.y - bone.y) * alpha + end + return + end + + local x = 0 + local y = 0 + if time >= frames[zlen(frames) - ENTRIES] then -- // Time is after last frame. + x = frames[zlen(frames) + PREV_X] + y = frames[zlen(frames) + PREV_Y] + else + -- Interpolate between the previous frame and the current frame. + local frame = binarySearch(frames, time, ENTRIES) + x = frames[frame + PREV_X] + y = 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)) + + x = x + (frames[frame + X] - x) * percent + y = y + (frames[frame + Y] - y) * percent + end + if blend == MixBlend.setup then + bone.x = bone.data.x + x * alpha + bone.y = bone.data.y + y * alpha + elseif blend == MixBlend.first or blend == MixBlend.replace then + bone.x = bone.x + (bone.data.x + x - bone.x) * alpha + bone.y = bone.y + (bone.data.y + y - bone.y) * alpha + elseif blend == MixBlend.add then + bone.x = bone.x + x * alpha + bone.y = bone.y + y * alpha + end + 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) + self.type = TimelineType.scale + + function self:getPropertyId () + return TimelineType.scale * SHL_24 + self.boneIndex + end + + function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction) + local frames = self.frames + + local bone = skeleton.bones[self.boneIndex] + if not bone.active then return end + + if time < frames[0] then + if blend == MixBlend.setup then + bone.scaleX = bone.data.scaleX + bone.scaleY = bone.data.scaleY + elseif blend == MixBlend.first then + bone.scaleX = bone.scaleX + (bone.data.scaleX - bone.scaleX) * alpha + bone.scaleY = bone.scaleY + (bone.data.scaleY - bone.scaleY) * alpha + end + return + end + + local x = 0 + local y = 0 + if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame. + x = frames[zlen(frames) + PREV_X] * bone.data.scaleX + y = frames[zlen(frames) + PREV_Y] * bone.data.scaleY + else + -- Interpolate between the previous frame and the current frame. + local frame = binarySearch(frames, time, ENTRIES) + x = frames[frame + PREV_X] + y = 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)) + + x = (x + (frames[frame + X] - x) * percent) * bone.data.scaleX + y = (y + (frames[frame + Y] - y) * percent) * bone.data.scaleY + end + if alpha == 1 then + if blend == MixBlend.add then + bone.scaleX = bone.scaleX + x - bone.data.scaleX + bone.scaleY = bone.scaleY + y - bone.data.scaleY + else + bone.scaleX = x + bone.scaleY = y + end + else + local bx = 0 + local by = 0 + if direction == MixDirection.out then + if blend == MixBlend.setup then + bx = bone.data.scaleX + by = bone.data.scaleY + bone.scaleX = bx + (math_abs(x) * math_signum(bx) - bx) * alpha + bone.scaleY = by + (math_abs(y) * math_signum(by) - by) * alpha + elseif blend == MixBlend.first or blend == MixBlend.replace then + bx = bone.scaleX + by = bone.scaleY + bone.scaleX = bx + (math_abs(x) * math_signum(bx) - bx) * alpha + bone.scaleY = by + (math_abs(y) * math_signum(by) - by) * alpha + elseif blend == MixBlend.add then + bx = bone.scaleX + by = bone.scaleY + bone.scaleX = bx + (math_abs(x) * math_signum(bx) - bone.data.scaleX) * alpha + bone.scaleY = by + (math_abs(y) * math_signum(by) - bone.data.scaleY) * alpha + end + else + if blend == MixBlend.setup then + bx = math_abs(bone.data.scaleX) * math_signum(x) + by = math_abs(bone.data.scaleY) * math_signum(y) + bone.scaleX = bx + (x - bx) * alpha + bone.scaleY = by + (y - by) * alpha + elseif blend == MixBlend.first or blend == MixBlend.replace then + bx = math_abs(bone.scaleX) * math_signum(x) + by = math_abs(bone.scaleY) * math_signum(y) + bone.scaleX = bx + (x - bx) * alpha + bone.scaleY = by + (y - by) * alpha + elseif blend == MixBlend.add then + bx = math_signum(x) + by = math_signum(y) + bone.scaleX = math_abs(bone.scaleX) * bx + (x - math_abs(bone.data.scaleX) * bx) * alpha + bone.scaleY = math_abs(bone.scaleY) * by + (y - math_abs(bone.data.scaleY) * by) * alpha + end + end + end + 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) + self.type = TimelineType.shear + + function self:getPropertyId () + return TimelineType.shear * SHL_24 + self.boneIndex + end + + function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction) + local frames = self.frames + + local bone = skeleton.bones[self.boneIndex] + if not bone.active then return end + + if time < frames[0] then + if blend == MixBlend.setup then + bone.shearX = bone.data.shearX + bone.shearY = bone.data.shearY + elseif blend == MixBlend.first then + bone.shearX = bone.shearX + (bone.data.shearX - bone.shearX) * alpha + bone.shearY = bone.shearX + (bone.data.shearY - bone.shearY) * alpha + end + return + end + + local x = 0 + local y = 0 + if time >= frames[zlen(frames) - ENTRIES] then -- // Time is after last frame. + x = frames[zlen(frames) + PREV_X] + y = frames[zlen(frames) + PREV_Y] + else + -- Interpolate between the previous frame and the current frame. + local frame = binarySearch(frames, time, ENTRIES) + x = frames[frame + PREV_X] + y = 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)) + + x = x + (frames[frame + X] - x) * percent + y = y + (frames[frame + Y] - y) * percent + end + if blend == MixBlend.setup then + bone.shearX = bone.data.shearX + x * alpha + bone.shearY = bone.data.shearY + y * alpha + elseif blend == MixBlend.first or blend == MixBlend.replace then + bone.shearX = bone.shearX + (bone.data.shearX + x - bone.shearX) * alpha + bone.shearY = bone.shearY + (bone.data.shearY + y - bone.shearY) * alpha + elseif blend == MixBlend.add then + bone.shearX = bone.shearX + x * alpha + bone.shearY = bone.shearY + y * alpha + end + 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 + self.type = TimelineType.color + + function self:getPropertyId () + return TimelineType.color * SHL_24 + self.slotIndex + end + + 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, blend, direction) + local frames = self.frames + local slot = skeleton.slots[self.slotIndex] + if not slot.bone.active then return end + if time < frames[0] then + if blend == MixBlend.setup then + slot.color:setFrom(slot.data.color) + elseif blend == MixBlend.first then + local color = slot.color + local setup = slot.data.color + color:add((setup.r - color.r) * alpha, (setup.g - color.g) * alpha, (setup.b - color.b) * alpha, + (setup.a - color.a) * alpha) + end + return + end + + 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 + if alpha == 1 then + slot.color:set(r, g, b, a) + else + local color = slot.color + if blend == MixBlend.setup then color:setFrom(slot.data.color) end + color:add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha) + end + end + + return self +end + +Animation.TwoColorTimeline = {} +Animation.TwoColorTimeline.ENTRIES = 8 +function Animation.TwoColorTimeline.new (frameCount) + local ENTRIES = Animation.TwoColorTimeline.ENTRIES + local PREV_TIME = -8 + local PREV_R = -7 + local PREV_G = -6 + local PREV_B = -5 + local PREV_A = -4 + local PREV_R2 = -3 + local PREV_G2 = -2 + local PREV_B2 = -1 + local R = 1 + local G = 2 + local B = 3 + local A = 4 + local R2 = 5 + local G2 = 6 + local B2 = 7 + + local self = Animation.CurveTimeline.new(frameCount) + self.frames = utils.newNumberArrayZero(frameCount * ENTRIES) + self.slotIndex = -1 + self.type = TimelineType.twoColor + + function self:getPropertyId () + return TimelineType.twoColor * SHL_24 + self.slotIndex + end + + function self:setFrame (frameIndex, time, r, g, b, a, r2, g2, b2) + 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 + self.frames[frameIndex + R2] = r2 + self.frames[frameIndex + G2] = g2 + self.frames[frameIndex + B2] = b2 + end + + function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction) + local frames = self.frames + local slot = skeleton.slots[self.slotIndex] + if not slot.bone.active then return end + + if time < frames[0] then + if blend == MixBlend.setup then + slot.color:setFrom(slot.data.color) + slot.darkColor:setFrom(slot.data.darkColor) + elseif blend == MixBlend.first then + local light = slot.color + local dark = slot.darkColor + local setupLight = slot.data.color + local setupDark = slot.data.darkColor + light:add((setupLight.r - light.r) * alpha, (setupLight.g - light.g) * alpha, (setupLight.b - light.b) * alpha, + (setupLight.a - light.a) * alpha) + dark:add((setupDark.r - dark.r) * alpha, (setupDark.g - dark.g) * alpha, (setupDark.b - dark.b) * alpha, 0) + end + return + end + + local r, g, b, a, r2, g2, b2 + 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] + r2 = frames[i + PREV_R2] + g2 = frames[i + PREV_G2] + b2 = frames[i + PREV_B2] + 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] + r2 = frames[frame + PREV_R2] + g2 = frames[frame + PREV_G2] + b2 = frames[frame + PREV_B2] + 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 + r2 = r2 + (frames[frame + R2] - r2) * percent + g2 = g2 + (frames[frame + G2] - g2) * percent + b2 = b2 + (frames[frame + B2] - b2) * percent + end + if alpha == 1 then + slot.color:set(r, g, b, a) + slot.darkColor:set(r2, g2, b2, 1) + else + local light = slot.color + local dark = slot.darkColor + if blend == MixBlend.setup then + light:setFrom(slot.data.color) + dark:setFrom(slot.data.darkColor) + end + light:add((r - light.r) * alpha, (g - light.g) * alpha, (b - light.b) * alpha, (a - light.a) * alpha) + dark:add((r2 - dark.r) * alpha, (g2 - dark.g) * alpha, (b2 - dark.b) * alpha, 0) + end + end + + return self +end + +Animation.AttachmentTimeline = {} +function Animation.AttachmentTimeline.new (frameCount) + local self = { + frames = utils.newNumberArrayZero(frameCount), -- time, ... + attachmentNames = {}, + slotIndex = -1, + type = TimelineType.attachment + } + + 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:getPropertyId () + return TimelineType.attachment * SHL_24 + self.slotIndex + end + + function self:setAttachment(skeleton, slot, attachmentName) + attachmentName = slot.data.attachmentName + if not attachmentName then + slot:setAttachment(nil) + else + slot:setAttachment(skeleton:getAttachmentByIndex(self.slotIndex, attachmentName)) + end + end + + function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction) + local slot = skeleton.slots[self.slotIndex] + if not slot.bone.active then return end + local attachmentName + if direction == MixDirection.out then + if blend == MixBlend.setup then + self:setAttachment(skeleton, slot, slot.data.attachmentName) + end + return + end + + local frames = self.frames + if time < frames[0] then + if blend == MixBlend.setup or blend == MixBlend.first then + self:setAttachment(skeleton, slot, slot.data.attachmentName) + end + return + end + + local frameIndex = 0 + if time >= frames[zlen(frames) - 1] then + frameIndex = zlen(frames) - 1 + else + frameIndex = binarySearch(frames, time, 1) - 1 + end + + attachmentName = self.attachmentNames[frameIndex] + if not attachmentName then + slot:setAttachment(nil) + else + slot:setAttachment(skeleton:getAttachmentByIndex(self.slotIndex, attachmentName)) + 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 + self.type = TimelineType.deform + + function self:getPropertyId () + return TimelineType.deform * SHL_27 + self.attachment.id + self.slotIndex + end + + function self:setFrame (frameIndex, time, vertices) + self.frames[frameIndex] = time + self.frameVertices[frameIndex] = vertices + end + + function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction) + local slot = skeleton.slots[self.slotIndex] + if not slot.bone.active then return end + + 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 or slotAttachment.type == AttachmentType.boundingbox) then return end + if slotAttachment.deformAttachment ~= self.attachment then return end + + local frames = self.frames + local deformArray = slot.deform + if #(deformArray) == 0 then blend = MixBlend.setup end + + local frameVertices = self.frameVertices + local vertexCount = #(frameVertices[0]) + + if time < frames[0] then + local vertexAttachment = slotAttachment + if blend == MixBlend.setup then + slot.deform = {} + return + elseif blend == MixBlend.first then + if (alpha == 1) then + slot.deform = {} + return + end + + local deform = utils.setArraySize(deformArray, vertexCount) + if (vertexAttachment.bones == nil) then + local setupVertices = vertexAttachment.vertices + local i = 1 + while i <= vertexCount do + deform[i] = deform[i] + (setupVertices[i] - deform[i]) * alpha + i = i + 1 + end + else + alpha = 1 - alpha + local i = 1 + while i <= vertexCount do + deform[i] = deform[i] * alpha + i = i + 1 + end + end + end + return + end + + local deform = utils.setArraySize(deformArray, vertexCount) + if time >= frames[zlen(frames) - 1] then -- Time is after last frame. + local lastVertices = frameVertices[zlen(frames) - 1] + if alpha == 1 then + if blend == MixBlend.add then + local vertexAttachment = slotAttachment + if vertexAttachment.bones == nil then + -- Unweighted vertex positions, with alpha. + local setupVertices = vertexAttachment.vertices + local i = 1 + while i <= vertexCount do + deform[i] = deform[i] + lastVertices[i] - setupVertices[i] + i = i + 1 + end + else + -- Weighted deform offsets, with alpha. + local i = 1 + while i <= vertexCount do + deform[i] = deform[i] + lastVertices[i] + i = i + 1 + end + end + else + local i = 1 + while i <= vertexCount do + deform[i] = lastVertices[i] + i = i + 1 + end + end + else + if blend == MixBlend.setup then + local vertexAttachment = slotAttachment + if vertexAttachment.bones == nil then + -- Unweighted vertex positions, with alpha. + local setupVertices = vertexAttachment.vertices + local i = 1 + while i <= vertexCount do + local setup = setupVertices[i] + deform[i] = setup + (lastVertices[i] - setup) * alpha + i = i + 1 + end + else + -- Weighted deform offsets, with alpha. + local i = 1 + while i <= vertexCount do + deform[i] = lastVertices[i] * alpha + i = i + 1 + end + end + elseif blend == MixBlend.first or blend == MixBlend.replace then + local i = 1 + while i <= vertexCount do + deform[i] = deform[i] + (lastVertices[i] - deform[i]) * alpha + i = i + 1 + end + local vertexAttachment = slotAttachment + if vertexAttachment.bones == nil then + local setupVertices = vertexAttachment.vertices + local i = 1 + while i <= vertexCount do + deform[i] = deform[i] + (lastVertices[i] - setupVertices[i]) * alpha + i = i + 1 + end + else + -- Weighted deform offsets, with alpha. + local i = 1 + while i <= vertexCount do + deform[i] = deform[i] + lastVertices[i] * alpha + i = i + 1 + end + end + elseif blend == MixBlend.add then + local vertexAttachment = slotAttachment + if vertexAttachment.bones == nil then + local setupVertices = vertexAttachment.vertices + local i = 1 + while i <= vertexCount do + deform[i] = deform[i] + (lastVertices[i] - setupVertices[i]) * alpha + i = i + 1 + end + else + -- Weighted deform offsets, with alpha. + local i = 1 + while i <= vertexCount do + deform[i] = deform[i] + lastVertices[i] * alpha + i = i + 1 + end + end + 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 + if blend == MixBlend.add then + local vertexAttachment = slotAttachment + if vertexAttachment.bones == nil then + -- Unweighted vertex positions, with alpha. + local setupVertices = vertexAttachment.vertices + local i = 1 + while i <= vertexCount do + local prev = prevVertices[i] + deform[i] = deform[i] + prev + (nextVertices[i] - prev) * precent - setupVertices[i] + i = i + 1 + end + else + -- Weighted deform offsets, with alpha. + local i = 1 + while i <= vertexCount do + local prev = prevVertices[i] + deform[i] = deform[i] + prev + (nextVertices[i] - prev) * percent + i = i + 1 + end + end + else + local i = 1 + while i <= vertexCount do + local prev = prevVertices[i] + deform[i] = prev + (nextVertices[i] - prev) * percent + i = i + 1 + end + end + else + if blend == MixBlend.setup then + local vertexAttachment = slotAttachment + if vertexAttachment.bones == nil then + -- Unweighted vertex positions, with alpha. + local setupVertices = vertexAttachment.vertices + local i = 1 + while i <= vertexCount do + local prev = prevVertices[i] + local setup = setupVertices[i] + deform[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha + i = i + 1 + end + else + -- Weighted deform offsets, with alpha. + local i = 1 + while i <= vertexCount do + local prev = prevVertices[i] + deform[i] = (prev + (nextVertices[i] - prev) * percent) * alpha + i = i + 1 + end + end + elseif blend == MixBlend.first or blend == MixBlend.replace then + local i = 1 + while i <= vertexCount do + local prev = prevVertices[i] + deform[i] = deform[i] + (prev + (nextVertices[i] - prev) * percent - deform[i]) * alpha + i = i + 1 + end + elseif blend == MixBlend.add then + local vertexAttachment = slotAttachment + if vertexAttachment.bones == nil then + local setupVertices = vertexAttachment.vertices + local i = 1 + while i <= vertexCount do + local prev = prevVertices[i] + deform[i] = deform[i] + (prev + (nextVertices[i] - prev) * percent - setupVertices[i]) * alpha + i = i + 1 + end + else + -- Weighted deform offsets, with alpha. + local i = 1 + while i <= vertexCount do + local prev = prevVertices[i] + deform[i] = deform[i] + (prev + (nextVertices[i] - prev) * percent) * alpha + i = i + 1 + end + end + end + end + end + + return self +end + +Animation.EventTimeline = {} +function Animation.EventTimeline.new (frameCount) + local self = { + frames = utils.newNumberArrayZero(frameCount), + events = {}, + type = TimelineType.event + } + + function self:getPropertyId () + return TimelineType.event * SHL_24 + end + + 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, blend, direction) + 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, blend, direction) + 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 = {}, + type = TimelineType.drawOrder + } + + function self:getPropertyId () + return TimelineType.drawOrder * SHL_24 + end + + 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, blend, direction) + local drawOrder = skeleton.drawOrder + local slots = skeleton.slots + if direction == MixDirection.out then + if blend == MixBlend.setup then + for i,slot in ipairs(slots) do + drawOrder[i] = slots[i] + end + end + return + end + + local frames = self.frames + if time < frames[0] then + if blend == MixBlend.setup or blend == MixBlend.first then + for i,slot in ipairs(slots) do + drawOrder[i] = slots[i] + end + end + return + end + + 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 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.IkConstraintTimeline = {} +Animation.IkConstraintTimeline.ENTRIES = 6 +function Animation.IkConstraintTimeline.new (frameCount) + local ENTRIES = Animation.IkConstraintTimeline.ENTRIES + local PREV_TIME = -6 + local PREV_MIX = -5 + local PREV_SOFTNESS = -4 + local PREV_BEND_DIRECTION = -3 + local PREV_COMPRESS = -2 + local PREV_STRETCH = -1 + local MIX = 1 + local SOFTNESS = 2 + local BEND_DIRECTION = 3 + local COMPRESS = 4 + local STRETCH = 5 + + local self = Animation.CurveTimeline.new(frameCount) + self.frames = utils.newNumberArrayZero(frameCount * ENTRIES) -- time, mix, softness, bendDirection, compress, stretch, ... + self.ikConstraintIndex = -1 + self.type = TimelineType.ikConstraint + + function self:getPropertyId () + return TimelineType.ikConstraint * SHL_24 + self.ikConstraintIndex + end + + function self:setFrame (frameIndex, time, mix, softness, bendDirection, compress, stretch) + frameIndex = frameIndex * ENTRIES + self.frames[frameIndex] = time + self.frames[frameIndex + MIX] = mix + self.frames[frameIndex + SOFTNESS] = softness + self.frames[frameIndex + BEND_DIRECTION] = bendDirection + if (compress) then + self.frames[frameIndex + COMPRESS] = 1 + else + self.frames[frameIndex + COMPRESS] = 0 + end + if (stretch) then + self.frames[frameIndex + STRETCH] = 1 + else + self.frames[frameIndex + STRETCH] = 0 + end + end + + function self:apply (skeleton, lastTime, time, firedEvents, alpha, blend, direction) + local frames = self.frames + + local constraint = skeleton.ikConstraints[self.ikConstraintIndex] + if not constraint.active then return end + if time < frames[0] then + if blend == MixBlend.setup then + constraint.mix = constraint.data.mix + constraint.softness = constraint.data.softness + constraint.bendDirection = constraint.data.bendDirection + constraint.compress = constraint.data.compress + constraint.stretch = constraint.data.stretch + elseif blend == MixBlend.first then + constraint.mix = constraint.mix + (constraint.data.mix - constraint.mix) * alpha + constraint.softness = constraint.softness + (constraint.data.softness - constraint.softness) * alpha + constraint.bendDirection = constraint.data.bendDirection + constraint.compress = constraint.data.compress + constraint.stretch = constraint.data.stretch + end + return + end + + if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame. + if blend == MixBlend.setup then + constraint.mix = constraint.data.mix + (frames[zlen(frames) + PREV_MIX] - constraint.data.mix) * alpha + constraint.softness = constraint.data.softness + (frames[zlen(frames) + PREV_SOFTNESS] - constraint.data.softness) * alpha + if direction == MixDirection.out then + constraint.bendDirection = constraint.data.bendDirection + constraint.compress = constraint.data.compress + constraint.stretch = constraint.data.stretch + else + constraint.bendDirection = math_floor(frames[zlen(frames) + PREV_BEND_DIRECTION]) + if (math_floor(frames[zlen(frames) + PREV_COMPRESS]) == 1) then constraint.compress = true else constraint.compress = false end + if (math_floor(frames[zlen(frames) + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end + end + else + constraint.mix = constraint.mix + (frames[zlen(frames) + PREV_MIX] - constraint.mix) * alpha + constraint.softness = constraint.softness + (frames[zlen(frames) + PREV_SOFTNESS] - constraint.softness) * alpha + if direction == MixDirection._in then + constraint.bendDirection = math_floor(frames[zlen(frames) + PREV_BEND_DIRECTION]) + if (math_floor(frames[zlen(frames) + PREV_COMPRESS]) == 1) then constraint.compress = true else constraint.compress = false end + if (math_floor(frames[zlen(frames) + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end + end + end + return + end + + -- Interpolate between the previous frame and the current frame. + local frame = binarySearch(frames, time, ENTRIES) + local mix = frames[frame + PREV_MIX] + local softness = frames[frame + PREV_SOFTNESS] + local frameTime = frames[frame] + local percent = self:getCurvePercent(math.floor(frame / ENTRIES) - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)) + + if blend == MixBlend.setup then + constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha + constraint.softness = constraint.data.softness + (softness + (frames[frame + SOFTNESS] - softness) * percent - constraint.data.softness) * alpha + if direction == MixDirection.out then + constraint.bendDirection = constraint.data.bendDirection + constraint.compress = constraint.data.compress + constraint.stretch = constraint.data.stretch + else + constraint.bendDirection = math_floor(frames[frame + PREV_BEND_DIRECTION]) + if (math_floor(frames[frame + PREV_COMPRESS]) == 1) then constraint.compress = true else constraint.compress = false end + if (math_floor(frames[frame + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end + end + else + constraint.mix = constraint.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha + constraint.softness = constraint.softness + (softness + (frames[frame + SOFTNESS] - softness) * percent - constraint.softness) * alpha + if direction == MixDirection._in then + constraint.bendDirection = math_floor(frames[frame + PREV_BEND_DIRECTION]) + if (math_floor(frames[frame + PREV_COMPRESS]) == 1) then constraint.compress = true else constraint.compress = false end + if (math_floor(frames[frame + PREV_STRETCH]) == 1) then constraint.stretch = true else constraint.stretch = false end + end + end + 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 + self.type = TimelineType.transformConstraint + + function self:getPropertyId () + return TimelineType.transformConstraint * SHL_24 + self.transformConstraintIndex + end + + 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, blend, direction) + local frames = self.frames + + local constraint = skeleton.transformConstraints[self.transformConstraintIndex] + if not constraint.active then return end + + if time < frames[0] then + local data = constraint.data + if blend == MixBlend.setup then + constraint.rotateMix = data.rotateMix + constraint.translateMix = data.translateMix + constraint.scaleMix = data.scaleMix + constraint.shearMix = data.shearMix + elseif blend == MixBlend.first then + constraint.rotateMix = constraint.rotateMix + (data.rotateMix - constraint.rotateMix) * alpha + constraint.translateMix = constraint.translateMix + (data.translateMix - constraint.translateMix) * alpha + constraint.scaleMix = constraint.scaleMix + (data.scaleMix - constraint.scaleMix) * alpha + constraint.shearMix = constraint.shearMix + (data.shearMix - constraint.shearMix) * alpha + end + return + end + + local rotate = 0 + local translate = 0 + local scale = 0 + local shear = 0 + if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame. + local i = zlen(frames) + rotate = frames[i + PREV_ROTATE] + translate = frames[i + PREV_TRANSLATE] + scale = frames[i + PREV_SCALE] + shear = frames[i + PREV_SHEAR] + else + -- Interpolate between the previous frame and the current frame. + local frame = binarySearch(frames, time, ENTRIES) + rotate = frames[frame + PREV_ROTATE] + translate = frames[frame + PREV_TRANSLATE] + scale = frames[frame + PREV_SCALE] + shear = frames[frame + PREV_SHEAR] + local frameTime = frames[frame] + local percent = self:getCurvePercent(math_floor(frame / ENTRIES) - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)) + + rotate = rotate + (frames[frame + ROTATE] - rotate) * percent + translate = translate + (frames[frame + TRANSLATE] - translate) * percent + scale = scale + (frames[frame + SCALE] - scale) * percent + shear = shear + (frames[frame + SHEAR] - shear) * percent + end + if blend == MixBlend.setup then + local data = constraint.data + constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha + constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha + constraint.scaleMix = data.scaleMix + (scale - data.scaleMix) * alpha + constraint.shearMix = data.shearMix + (shear - data.shearMix) * alpha + else + constraint.rotateMix = constraint.rotateMix + (rotate - constraint.rotateMix) * alpha + constraint.translateMix = constraint.translateMix + (translate - constraint.translateMix) * alpha + constraint.scaleMix = constraint.scaleMix + (scale - constraint.scaleMix) * alpha + constraint.shearMix = constraint.shearMix + (shear - constraint.shearMix) * alpha + end + 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 + self.type = TimelineType.pathConstraintPosition + + function self:getPropertyId () + return TimelineType.pathConstraintPosition * SHL_24 + self.pathConstraintIndex + end + + 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, blend, direction) + local frames = self.frames + + local constraint = skeleton.pathConstraints[self.pathConstraintIndex] + if not constraint.active then return end + + if (time < frames[0]) then + if blend == MixBlend.setup then + constraint.position = constraint.data.position + elseif blend == MixBlend.first then + constraint.position = constraint.position + (constraint.data.position - constraint.position) * alpha + end + return + end + + local position = 0 + if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame. + position = frames[zlen(frames) + PREV_VALUE] + else + -- Interpolate between the previous frame and the current frame. + local frame = binarySearch(frames, time, ENTRIES) + 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)) + + position = position + (frames[frame + VALUE] - position) * percent + end + if blend == MixBlend.setup then + constraint.position = constraint.data.position + (position - constraint.data.position) * alpha + else + constraint.position = constraint.position + (position - constraint.position) * alpha + end + 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 + self.type = TimelineType.pathConstraintSpacing + + function self:getPropertyId () + return TimelineType.pathConstraintSpacing * SHL_24 + self.pathConstraintIndex + end + + 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, blend, direction) + local frames = self.frames + + local constraint = skeleton.pathConstraints[self.pathConstraintIndex] + if not constraint.active then return end + + if (time < frames[0]) then + if blend == MixBlend.setup then + constraint.spacing = constraint.data.spacing + elseif blend == MixBlend.first then + constraint.spacing = constraint.spacing + (constraint.data.spacing - constraint.spacing) * alpha + end + return + end + + local spacing = 0 + if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame. + spacing = frames[zlen(frames) + PREV_VALUE] + else + -- Interpolate between the previous frame and the current frame. + local frame = binarySearch(frames, time, ENTRIES) + 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)) + + spacing = spacing + (frames[frame + VALUE] - spacing) * percent + end + + if blend == MixBlend.setup then + constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha + else + constraint.spacing = constraint.spacing + (spacing - constraint.spacing) * alpha + end + 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 + self.type = TimelineType.pathConstraintMix + + function self:getPropertyId () + return TimelineType.pathConstraintMix * SHL_24 + self.pathConstraintIndex + end + + 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, blend, direction) + local frames = self.frames + + local constraint = skeleton.pathConstraints[self.pathConstraintIndex] + if not constraint.active then return end + + if (time < frames[0]) then + if blend == MixBlend.setup then + constraint.rotateMix = constraint.data.rotateMix + constraint.translateMix = constraint.data.translateMix + elseif blend == MixBlend.first then + constraint.rotateMix = constraint.rotateMix + (constraint.data.rotateMix - constraint.rotateMix) * alpha + constraint.translateMix = constraint.translateMix + (constraint.data.translateMix - constraint.translateMix) * alpha + end + return + end + + local rotate = 0 + local translate = 0 + if time >= frames[zlen(frames) - ENTRIES] then -- Time is after last frame. + rotate = frames[zlen(frames) + PREV_ROTATE] + translate = frames[zlen(frames) + PREV_TRANSLATE] + else + -- Interpolate between the previous frame and the current frame. + local frame = binarySearch(frames, time, ENTRIES) + rotate = frames[frame + PREV_ROTATE] + 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)) + + rotate = rotate + (frames[frame + ROTATE] - rotate) * percent + translate = translate + (frames[frame + TRANSLATE] - translate) * percent + end + + if blend == MixBlend.setup then + constraint.rotateMix = constraint.data.rotateMix + (rotate - constraint.data.rotateMix) * alpha + constraint.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha + else + constraint.rotateMix = constraint.rotateMix + (rotate - constraint.rotateMix) * alpha + constraint.translateMix = constraint.translateMix + (translate - constraint.translateMix) * alpha + end + end + + return self +end + +return Animation diff --git a/spine-lua/spine-lua/AnimationState.lua b/spine-lua/spine-lua/AnimationState.lua index 9b6bf5927..eabf6310e 100644 --- a/spine-lua/spine-lua/AnimationState.lua +++ b/spine-lua/spine-lua/AnimationState.lua @@ -159,7 +159,7 @@ function EventQueue:drain () end self:clear() - self.drainDisabled = false; + self.drainDisabled = false end function EventQueue:clear () @@ -326,7 +326,7 @@ function AnimationState:updateMixingFrom (to, delta) from.trackTime = from.trackTime + delta * from.timeScale to.mixTime = to.mixTime + delta - return false; + return false end function AnimationState:apply (skeleton) @@ -388,7 +388,7 @@ function AnimationState:apply (skeleton) end end self:queueEvents(current, animationTime) - self.events = {}; + self.events = {} current.nextAnimationLast = animationTime current.nextTrackLast = current.trackTime end @@ -400,7 +400,7 @@ function AnimationState:apply (skeleton) -- subsequent timelines see any deform, but the subsequent timelines don't set an attachment (eg they are also mixing out or -- the time is before the first key). local setupState = self.unkeyedState + SETUP - local slots = skeleton.slots; + local slots = skeleton.slots for _, slot in ipairs(slots) do if slot.attachmentState == setupState then local attachmentName = slot.data.attachmentName @@ -411,7 +411,7 @@ function AnimationState:apply (skeleton) end end end - self.unkeyedState = self.unkeyedState + 2; -- Increasing after each use avoids the need to reset attachmentState for every slot. + self.unkeyedState = self.unkeyedState + 2 -- Increasing after each use avoids the need to reset attachmentState for every slot. queue:drain() @@ -453,11 +453,11 @@ function AnimationState:applyMixingFrom (to, skeleton, blend) local firstFrame = #from.timelinesRotation == 0 local timelinesRotation = from.timelinesRotation - from.totalAlpha = 0; + from.totalAlpha = 0 for i,timeline in ipairs(timelines) do - local skipSubsequent = false; - local direction = MixDirection.out; + local skipSubsequent = false + local direction = MixDirection.out local timelineBlend = MixBlend.setup local alpha = 0 if timelineMode[i] == SUBSEQUENT then @@ -498,7 +498,7 @@ function AnimationState:applyMixingFrom (to, skeleton, blend) if (to.mixDuration > 0) then self:queueEvents(from, animationTime) end - self.events = {}; + self.events = {} from.nextAnimationLast = animationTime from.nextTrackLast = from.trackTime @@ -506,20 +506,20 @@ function AnimationState:applyMixingFrom (to, skeleton, blend) end function AnimationState:applyAttachmentTimeline(timeline, skeleton, time, blend, attachments) - local slot = skeleton.slots[timeline.slotIndex]; + local slot = skeleton.slots[timeline.slotIndex] if slot.bone.active == false then return end local frames = timeline.frames if time < frames[0] then -- Time is before first frame. if blend == MixBlend.setup or blend == MixBlend.first then - self:setAttachment(skeleton, slot, slot.data.attachmentName, attachments); + self:setAttachment(skeleton, slot, slot.data.attachmentName, attachments) end else local frameIndex = 0 if (time >= frames[zlen(frames) - 1]) then -- Time is after last frame. - frameIndex = zlen(frames) - 1; + frameIndex = zlen(frames) - 1 else - frameIndex = Animation.binarySearch(frames, time, 1) - 1; + frameIndex = Animation.binarySearch(frames, time, 1) - 1 end self:setAttachment(skeleton, slot, timeline.attachmentNames[frameIndex], attachments) end @@ -665,7 +665,7 @@ function AnimationState:clearTracks () local queue = self.queue local tracks = self.tracks local oldDrainDisabled = queue.drainDisabled - queue.drainDisabled = true; + queue.drainDisabled = true local numTracks = getNumTracks(tracks) local i = 0 while i <= numTracks do @@ -673,7 +673,7 @@ function AnimationState:clearTracks () end tracks = {} queue.drainDisabled = oldDrainDisabled - queue:drain(); + queue:drain() end function AnimationState:clearTrack (trackIndex) @@ -686,7 +686,7 @@ function AnimationState:clearTrack (trackIndex) self:disposeNext(current) - local entry = current; + local entry = current while (true) do local from = entry.mixingFrom if from == nil then break end @@ -717,7 +717,7 @@ function AnimationState:setCurrent (index, current, interrupt) current.interruptAlpha = current.interruptAlpha * math_min(1, from.mixTime / from.mixDuration) end - from.timelinesRotation = {}; + from.timelinesRotation = {} end queue:start(current) @@ -731,7 +731,7 @@ end function AnimationState:setAnimation (trackIndex, animation, loop) if not animation then error("animation cannot be null.") end - local interrupt = true; + local interrupt = true local current = self:expandToIndex(trackIndex) local queue = self.queue local tracks = self.tracks @@ -743,7 +743,7 @@ function AnimationState:setAnimation (trackIndex, animation, loop) queue:_end(current) self:disposeNext(current) current = current.mixingFrom - interrupt = false; + interrupt = false else self:disposeNext(current) end diff --git a/spine-lua/spine-lua/Bone.lua b/spine-lua/spine-lua/Bone.lua index 60f951f6c..43965571d 100644 --- a/spine-lua/spine-lua/Bone.lua +++ b/spine-lua/spine-lua/Bone.lua @@ -40,13 +40,12 @@ local math_pi = math.pi local TransformMode = require "spine-lua.TransformMode" function math.sign(x) - if x<0 then + if x < 0 then return -1 - elseif x>0 then + elseif x > 0 then return 1 - else - return 0 end + return 0 end local math_sign = math.sign @@ -96,8 +95,8 @@ function Bone:updateWorldTransformWith (x, y, rotation, scaleX, scaleY, shearX, self.ashearY = shearY self.appliedValid = true - local sx = self.skeleton.scaleX; - local sy = self.skeleton.scaleY; + local sx = self.skeleton.scaleX + local sy = self.skeleton.scaleY local parent = self.parent if parent == nil then @@ -132,7 +131,7 @@ function Bone:updateWorldTransformWith (x, y, rotation, scaleX, scaleY, shearX, self.b = pa * lb + pb * ld self.c = pc * la + pd * lc self.d = pc * lb + pd * ld - return; + return elseif transformMode == TransformMode.onlyTranslation then local rotationY = rotation + 90 + shearY self.a = math_cos(math_rad(rotation + shearX)) * scaleX @@ -148,11 +147,11 @@ function Bone:updateWorldTransformWith (x, y, rotation, scaleX, scaleY, shearX, pc = pc / self.skeleton.scaleY pb = pc * s pd = pa * s - prx = math_deg(math_atan2(pc, pa)); + prx = math_deg(math_atan2(pc, pa)) else - pa = 0; - pc = 0; - prx = 90 - math_deg(math_atan2(pd, pb)); + pa = 0 + pc = 0 + prx = 90 - math_deg(math_atan2(pd, pb)) end local rx = rotation + shearX - prx local ry = rotation + shearY - prx + 90 @@ -181,10 +180,10 @@ function Bone:updateWorldTransformWith (x, y, rotation, scaleX, scaleY, shearX, local r = math_pi / 2 + math_atan2(zc, za) local zb = math_cos(r) * s local zd = math_sin(r) * s - local la = math_cos(math_rad(shearX)) * scaleX; - local lb = math_cos(math_rad(90 + shearY)) * scaleY; - local lc = math_sin(math_rad(shearX)) * scaleX; - local ld = math_sin(math_rad(90 + shearY)) * scaleY; + local la = math_cos(math_rad(shearX)) * scaleX + local lb = math_cos(math_rad(90 + shearY)) * scaleY + local lc = math_sin(math_rad(shearX)) * scaleX + local ld = math_sin(math_rad(90 + shearY)) * scaleY self.a = za * la + zb * lc self.b = za * lb + zb * ld self.c = zc * la + zd * lc diff --git a/spine-lua/spine-lua/BoneData.lua b/spine-lua/spine-lua/BoneData.lua index efb6074f0..69b9e2067 100644 --- a/spine-lua/spine-lua/BoneData.lua +++ b/spine-lua/spine-lua/BoneData.lua @@ -47,7 +47,8 @@ function BoneData.new (index, name, parent) shearX = 0, shearY = 0, inheritRotation = true, inheritScale = true, - skinRequired = false + skinRequired = false, + color = nil } return self diff --git a/spine-lua/spine-lua/BoundingBoxAttachment.lua b/spine-lua/spine-lua/BoundingBoxAttachment.lua deleted file mode 100644 index 7e1871795..000000000 --- a/spine-lua/spine-lua/BoundingBoxAttachment.lua +++ /dev/null @@ -1,61 +0,0 @@ -------------------------------------------------------------------------------- --- Spine Runtimes License Agreement --- Last updated January 1, 2020. Replaces all prior versions. --- --- Copyright (c) 2013-2020, Esoteric Software LLC --- --- Integration of the Spine Runtimes into software or otherwise creating --- derivative works of the Spine Runtimes is permitted under the terms and --- conditions of Section 2 of the Spine Editor License Agreement: --- http://esotericsoftware.com/spine-editor-license --- --- Otherwise, it is permitted to integrate the Spine Runtimes into software --- or otherwise create derivative works of the Spine Runtimes (collectively, --- "Products"), provided that each user of the Products must obtain their own --- Spine Editor license and redistribution of the Products in any form must --- include this license and copyright notice. --- --- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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 --- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- - -local AttachmentType = require "spine-lua.AttachmentType" - -local BoundingBoxAttachment = {} -function BoundingBoxAttachment.new (name) - if not name then error("name cannot be nil", 2) end - - local self = { - name = name, - type = AttachmentType.boundingbox, - vertices = {} - } - - function self:computeWorldVertices (x, y, bone, worldVertices) - x = x + bone.worldX - y = y + bone.worldY - local m00 = bone.m00 - local m01 = bone.m01 - local m10 = bone.m10 - local m11 = bone.m11 - local vertices = self.vertices - local count = #vertices - for i = 1, count, 2 do - local px = vertices[i] - local py = vertices[i + 1] - worldVertices[i] = px * m00 + py * m01 + x - worldVertices[i + 1] = px * m10 + py * m11 + y - end - end - - return self -end -return BoundingBoxAttachment diff --git a/spine-lua/spine-lua/IkConstraint.lua b/spine-lua/spine-lua/IkConstraint.lua index 36e7ea45a..d799be5c1 100644 --- a/spine-lua/spine-lua/IkConstraint.lua +++ b/spine-lua/spine-lua/IkConstraint.lua @@ -102,12 +102,12 @@ function IkConstraint:apply1 (bone, targetX, targetY, compress, stretch, uniform tx = targetX - bone.worldX ty = targetY - bone.worldY elseif bone.data.transformMode == TransformMode.noRotationOrReflection then - local s = math_abs(pa * pd - pb * pc) / (pa * pa + pc * pc); - local sa = pa / bone.skeleton.scaleX; - local sc = pc / bone.skeleton.scaleY; - pb = -sc * s * bone.skeleton.scaleX; - pd = sa * s * bone.skeleton.scaleY; - rotationIK = rotationIK + math_deg(math_atan2(sc, sa)); + local s = math_abs(pa * pd - pb * pc) / (pa * pa + pc * pc) + local sa = pa / bone.skeleton.scaleX + local sc = pc / bone.skeleton.scaleY + pb = -sc * s * bone.skeleton.scaleX + pd = sa * s * bone.skeleton.scaleY + rotationIK = rotationIK + math_deg(math_atan2(sc, sa)) local x = targetX - p.worldX @@ -255,13 +255,13 @@ function IkConstraint:apply2 (parent, child, targetX, targetY, bendDir, stretch, b = psy * l2 local aa = a * a local bb = b * b - local ta = math_atan2(ty, tx); + local ta = math_atan2(ty, tx) c = bb * l1 * l1 + aa * dd - aa * bb local c1 = -2 * bb * l1 local c2 = bb - aa d = c1 * c1 - 4 * c2 * c if d >= 0 then - local q = math_sqrt(d); + local q = math_sqrt(d) if (c1 < 0) then q = -q end q = -(c1 + q) / 2 local r0 = q / c2 @@ -279,7 +279,7 @@ function IkConstraint:apply2 (parent, child, targetX, targetY, bendDir, stretch, local minAngle = math_pi local minX = l1 - a local minDist = minX * minX - local minY = 0; + local minY = 0 local maxAngle = 0 local maxX = l1 + a local maxDist = maxX * maxX @@ -328,7 +328,7 @@ function IkConstraint:apply2 (parent, child, targetX, targetY, bendDir, stretch, elseif a2 < -180 then a2 = a2 + 360 end - child:updateWorldTransformWith(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY); + child:updateWorldTransformWith(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY) end return IkConstraint diff --git a/spine-lua/spine-lua/MeshAttachment.lua b/spine-lua/spine-lua/MeshAttachment.lua deleted file mode 100644 index 2e8d0dd46..000000000 --- a/spine-lua/spine-lua/MeshAttachment.lua +++ /dev/null @@ -1,93 +0,0 @@ -------------------------------------------------------------------------------- --- Spine Runtimes License Agreement --- Last updated January 1, 2020. Replaces all prior versions. --- --- Copyright (c) 2013-2020, Esoteric Software LLC --- --- Integration of the Spine Runtimes into software or otherwise creating --- derivative works of the Spine Runtimes is permitted under the terms and --- conditions of Section 2 of the Spine Editor License Agreement: --- http://esotericsoftware.com/spine-editor-license --- --- Otherwise, it is permitted to integrate the Spine Runtimes into software --- or otherwise create derivative works of the Spine Runtimes (collectively, --- "Products"), provided that each user of the Products must obtain their own --- Spine Editor license and redistribution of the Products in any form must --- include this license and copyright notice. --- --- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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 --- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- - -local AttachmentType = require "spine-lua.AttachmentType" - -local MeshAttachment = {} -function MeshAttachment.new (name) - if not name then error("name cannot be nil", 2) end - - local self = { - name = name, - type = AttachmentType.mesh, - vertices = nil, - uvs = nil, - regionUVs = nil, - triangles = nil, - hullLength = 0, - r = 1, g = 1, b = 1, a = 1, - path = nil, - rendererObject = nil, - regionU = 0, regionV = 0, regionU2 = 1, regionV2 = 1, regionRotate = false, - regionOffsetX = 0, regionOffsetY = 0, - regionWidth = 0, regionHeight = 0, - regionOriginalWidth = 0, regionOriginalHeight = 0, - edges = nil, - width = 0, height = 0 - } - - function self:updateUVs () - local width, height = self.regionU2 - self.regionU, self.regionV2 - self.regionV - local n = #self.regionUVs - if not self.uvs or #self.uvs ~= n then - self.uvs = {} - end - if self.regionRotate then - for i = 1, n, 2 do - self.uvs[i] = self.regionU + self.regionUVs[i + 1] * width - self.uvs[i + 1] = self.regionV + height - self.regionUVs[i] * height - end - else - for i = 1, n, 2 do - self.uvs[i] = self.regionU + self.regionUVs[i] * width - self.uvs[i + 1] = self.regionV + self.regionUVs[i + 1] * height - end - end - end - - function self:computeWorldVertices (x, y, slot, worldVertices) - local bone = slot.bone -x,y=slot.bone.skeleton.x,slot.bone.skeleton.y - x = x + bone.worldX - y = y + bone.worldY - local m00, m01, m10, m11 = bone.m00, bone.m01, bone.m10, bone.m11 - local vertices = self.vertices - local verticesCount = #vertices - if slot.deform and #slot.deform == verticesCount then vertices = slot.deform end - for i = 1, verticesCount, 2 do - local vx = vertices[i] - local vy = vertices[i + 1] - worldVertices[i] = vx * m00 + vy * m01 + x - worldVertices[i + 1] = vx * m10 + vy * m11 + y - end - end - - return self -end -return MeshAttachment diff --git a/spine-lua/spine-lua/PathConstraint.lua b/spine-lua/spine-lua/PathConstraint.lua index 83957f18f..6467043de 100644 --- a/spine-lua/spine-lua/PathConstraint.lua +++ b/spine-lua/spine-lua/PathConstraint.lua @@ -99,7 +99,7 @@ function PathConstraint:update () local rotate = rotateMix > 0 if not translate and not rotate then return end - local data = self.data; + local data = self.data local percentSpacing = data.spacingMode == PathConstraintData.SpacingMode.percent local rotateMode = data.rotateMode local tangents = rotateMode == PathConstraintData.RotateMode.tangent @@ -117,7 +117,7 @@ function PathConstraint:update () local i = 0 local n = spacesCount - 1 while i < n do - local bone = bones[i + 1]; + local bone = bones[i + 1] local setupLength = bone.data.length if setupLength < PathConstraint.epsilon then if scale then lengths[i + 1] = 0 end @@ -157,12 +157,12 @@ function PathConstraint:update () local boneX = positions[1] local boneY = positions[2] local offsetRotation = data.offsetRotation - local tip = false; + local tip = false if offsetRotation == 0 then tip = rotateMode == PathConstraintData.RotateMode.chain else - tip = false; - local p = self.target.bone; + tip = false + local p = self.target.bone if p.a * p.d - p.b * p.c > 0 then offsetRotation = offsetRotation * utils.degRad else @@ -210,8 +210,8 @@ function PathConstraint:update () cos = math_cos(r) sin = math_sin(r) local length = bone.data.length - boneX = boneX + (length * (cos * a - sin * c) - dx) * rotateMix; - boneY = boneY + (length * (sin * a + cos * c) - dy) * rotateMix; + boneX = boneX + (length * (cos * a - sin * c) - dx) * rotateMix + boneY = boneY + (length * (sin * a + cos * c) - dy) * rotateMix else r = r + offsetRotation end @@ -249,7 +249,7 @@ function PathConstraint:computeWorldPositions (path, spacesCount, tangents, perc if not path.constantSpeed then local lengths = path.lengths if closed then curveCount = curveCount - 1 else curveCount = curveCount - 2 end - local pathLength = lengths[curveCount + 1]; + local pathLength = lengths[curveCount + 1] if percentPosition then position = position * pathLength end if percentSpacing then i = 1 @@ -258,12 +258,12 @@ function PathConstraint:computeWorldPositions (path, spacesCount, tangents, perc i = i + 1 end end - world = utils.setArraySize(self.world, 8); + world = utils.setArraySize(self.world, 8) i = 0 local o = 0 local curve = 0 while i < spacesCount do - local space = spaces[i + 1]; + local space = spaces[i + 1] position = position + space local p = position @@ -331,14 +331,14 @@ function PathConstraint:computeWorldPositions (path, spacesCount, tangents, perc world[verticesLength - 1 + 1] = world[1 + 1] else curveCount = curveCount - 1 - verticesLength = verticesLength - 4; + verticesLength = verticesLength - 4 world = utils.setArraySize(self.world, verticesLength) path:computeWorldVertices(target, 2, verticesLength, world, 0, 2) end -- Curve lengths. local curves = utils.setArraySize(self.curves, curveCount) - local pathLength = 0; + local pathLength = 0 local x1 = world[0 + 1] local y1 = world[1 + 1] local cx1 = 0 @@ -392,7 +392,7 @@ function PathConstraint:computeWorldPositions (path, spacesCount, tangents, perc if percentPosition then position = position * pathLength else - position = position * pathLength / path.lengths[curveCount]; + position = position * pathLength / path.lengths[curveCount] end if percentSpacing then i = 1 @@ -496,7 +496,7 @@ function PathConstraint:computeWorldPositions (path, spacesCount, tangents, perc local prev = segments[segment - 1 + 1] p = segment + (p - prev) / (length - prev) end - break; + break end segment = segment + 1 end @@ -536,7 +536,7 @@ function PathConstraint:addCurvePosition(p, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out[o + 1] = x1 out[o + 2] = y1 out[o + 3] = math_atan2(cy1 - y1, cx1 - x1) - return; + return end local tt = p * p local ttt = tt * p diff --git a/spine-lua/spine-lua/RegionAttachment.lua b/spine-lua/spine-lua/RegionAttachment.lua deleted file mode 100644 index 8d00bc7a9..000000000 --- a/spine-lua/spine-lua/RegionAttachment.lua +++ /dev/null @@ -1,100 +0,0 @@ -------------------------------------------------------------------------------- --- Spine Runtimes License Agreement --- Last updated January 1, 2020. Replaces all prior versions. --- --- Copyright (c) 2013-2020, Esoteric Software LLC --- --- Integration of the Spine Runtimes into software or otherwise creating --- derivative works of the Spine Runtimes is permitted under the terms and --- conditions of Section 2 of the Spine Editor License Agreement: --- http://esotericsoftware.com/spine-editor-license --- --- Otherwise, it is permitted to integrate the Spine Runtimes into software --- or otherwise create derivative works of the Spine Runtimes (collectively, --- "Products"), provided that each user of the Products must obtain their own --- Spine Editor license and redistribution of the Products in any form must --- include this license and copyright notice. --- --- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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 --- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- - -local AttachmentType = require "spine-lua.AttachmentType" - -local RegionAttachment = {} -function RegionAttachment.new (name) - if not name then error("name cannot be nil", 2) end - - local self = { - name = name, - type = AttachmentType.region, - x = 0, y = 0, - rotation = 0, - scaleX = 1, scaleY = 1, - width = 0, height = 0, - offset = {}, - uvs = {}, - r = 1, g = 1, b = 1, a = 1, - path = nil, - rendererObject = nil, - regionOffsetX = 0, regionOffsetY = 0, - regionWidth = 0, regionHeight = 0, - regionOriginalWidth = 0, regionOriginalHeight = 0 - } - - function self:updateOffset () - local regionScaleX = self.width / self.regionOriginalWidth * self.scaleX - local regionScaleY = self.height / self.regionOriginalHeight * self.scaleY - local localX = -self.width / 2 * self.scaleX + self.regionOffsetX * regionScaleX - local localY = -self.height / 2 * self.scaleY + self.regionOffsetY * regionScaleY - local localX2 = localX + self.regionWidth * regionScaleX - local localY2 = localY + self.regionHeight * regionScaleY - local radians = self.rotation * math.pi / 180 - local cos = math.cos(radians) - local sin = math.sin(radians) - local localXCos = localX * cos + self.x - local localXSin = localX * sin - local localYCos = localY * cos + self.y - local localYSin = localY * sin - local localX2Cos = localX2 * cos + self.x - local localX2Sin = localX2 * sin - local localY2Cos = localY2 * cos + self.y - local localY2Sin = localY2 * sin - local offset = self.offset - offset[0] = localXCos - localYSin -- X1 - offset[1] = localYCos + localXSin -- Y1 - offset[2] = localXCos - localY2Sin -- X2 - offset[3] = localY2Cos + localXSin -- Y2 - offset[4] = localX2Cos - localY2Sin -- X3 - offset[5] = localY2Cos + localX2Sin -- Y3 - offset[6] = localX2Cos - localYSin -- X4 - offset[7] = localYCos + localX2Sin -- Y4 - end - - function self:computeWorldVertices (x, y, bone, worldVertices) - x = x + bone.worldX - y = y + bone.worldY - local m00, m01, m10, m11 = bone.m00, bone.m01, bone.m10, bone.m11 - local offset = self.offset - local vertices = self.vertices; - vertices[0] = offset[0] * m00 + offset[1] * m01 + x - vertices[1] = offset[0] * m10 + offset[1] * m11 + y - vertices[2] = offset[2] * m00 + offset[3] * m01 + x - vertices[3] = offset[2] * m10 + offset[3] * m11 + y - vertices[4] = offset[4] * m00 + offset[5] * m01 + x - vertices[5] = offset[4] * m10 + offset[5] * m11 + y - vertices[6] = offset[6] * m00 + offset[7] * m01 + x - vertices[7] = offset[6] * m10 + offset[7] * m11 + y - end - - return self -end -return RegionAttachment diff --git a/spine-lua/spine-lua/Skeleton.lua b/spine-lua/spine-lua/Skeleton.lua index 50379f371..cee3b0fa8 100644 --- a/spine-lua/spine-lua/Skeleton.lua +++ b/spine-lua/spine-lua/Skeleton.lua @@ -460,7 +460,7 @@ function Skeleton:setSkinByReference(newSkin) end function Skeleton:getAttachment (slotName, attachmentName) - return self:getAttachmentByIndex(self.data.slotNameIndices[slotName], attachmentName) + return self:getAttachmentByIndex(self.data.nameToSlot[slotName].index, attachmentName) end function Skeleton:getAttachmentByIndex (slotIndex, attachmentName) @@ -520,7 +520,7 @@ 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 drawOrder = self.drawOrder local minX = 99999999 local minY = 99999999 local maxX = -99999999 diff --git a/spine-lua/spine-lua/SkeletonClipping.lua b/spine-lua/spine-lua/SkeletonClipping.lua index 0e6240c0b..92a5d9450 100644 --- a/spine-lua/spine-lua/SkeletonClipping.lua +++ b/spine-lua/spine-lua/SkeletonClipping.lua @@ -118,7 +118,7 @@ function SkeletonClipping:clipTriangles(vertices, uvs, triangles, trianglesLengt local u2 = uvs[vertexOffset] local v2 = uvs[vertexOffset + 1] - vertexOffset = (triangles[i + 2] - 1) * 2 + 1; + vertexOffset = (triangles[i + 2] - 1) * 2 + 1 local x3 = vertices[vertexOffset] local y3 = vertices[vertexOffset + 1] local u3 = uvs[vertexOffset] @@ -135,7 +135,7 @@ function SkeletonClipping:clipTriangles(vertices, uvs, triangles, trianglesLengt local d1 = x3 - x2 local d2 = x1 - x3 local d4 = y3 - y1 - local d = 1 / (d0 * d2 + d1 * (y1 - y3)); + local d = 1 / (d0 * d2 + d1 * (y1 - y3)) local clipOutputCount = clipOutputLength / 2 local clipOutputItems = clipOutput @@ -193,7 +193,7 @@ function SkeletonClipping:clipTriangles(vertices, uvs, triangles, trianglesLengt clippedTrianglesItems[s] = index clippedTrianglesItems[s + 1] = index + 1 clippedTrianglesItems[s + 2] = index + 2 - index = index + 3; + index = index + 3 break end p = p + 1 @@ -246,7 +246,7 @@ function SkeletonClipping:clip(x1, y1, x2, y2, x3, y3, clippingArea, output) local inputX2 = inputVertices[ii + 2] local inputY2 = inputVertices[ii + 3] local side2 = deltaX * (inputY2 - edgeY2) - deltaY * (inputX2 - edgeX2) > 0 - local continue = false; + local continue = false if deltaX * (inputY - edgeY2) - deltaY * (inputX - edgeX2) > 0 then if side2 then -- v1 inside, v2 inside table_insert(output, inputX2) diff --git a/spine-lua/spine-lua/SkeletonData.lua b/spine-lua/spine-lua/SkeletonData.lua index 680f6cbaa..c5024029e 100644 --- a/spine-lua/spine-lua/SkeletonData.lua +++ b/spine-lua/spine-lua/SkeletonData.lua @@ -37,6 +37,7 @@ function SkeletonData.new () name, bones = {}, slots = {}, + nameToSlot = {}, skins = {}, defaultSkin = nil, events = {}, @@ -45,8 +46,7 @@ function SkeletonData.new () transformConstraints = {}, pathConstraints = {}, x, y, width, height, - version, hash, imagesPath, - slotNameIndices = {} + version, hash, imagesPath } setmetatable(self, SkeletonData) @@ -71,15 +71,7 @@ end function SkeletonData:findSlot (slotName) if not slotName then error("slotName cannot be nil.", 2) end - for i,slot in ipairs(self.slots) do - if slot.name == slotName then return slot end - end - return nil -end - -function SkeletonData:findSlotIndex (slotName) - if not slotName then error("slotName cannot be nil.", 2) end - return self.slotNameIndices[slotName] or -1 + return self.nameToSlot[slotName] end function SkeletonData:findSkin (skinName) @@ -130,12 +122,4 @@ function SkeletonData:findPathConstraint (constraintName) return nil end -function SkeletonData:findPathConstraintIndex (constraintName) - if not constraintName then error("constraintName cannot be nil.", 2) end - for i,constraint in ipairs(self.pathConstraints) do - if constraint.name == constraintName then return i end - end - return -1 -end - return SkeletonData diff --git a/spine-lua/spine-lua/SkeletonJson.lua b/spine-lua/spine-lua/SkeletonJson.lua index f9249993d..185ec16d7 100644 --- a/spine-lua/spine-lua/SkeletonJson.lua +++ b/spine-lua/spine-lua/SkeletonJson.lua @@ -65,6 +65,8 @@ function SkeletonJson.new (attachmentLoader) local readAttachment local readAnimation local readCurve + local readTimeline1 + local readTimeline2 local getArray local getValue = function (map, name, default) @@ -83,9 +85,6 @@ function SkeletonJson.new (attachmentLoader) if skeletonMap then skeletonData.hash = skeletonMap["hash"] skeletonData.version = skeletonMap["spine"] - if ("3.8.75" == skeletonData.version) then - error("Unsupported skeleton data, please export with a newer version of Spine.") - end skeletonData.x = skeletonMap["x"] skeletonData.y = skeletonMap["y"] skeletonData.width = skeletonMap["width"] @@ -105,17 +104,25 @@ function SkeletonJson.new (attachmentLoader) if not parent then error("Parent bone not found: " .. parentName) end end local data = BoneData.new(i, boneName, parent) - data.length = getValue(boneMap, "length", 0) * scale; - data.x = getValue(boneMap, "x", 0) * scale; - data.y = getValue(boneMap, "y", 0) * scale; - data.rotation = getValue(boneMap, "rotation", 0); - data.scaleX = getValue(boneMap, "scaleX", 1); - data.scaleY = getValue(boneMap, "scaleY", 1); - data.shearX = getValue(boneMap, "shearX", 0); - data.shearY = getValue(boneMap, "shearY", 0); + data.length = getValue(boneMap, "length", 0) * scale + data.x = getValue(boneMap, "x", 0) * scale + data.y = getValue(boneMap, "y", 0) * scale + data.rotation = getValue(boneMap, "rotation", 0) + data.scaleX = getValue(boneMap, "scaleX", 1) + data.scaleY = getValue(boneMap, "scaleY", 1) + data.shearX = getValue(boneMap, "shearX", 0) + data.shearY = getValue(boneMap, "shearY", 0) data.transformMode = TransformMode[getValue(boneMap, "transform", "normal")] data.skinRequired = getValue(boneMap, "skin", false) + local color = boneMap["color"] + if color then + data.color = Color.newWith(tonumber(color:sub(1, 2), 16) / 255, + tonumber(color:sub(3, 4), 16) / 255, + tonumber(color:sub(5, 6), 16) / 255, + tonumber(color:sub(7, 8), 16) / 255) + end + table_insert(skeletonData.bones, data) end @@ -138,18 +145,17 @@ function SkeletonJson.new (attachmentLoader) local dark = slotMap["dark"] if dark then - data.darkColor = Color.newWith(1, 1, 1, 1) - data.darkColor:set(tonumber(dark:sub(1, 2), 16) / 255, + data.darkColor = Color.newWith( + tonumber(dark:sub(1, 2), 16) / 255, tonumber(dark:sub(3, 4), 16) / 255, - tonumber(dark:sub(5, 6), 16) / 255, - 0) + tonumber(dark:sub(5, 6), 16) / 255, 0) end data.attachmentName = getValue(slotMap, "attachment", nil) data.blendMode = BlendMode[getValue(slotMap, "blend", "normal")] table_insert(skeletonData.slots, data) - skeletonData.slotNameIndices[data.name] = #skeletonData.slots + skeletonData.nameToSlot[data.name] = data end end @@ -204,17 +210,19 @@ function SkeletonJson.new (attachmentLoader) data.local_ = getValue(constraintMap, "local", false) data.relative = getValue(constraintMap, "relative", false) - data.offsetRotation = getValue(constraintMap, "rotation", 0); - data.offsetX = getValue(constraintMap, "x", 0) * scale; - data.offsetY = getValue(constraintMap, "y", 0) * scale; - data.offsetScaleX = getValue(constraintMap, "scaleX", 0); - data.offsetScaleY = getValue(constraintMap, "scaleY", 0); - data.offsetShearY = getValue(constraintMap, "shearY", 0); + data.offsetRotation = getValue(constraintMap, "rotation", 0) + data.offsetX = getValue(constraintMap, "x", 0) * scale + data.offsetY = getValue(constraintMap, "y", 0) * scale + data.offsetScaleX = getValue(constraintMap, "scaleX", 0) + data.offsetScaleY = getValue(constraintMap, "scaleY", 0) + data.offsetShearY = getValue(constraintMap, "shearY", 0) - data.rotateMix = getValue(constraintMap, "rotateMix", 1); - data.translateMix = getValue(constraintMap, "translateMix", 1); - data.scaleMix = getValue(constraintMap, "scaleMix", 1); - data.shearMix = getValue(constraintMap, "shearMix", 1); + data.mixRotate = getValue(constraintMap, "rotateMix", 1) + data.mixX = getValue(constraintMap, "mixX", 1) + data.mixY = getValue(constraintMap, "mixY", data.mixX) + data.mixScaleX = getValue(constraintMap, "mixScaleX", 1) + data.mixScaleY = getValue(constraintMap, "mixScaleY", data.mixScaleX) + data.mixShearY = getValue(constraintMap, "mixShearY", 1) table_insert(skeletonData.transformConstraints, data) end @@ -222,8 +230,8 @@ function SkeletonJson.new (attachmentLoader) -- Path constraints if root["path"] then - for _,constraintMap in ipairs(root.path) do - local data = PathConstraintData.new(constraintMap.name); + for _,constraintMap in ipairs(root["path"]) do + local data = PathConstraintData.new(constraintMap.name) data.order = getValue(constraintMap, "order", 0) data.skinRequired = getValue(constraintMap, "skin", false) @@ -233,20 +241,21 @@ function SkeletonJson.new (attachmentLoader) table_insert(data.bones, bone) end - local targetName = constraintMap.target; + local targetName = constraintMap.target data.target = skeletonData:findSlot(targetName) if data.target == nil then error("Path target slot not found: " .. targetName, 2) end data.positionMode = PathConstraintData.PositionMode[getValue(constraintMap, "positionMode", "percent"):lower()] data.spacingMode = PathConstraintData.SpacingMode[getValue(constraintMap, "spacingMode", "length"):lower()] data.rotateMode = PathConstraintData.RotateMode[getValue(constraintMap, "rotateMode", "tangent"):lower()] - data.offsetRotation = getValue(constraintMap, "rotation", 0); - data.position = getValue(constraintMap, "position", 0); + data.offsetRotation = getValue(constraintMap, "rotation", 0) + data.position = getValue(constraintMap, "position", 0) if data.positionMode == PathConstraintData.PositionMode.fixed then data.position = data.position * scale end - data.spacing = getValue(constraintMap, "spacing", 0); + data.spacing = getValue(constraintMap, "spacing", 0) if data.spacingMode == PathConstraintData.SpacingMode.length or data.spacingMode == PathConstraintData.SpacingMode.fixed then data.spacing = data.spacing * scale end - data.rotateMix = getValue(constraintMap, "rotateMix", 1); - data.translateMix = getValue(constraintMap, "translateMix", 1); + data.mixRotate = getValue(constraintMap, "mixRotate", 1) + data.mixX = getValue(constraintMap, "mixX", 1) + data.mixY = getValue(constraintMap, "mixY", data.mixX) table_insert(skeletonData.pathConstraints, data) end @@ -290,7 +299,7 @@ function SkeletonJson.new (attachmentLoader) end for slotName,slotMap in pairs(skinMap.attachments) do - local slotIndex = skeletonData.slotNameIndices[slotName] + local slotIndex = skeletonData:findSlot(slotName).index for attachmentName,attachmentMap in pairs(slotMap) do local attachment = readAttachment(attachmentMap, skin, slotIndex, attachmentName, skeletonData) if attachment then @@ -360,11 +369,11 @@ function SkeletonJson.new (attachmentLoader) region.path = path region.x = getValue(map, "x", 0) * scale region.y = getValue(map, "y", 0) * scale - region.scaleX = getValue(map, "scaleX", 1); - region.scaleY = getValue(map, "scaleY", 1); - region.rotation = getValue(map, "rotation", 0); - region.width = map.width * scale; - region.height = map.height * scale; + region.scaleX = getValue(map, "scaleX", 1) + region.scaleY = getValue(map, "scaleY", 1) + region.rotation = getValue(map, "rotation", 0) + region.width = map.width * scale + region.height = map.height * scale local color = map["color"] if color then @@ -453,7 +462,7 @@ function SkeletonJson.new (attachmentLoader) tonumber(color:sub(5, 6), 16) / 255, tonumber(color:sub(7, 8), 16) / 255) end - return path; + return path elseif type == AttachmentType.point then local point = self.attachmentLoader:newPointAttachment(skin, name) @@ -536,124 +545,239 @@ function SkeletonJson.new (attachmentLoader) readAnimation = function (map, name, skeletonData) local timelines = {} - local duration = 0 local scale = self.scale - -- Slot timelines + -- Slot timelines. local slotsMap = map["slots"] if slotsMap then - for slotName,timelineMap in pairs(slotsMap) do - local slotIndex = skeletonData.slotNameIndices[slotName] - - for timelineName,values in pairs(timelineMap) do - if timelineName == "color" then - local timeline = Animation.ColorTimeline.new(#values) - timeline.slotIndex = slotIndex - - local frameIndex = 0 - for _,valueMap in ipairs(values) do - local color = valueMap["color"] - timeline:setFrame( - frameIndex, getValue(valueMap, "time", 0), - tonumber(color:sub(1, 2), 16) / 255, - tonumber(color:sub(3, 4), 16) / 255, - tonumber(color:sub(5, 6), 16) / 255, - tonumber(color:sub(7, 8), 16) / 255 - ) - readCurve(valueMap, timeline, frameIndex) - frameIndex = frameIndex + 1 - end - table_insert(timelines, timeline) - duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.ColorTimeline.ENTRIES]) - elseif timelineName == "twoColor" then - local timeline = Animation.TwoColorTimeline.new(#values) - timeline.slotIndex = slotIndex - - local frameIndex = 0 - for _,valueMap in ipairs(values) do - local light = valueMap["light"] - local dark = valueMap["dark"] - timeline:setFrame( - frameIndex, getValue(valueMap, "time", 0), - tonumber(light:sub(1, 2), 16) / 255, - tonumber(light:sub(3, 4), 16) / 255, - tonumber(light:sub(5, 6), 16) / 255, - tonumber(light:sub(7, 8), 16) / 255, - tonumber(dark:sub(1, 2), 16) / 255, - tonumber(dark:sub(3, 4), 16) / 255, - tonumber(dark:sub(5, 6), 16) / 255 - ) - readCurve(valueMap, timeline, frameIndex) - frameIndex = frameIndex + 1 - end - table_insert(timelines, timeline) - duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.TwoColorTimeline.ENTRIES]) + for slotName,slotMap in pairs(slotsMap) do + local slotIndex = skeletonData:findSlot(slotName).index + for timelineName,timelineMap in pairs(slotMap) do + if not timelineMap then elseif timelineName == "attachment" then - local timeline = Animation.AttachmentTimeline.new(#values) - timeline.slotIndex = slotIndex - - local frameIndex = 0 - for _,valueMap in ipairs(values) do - local attachmentName = valueMap["name"] - timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), attachmentName) - frameIndex = frameIndex + 1 + local timeline = Animation.AttachmentTimeline.new(#timelineMap, slotIndex) + for i,keyMap in ipairs(timelineMap) do + timeline:setFrame(i + 1, getValue(keyMap, "time", 0), keyMap["name"]) + end + table_insert(timelines, timeline) + elseif timelineName == "rgba" then + local timeline = Animation.RGBATimeline.new(#timelineMap, #timelineMap * 4, slotIndex) + local keyMap = timelineMap[1] + local time = getValue(keyMap, "time", 0) + local color = keyMap["color"] + local r = tonumber(color:sub(1, 2), 16) / 255 + local g = tonumber(color:sub(3, 4), 16) / 255 + local b = tonumber(color:sub(5, 6), 16) / 255 + local a = tonumber(color:sub(7, 8), 16) / 255 + local bezier = 0 + for i,keyMap in ipairs(timelineMap) do + local frame = i - 1 + timeline:setFrame(frame, time, r, g, b, a) + local nextMap = timelineMap[i + 1] + if not nextMap then + timeline:shrink(bezier) + break + end + local time2 = getValue(nextMap, "time", 0) + color = nextMap["color"] + local nr = tonumber(color:sub(1, 2), 16) / 255 + local ng = tonumber(color:sub(3, 4), 16) / 255 + local nb = tonumber(color:sub(5, 6), 16) / 255 + local na = tonumber(color:sub(7, 8), 16) / 255 + local curve = keyMap.curve + if curve then + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1) + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1) + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1) + bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, a, na, 1) + end + time = time2 + r = nr + g = ng + b = nb + a = na + end + table_insert(timelines, timeline) + elseif timelineName == "rgb" then + local timeline = Animation.RGBTimeline.new(#timelineMap, #timelineMap * 3, slotIndex) + local keyMap = timelineMap[1] + local time = getValue(keyMap, "time", 0) + local color = keyMap["color"] + local r = tonumber(color:sub(1, 2), 16) / 255 + local g = tonumber(color:sub(3, 4), 16) / 255 + local b = tonumber(color:sub(5, 6), 16) / 255 + local bezier = 0 + for i,keyMap in ipairs(timelineMap) do + local frame = i - 1 + timeline:setFrame(frame, time, r, g, b) + local nextMap = timelineMap[i + 1] + if not nextMap then + timeline:shrink(bezier) + break + end + local time2 = getValue(nextMap, "time", 0) + color = nextMap["color"] + local nr = tonumber(color:sub(1, 2), 16) / 255 + local ng = tonumber(color:sub(3, 4), 16) / 255 + local nb = tonumber(color:sub(5, 6), 16) / 255 + local curve = keyMap.curve + if curve then + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1) + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1) + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1) + end + time = time2 + r = nr + g = ng + b = nb + end + table_insert(timelines, timeline) + elseif timelineName == "alpha" then + table_insert(timelines, readTimeline1(timelineMap, Animation.AlphaTimeline.new(#timelineMap, #timelineMap, slotIndex), 0, 1)) + elseif timelineName == "rgba2" then + local timeline = Animation.RGBA2Timeline.new(#timelineMap, #timelineMap * 7, slotIndex) + local keyMap = timelineMap[1] + local time = getValue(keyMap, "time", 0) + local color = keyMap["light"] + local r = tonumber(color:sub(1, 2), 16) / 255 + local g = tonumber(color:sub(3, 4), 16) / 255 + local b = tonumber(color:sub(5, 6), 16) / 255 + local a = tonumber(color:sub(7, 8), 16) / 255 + color = keyMap["dark"] + local r2 = tonumber(color:sub(1, 2), 16) / 255 + local g2 = tonumber(color:sub(3, 4), 16) / 255 + local b2 = tonumber(color:sub(5, 6), 16) / 255 + local bezier = 0 + for i,keyMap in ipairs(timelineMap) do + local frame = i - 1 + timeline:setFrame(frame, time, r, g, b, a, r2, g2, b2) + local nextMap = timelineMap[i + 1] + if not nextMap then + timeline:shrink(bezier) + break + end + local time2 = getValue(nextMap, "time", 0) + color = nextMap["light"] + local nr = tonumber(color:sub(1, 2), 16) / 255 + local ng = tonumber(color:sub(3, 4), 16) / 255 + local nb = tonumber(color:sub(5, 6), 16) / 255 + local na = tonumber(color:sub(7, 8), 16) / 255 + color = nextMap["dark"] + local nr2 = tonumber(color:sub(1, 2), 16) / 255 + local ng2 = tonumber(color:sub(3, 4), 16) / 255 + local nb2 = tonumber(color:sub(5, 6), 16) / 255 + local curve = keyMap.curve + if curve then + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1) + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1) + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1) + bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, a, na, 1) + bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, r2, nr2, 1) + bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, g2, ng2, 1) + bezier = readCurve(curve, timeline, bezier, frame, 6, time, time2, b2, nb2, 1) + end + time = time2 + r = nr + g = ng + b = nb + a = na + r2 = nr2 + g2 = ng2 + b2 = nb2 + end + table_insert(timelines, timeline) + elseif timelineName == "rgb2" then + local timeline = Animation.RGB2Timeline.new(#timelineMap, #timelineMap * 6, slotIndex) + local keyMap = timelineMap[1] + local time = getValue(keyMap, "time", 0) + local color = keyMap["light"] + local r = tonumber(color:sub(1, 2), 16) / 255 + local g = tonumber(color:sub(3, 4), 16) / 255 + local b = tonumber(color:sub(5, 6), 16) / 255 + color = keyMap["dark"] + local r2 = tonumber(color:sub(1, 2), 16) / 255 + local g2 = tonumber(color:sub(3, 4), 16) / 255 + local b2 = tonumber(color:sub(5, 6), 16) / 255 + local bezier = 0 + for i,keyMap in ipairs(timelineMap) do + local frame = i - 1 + timeline:setFrame(frame, time, r, g, b, r2, g2, b2) + local nextMap = timelineMap[i + 1] + if not nextMap then + timeline:shrink(bezier) + break + end + local time2 = getValue(nextMap, "time", 0) + color = nextMap["light"] + local nr = tonumber(color:sub(1, 2), 16) / 255 + local ng = tonumber(color:sub(3, 4), 16) / 255 + local nb = tonumber(color:sub(5, 6), 16) / 255 + color = nextMap["dark"] + local nr2 = tonumber(color:sub(1, 2), 16) / 255 + local ng2 = tonumber(color:sub(3, 4), 16) / 255 + local nb2 = tonumber(color:sub(5, 6), 16) / 255 + local curve = keyMap.curve + if curve then + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1) + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1) + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1) + bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, r2, nr2, 1) + bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, g2, ng2, 1) + bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, b2, nb2, 1) + end + time = time2 + r = nr + g = ng + b = nb + r2 = nr2 + g2 = ng2 + b2 = nb2 end table_insert(timelines, timeline) - duration = math.max(duration, timeline.frames[timeline:getFrameCount() - 1]) - else - error("Invalid frame type for a slot: " .. timelineName .. " (" .. slotName .. ")") + error("Invalid timeline type for a slot: " .. timelineName .. " (" .. slotName .. ")") end end end end - -- Bone timelines + -- Bone timelines. local bonesMap = map["bones"] if bonesMap then - for boneName,timelineMap in pairs(bonesMap) do + for boneName,boneMap in pairs(bonesMap) do local boneIndex = skeletonData:findBoneIndex(boneName) if boneIndex == -1 then error("Bone not found: " .. boneName) end - - for timelineName,values in pairs(timelineMap) do - if timelineName == "rotate" then - local timeline = Animation.RotateTimeline.new(#values) - timeline.boneIndex = boneIndex - - local frameIndex = 0 - for _,valueMap in ipairs(values) do - timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), getValue(valueMap, "angle", 0)) - readCurve(valueMap, timeline, frameIndex) - frameIndex = frameIndex + 1 - end - table_insert(timelines, timeline) - duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.RotateTimeline.ENTRIES]) - - elseif timelineName == "translate" or timelineName == "scale" or timelineName == "shear" then - local timeline - local timelineScale = 1 - local defaultValue = 0 - if timelineName == "scale" then - timeline = Animation.ScaleTimeline.new(#values) - defaultValue = 1 - elseif timelineName == "shear" then - timeline = Animation.ShearTimeline.new(#values) - else - timeline = Animation.TranslateTimeline.new(#values) - timelineScale = self.scale - end - timeline.boneIndex = boneIndex - - local frameIndex = 0 - for _,valueMap in ipairs(values) do - local x = getValue(valueMap, "x", defaultValue) * timelineScale - local y = getValue(valueMap, "y", defaultValue) * timelineScale - timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), x, y) - readCurve(valueMap, timeline, frameIndex) - frameIndex = frameIndex + 1 - end - table_insert(timelines, timeline) - duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.TranslateTimeline.ENTRIES]) + for timelineName,timelineMap in pairs(boneMap) do + if not timelineMap then + elseif timelineName == "rotate" then + table_insert(timelines, readTimeline1(timelineMap, Animation.RotateTimeline.new(#timelineMap, #timelineMap, boneIndex), 0, 1)) + elseif timelineName == "translate" then + local timeline = Animation.TranslateTimeline.new(#timelineMap, #timelineMap * 2, boneIndex) + table_insert(timelines, readTimeline2(timelineMap, timeline, "x", "y", 0, scale)) + elseif timelineName == "translatex" then + local timeline = Animation.TranslateXTimeline.new(#timelineMap, #timelineMap, boneIndex) + table_insert(timelines, readTimeline1(timelineMap, timeline, 0, scale)) + elseif timelineName == "translatey" then + local timeline = Animation.TranslateYTimeline.new(#timelineMap, #timelineMap, boneIndex) + table_insert(timelines, readTimeline1(timelineMap, timeline, 0, scale)) + elseif timelineName == "scale" then + local timeline = Animation.ScaleTimeline.new(#timelineMap, #timelineMap * 2, boneIndex) + table_insert(timelines, readTimeline2(timelineMap, "x", "y", 1, 1)) + elseif timelineName == "scalex" then + local timeline = Animation.ScaleXTimeline.new(#timelineMap, #timelineMap, boneIndex) + table_insert(timelines, readTimeline1(timelineMap, timeline, 1, 1)) + elseif timelineName == "scaley" then + local timeline = Animation.ScaleYTimeline.new(#timelineMap, #timelineMap, boneIndex) + table_insert(timelines, readTimeline1(timelineMap, timeline, 1, 1)) + elseif timelineName == "shear" then + local timeline = Animation.ShearTimeline.new(#timelineMap, #timelineMap * 2, boneIndex) + table_insert(timelines, readTimeline2(timelineMap, "x", "y", 0, 1)) + elseif timelineName == "shearx" then + local timeline = Animation.ShearXTimeline.new(#timelineMap, #timelineMap, boneIndex) + table_insert(timelines, readTimeline1(timelineMap, timeline, 0, 1)) + elseif timelineName == "sheary" then + local timeline = Animation.ShearYTimeline.new(#timelineMap, #timelineMap, boneIndex) + table_insert(timelines, readTimeline1(timelineMap, timeline, 0, 1)) else error("Invalid timeline type for a bone: " .. timelineName .. " (" .. boneName .. ")") end @@ -664,96 +788,169 @@ function SkeletonJson.new (attachmentLoader) -- IK timelines. local ik = map["ik"] if ik then - for ikConstraintName,values in pairs(ik) do - local ikConstraint = skeletonData:findIkConstraint(ikConstraintName) - local timeline = Animation.IkConstraintTimeline.new(#values) - for i,other in pairs(skeletonData.ikConstraints) do - if other == ikConstraint then - timeline.ikConstraintIndex = i - break + for constraintName,timelineMap in pairs(ik) do + local keyMap = timelineMap[1] + if keyMap then + local constraintIndex = -1 + for i,other in pairs(skeletonData.ikConstraints) do + if other.name == constraintName then + constraintIndex = i + break + end + end + local timeline = Animation.IkConstraintTimeline.new(#timelineMap, #timelineMap * 2, constraintIndex) + local time = getValue(keyMap, "time", 0) + local mix = getValue(keyMap, "mix", 1) + local softness = getValue(keyMap, "softness", 0) * scale + local bezier = 0 + for i,keyMap in ipairs(timelineMap) do + local frame = i - 1 + local bendPositive = 1 + local compress = false + local stretch = false + if keyMap["bendPositive"] == false then bendPositive = -1 end + if keyMap["compress"] ~= nil then compress = keyMap["compress"] end + if keyMap["stretch"] ~= nil then stretch = keyMap["stretch"] end + timeline:setFrame(frame, time, mix, softness, bendPositive, compress, stretch) + local nextMap = timelineMap[i + 1] + if not nextMap then + timeline:shrink(bezier) + break + end + local time2 = getValue(nextMap, "time", 0) + color = nextMap["color"] + local mix2 = getValue(nextMap, "mix", 1) + local softness2 = getValue(nextMap, "softness", 0) * scale + local curve = keyMap.curve + if curve then + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mix, mix2, 1) + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, softness, softness2, scale) + end + time = time2 + mix = mix2 + softness = softness2 end end - local frameIndex = 0 - for _,valueMap in ipairs(values) do - local mix = 1 - local softness = 0 - if valueMap["mix"] ~= nil then mix = valueMap["mix"] end - if valueMap["softness"] ~= nil then softness = valueMap["softness"] * scale end - local bendPositive = 1 - if valueMap["bendPositive"] == false then bendPositive = -1 end - local stretch = false - if valueMap["stretch"] ~= nil then stretch = valueMap["stretch"] end - local compress = false - if valueMap["compress"] ~= nil then compress = valueMap["compress"] end - timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), mix, softness, bendPositive, compress, stretch) - readCurve(valueMap, timeline, frameIndex) - frameIndex = frameIndex + 1 - end table_insert(timelines, timeline) - duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.IkConstraintTimeline.ENTRIES]) end end -- Transform constraint timelines. local transform = map["transform"] if transform then - for constraintName, values in pairs(transform) do - local constraint = skeletonData:findTransformConstraint(constraintName) - local timeline = Animation.TransformConstraintTimeline.new(#values) - for i,other in pairs(skeletonData.transformConstraints) do - if other == constraint then - timeline.transformConstraintIndex = i - break + for constraintName, timelineMap in pairs(transform) do + local keyMap = timelineMap[1] + if keyMap then + local constraintIndex = -1 + for i,other in pairs(skeletonData.transformConstraints) do + if other.name == constraintName then + constraintIndex = i + break + end end + local timeline = Animation.TransformConstraintTimeline.new(#timelineMap, #timelineMap * 4, constraintIndex) + local time = getValue(keyMap, "time", 0) + local mixRotate = getValue(keyMap, "mixRotate", 0) + local mixX = getValue(keyMap, "mixX", 1) + local mixY = getValue(keyMap, "mixY", mixX) + local mixScaleX = getValue(keyMap, "mixScaleX", 1) + local mixScaleY = getValue(keyMap, "mixScaleY", mixScaleX) + local mixShearY = getValue(keyMap, "mixShearY", 1) + local bezier = 0 + for i,keyMap in ipairs(timelineMap) do + local frame = i - 1 + timeline:setFrame(frame, time, mixRotate, mixX, mixY, mixScaleX, mixScaleY, mixShearY) + local nextMap = timelineMap[frame + 1] + if not nextMap then + timeline:shrink(bezier) + break + end + local time2 = getValue(nextMap, "time", 0) + local mixRotate2 = getValue(nextMap, "mixRotate", 1) + local mixX2 = getValue(nextMap, "mixX", 1) + local mixY2 = getValue(nextMap, "mixY", mixX2) + local mixScaleX2 = getValue(nextMap, "mixScaleX", 1) + local mixScaleY2 = getValue(nextMap, "mixScaleY", mixScaleX2) + local mixShearY2 = getValue(nextMap, "mixShearY", 1) + local curve = keyMap.curve + if curve then + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mixRotate, mixRotate2, 1) + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, mixX, mixX2, 1) + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, mixY, mixY2, 1) + bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, mixScaleX, mixScaleX2, 1) + bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, mixScaleY, mixScaleY2, 1) + bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, mixShearY, mixShearY2, 1) + end + time = time2 + mixRotate = mixRotate2 + mixX = mixX2 + mixY = mixY2 + mixScaleX = mixScaleX2 + mixScaleY = mixScaleY2 + mixScaleX = mixScaleX2 + end + table_insert(timelines, timeline) end - local frameIndex = 0 - for _,valueMap in ipairs(values) do - timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), getValue(valueMap, "rotateMix", 1), getValue(valueMap, "translateMix", 1), getValue(valueMap, "scaleMix", 1), getValue(valueMap, "shearMix", 1)) - readCurve(valueMap, timeline, frameIndex) - frameIndex = frameIndex + 1 - end - table_insert(timelines, timeline) - duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.TransformConstraintTimeline.ENTRIES]) end end -- Path constraint timelines. if map.path then for constraintName,constraintMap in pairs(map.path) do - local index = skeletonData:findPathConstraintIndex(constraintName) - if index == -1 then error("Path constraint not found: " .. constraintName, 2) end - local data = skeletonData.pathConstraints[index] + local constraint, constraintIndex = -1 + for i,other in pairs(skeletonData.transformConstraints) do + if other.name == constraintName then + constraintIndex = i + constraint = other + break + end + end for timelineName, timelineMap in pairs(constraintMap) do - if timelineName == "position" or timelineName == "spacing" then - local timeline = nil - local timelineScale = 1 - if timelineName == "spacing" then - timeline = Animation.PathConstraintSpacingTimeline.new(#timelineMap) - if data.spacingMode == PathConstraintData.SpacingMode.length or data.spacingMode == PathConstraintData.SpacingMode.fixed then timelineScale = scale end - else - timeline = Animation.PathConstraintPositionTimeline.new(#timelineMap) - if data.positionMode == PathConstraintData.PositionMode.fixed then timelineScale = scale end + local keyMap = timelineMap[1] + if keyMap then + if timelineName == "position" then + local timeline = Animation.PathConstraintPositionTimeline.new(#timelineMap, #timelineMap, constraintIndex) + local timelineScale = 1 + if constraint.positionMode == PositionMode.fixed then timelineScale = scale end + table_insert(timelines, readTimeline1(timelineMap, timeline, 0, timelineScale)) + elseif timelineName == "spacing" then + local timeline = Animation.PathConstraintSpacingTimeline.new(#timelineMap, #timelineMap, constraintIndex) + local timelineScale = 1; + if data.spacingMode == SpacingMode.Length or data.spacingMode == SpacingMode.Fixed then timelineScale = scale end + table_insert(timelines, readTimeline1(timelineMap, timeline, 0, timelineScale)) + elseif timelineName == "mix" then + local timeline = Animation.PathConstraintMixTimeline.new(#timelineMap, #timelineMap * 3, constraintIndex) + local time = getValue(keyMap, "time", 0) + local mixRotate = getValue(keyMap, "mixRotate", 1) + local mixX = getValue(keyMap, "mixX", 1) + local mixY = getValue(keyMap, "mixY", mixX) + local bezier = 0 + for i,keyMap in ipairs(timelineMap) do + local frame = i - 1 + timeline:setFrame(frame, time, mixRotate, mixX, mixY) + local nextMap = timelineMap[frame + 1] + if not nextMap then + timeline:shrink(bezier) + break + end + local time2 = getValue(nextMap, "time", 0) + local mixRotate2 = getValue(nextMap, "mixRotate", 1) + local mixX2 = getValue(nextMap, "mixX", 1) + local mixY2 = getValue(nextMap, "mixY", mixX2) + local curve = keyMap.curve + if curve then + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mixRotate, mixRotate2, 1) + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, mixX, mixX2, 1) + bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, mixY, mixY2, 1) + end + time = time2 + mixRotate = mixRotate2 + mixX = mixX2 + mixY = mixY2 + keyMap = nextMap + end + table_insert(timelines, timeline) end - timeline.pathConstraintIndex = index - local frameIndex = 0 - for _,valueMap in ipairs(timelineMap) do - timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), getValue(valueMap, timelineName, 0) * timelineScale) - readCurve(valueMap, timeline, frameIndex) - frameIndex = frameIndex + 1 - end - table_insert(timelines, timeline) - duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.PathConstraintPositionTimeline.ENTRIES]) - elseif timelineName == "mix" then - local timeline = Animation.PathConstraintMixTimeline.new(#timelineMap) - timeline.pathConstraintIndex = index - local frameIndex = 0 - for _,valueMap in ipairs(timelineMap) do - timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), getValue(valueMap, "rotateMix", 1), getValue(valueMap, "translateMix", 1)) - readCurve(valueMap, timeline, frameIndex) - frameIndex = frameIndex + 1 - end - table_insert(timelines, timeline) - duration = math.max(duration, timeline.frames[(timeline:getFrameCount() - 1) * Animation.PathConstraintMixTimeline.ENTRIES]) end end end @@ -765,68 +962,72 @@ function SkeletonJson.new (attachmentLoader) local skin = skeletonData:findSkin(deformName) if not skin then error("Skin not found: " .. deformName, 2) end for slotName,slotMap in pairs(deformMap) do - local slotIndex = skeletonData:findSlotIndex(slotName) + local slotIndex = skeletonData:findSlot(slotName).index if slotIndex == -1 then error("Slot not found: " .. slotMap.name, 2) end for timelineName,timelineMap in pairs(slotMap) do - local attachment = skin:getAttachment(slotIndex, timelineName) - if not attachment then error("Deform attachment not found: " .. timelineMap.name, 2) end - local weighted = attachment.bones ~= nil - local vertices = attachment.vertices; - local deformLength = #vertices - if weighted then deformLength = math.floor(#vertices / 3) * 2 end + local keyMap = timelineMap[1] + if keyMap then + local attachment = skin:getAttachment(slotIndex, timelineName) + if not attachment then error("Deform attachment not found: " .. timelineMap.name, 2) end + local weighted = attachment.bones ~= nil + local vertices = attachment.vertices + local deformLength = #vertices + if weighted then deformLength = math.floor(deformLength / 3) * 2 end - local timeline = Animation.DeformTimeline.new(#timelineMap) - timeline.slotIndex = slotIndex - timeline.attachment = attachment - - local frameIndex = 0 - for _,valueMap in ipairs(timelineMap) do - local deform = nil - local verticesValue = getValue(valueMap, "vertices", nil) - if verticesValue == nil then - deform = vertices - if weighted then deform = utils.newNumberArray(deformLength) end - else - deform = utils.newNumberArray(deformLength) - local start = getValue(valueMap, "offset", 0) + 1 - utils.arrayCopy(verticesValue, 1, deform, start, #verticesValue) - if scale ~= 1 then - local i = start - local n = i + #verticesValue - while i < n do - deform[i] = deform[i] * scale - i = i + 1 + local timeline = Animation.DeformTimeline.new(#timelineMap, #timelineMap, slotIndex, attachment) + local bezier = 0 + for i,keyMap in ipairs(timelineMap) do + local deform = nil + local verticesValue = getValue(keyMap, "vertices", nil) + if verticesValue == nil then + deform = vertices + if weighted then deform = utils.newNumberArray(deformLength) end + else + deform = utils.newNumberArray(deformLength) + local start = getValue(keyMap, "offset", 0) + 1 + utils.arrayCopy(verticesValue, 1, deform, start, #verticesValue) + if scale ~= 1 then + local i = start + local n = i + #verticesValue + while i < n do + deform[i] = deform[i] * scale + i = i + 1 + end + end + if not weighted then + local i = 1 + local n = i + deformLength + while i < n do + deform[i] = deform[i] + vertices[i] + i = i + 1 + end end end - if not weighted then - local i = 1 - local n = i + deformLength - while i < n do - deform[i] = deform[i] + vertices[i] - i = i + 1 - end + local frame = i - 1 + timeline:setFrame(frame, time, mixRotate, mixX, mixY) + local nextMap = timelineMap[frame + 1] + if not nextMap then + timeline:shrink(bezier) + break end + local time2 = getValue(nextMap, "time", 0) + local curve = keyMap.curve + if curve then bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, 0, 1, 1) end + time = time2 end - - timeline:setFrame(frameIndex, getValue(valueMap, "time", 0), deform) - readCurve(valueMap, timeline, frameIndex) - frameIndex = frameIndex + 1 + table_insert(timelines, timeline) end - table_insert(timelines, timeline) - duration = math.max(duration, timeline.frames[timeline:getFrameCount() - 1]) end end end end - -- Draworder timeline. - local drawOrderValues = map["drawOrder"] - if not drawOrderValues then drawOrderValues = map["draworder"] end - if drawOrderValues then - local timeline = Animation.DrawOrderTimeline.new(#drawOrderValues) + -- Draw order timelines. + if map["drawOrder"] then + local timeline = Animation.DrawOrderTimeline.new(#map["drawOrder"]) local slotCount = #skeletonData.slots - local frameIndex = 0 - for _,drawOrderMap in ipairs(drawOrderValues) do + local frame = 0 + for _,drawOrderMap in ipairs(map["drawOrder"]) do local drawOrder = nil local offsets = drawOrderMap["offsets"] if offsets then @@ -835,7 +1036,7 @@ function SkeletonJson.new (attachmentLoader) local originalIndex = 1 local unchangedIndex = 1 for _,offsetMap in ipairs(offsets) do - local slotIndex = skeletonData:findSlotIndex(offsetMap["slot"]) + local slotIndex = skeletonData:findSlot(offsetMap["slot"]).index if slotIndex == -1 then error("Slot not found: " .. offsetMap["slot"]) end -- Collect unchanged items. while originalIndex ~= slotIndex do @@ -861,18 +1062,17 @@ function SkeletonJson.new (attachmentLoader) end end end - timeline:setFrame(frameIndex, getValue(drawOrderMap, "time", 0), drawOrder) - frameIndex = frameIndex + 1 + timeline:setFrame(frame, getValue(drawOrderMap, "time", 0), drawOrder) + frame = frame + 1 end table_insert(timelines, timeline) - duration = math.max(duration, timeline.frames[timeline:getFrameCount() - 1]) end - -- Event timeline. + -- Event timelines. local events = map["events"] if events then local timeline = Animation.EventTimeline.new(#events) - local frameIndex = 0 + local frame = 0 for _,eventMap in ipairs(events) do local eventData = skeletonData:findEvent(eventMap["name"]) if not eventData then error("Event not found: " .. eventMap["name"]) end @@ -896,26 +1096,91 @@ function SkeletonJson.new (attachmentLoader) event.volume = getValue(eventMap, "volume", 1) event.balance = getValue(eventMap, "balance", 0) end - timeline:setFrame(frameIndex, event) - frameIndex = frameIndex + 1 + timeline:setFrame(frame, event) + frame = frame + 1 end table_insert(timelines, timeline) - duration = math.max(duration, timeline.frames[timeline:getFrameCount() - 1]) end + local duration = 0 + for _,timeline in ipairs(timelines) do + duration = math.max(duration, timeline:getDuration()) + end table_insert(skeletonData.animations, Animation.new(name, timelines, duration)) end - readCurve = function (map, timeline, frameIndex) + readCurve = function (map, timeline, frame) local curve = map["curve"] if not curve then return end if curve == "stepped" then - timeline:setStepped(frameIndex) + timeline:setStepped(frame) else - timeline:setCurve(frameIndex, getValue(map, "curve", 0), getValue(map, "c2", 0), getValue(map, "c3", 1), getValue(map, "c4", 1)) + timeline:setCurve(frame, getValue(map, "curve", 0), getValue(map, "c2", 0), getValue(map, "c3", 1), getValue(map, "c4", 1)) end end + readTimeline1 = function (keys, timeline, defaultValue, scale) + local keyMap = keys[1] + local time = getValue(keyMap, "time", 0) + local value = getValue(keyMap, "value", defaultValue) * scale + local bezier = 0 + for i,keyMap in ipairs(keys) do + local frame = i - 1 + timeline:setFrame(frame, time, value) + local nextMap = keys[frame + 1] + if not nextMap then break end + local time2 = getValue(nextMap, "time", 0) + local value2 = getValue(nextMap, "value", defaultValue) * scale + local curve = keyMap.curve + if curve then bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, value, value2, scale) end + time = time2 + value = value2 + end + timeline:shrink(bezier) + return timeline + end + + readTimeline2 = function (keys, timeline, name1, name2, defaultValue, scale) + local keyMap = keys[1] + local time = getValue(keyMap, "time", 0) + local value1 = getValue(keyMap, name1, defaultValue) * scale + local value2 = getValue(keyMap, name2, defaultValue) * scale + local bezier = 0 + for i,keyMap in ipairs(keys) do + local frame = i - 1 + timeline:setFrame(frame, time, value1, value2) + local nextMap = keys[frame + 1] + if not nextMap then break end + local time2 = getValue(nextMap, "time", 0) + local nvalue1 = getValue(nextMap, name1, defaultValue) * scale + local nvalue2 = getValue(nextMap, name2, defaultValue) * scale + local curve = keyMap.curve + if curve then + bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, value1, nvalue1, scale) + bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, value2, nvalue2, scale) + end + time = time2 + value1 = nvalue1 + value2 = nvalue2 + end + timeline:shrink(bezier) + return timeline + end + + readCurve = function (curve, timeline, bezier, frame, value, time1, time2, value1, value2, scale) + if curve == "stepped" then + if value ~= 0 then timeline.setStepped(frame) end + return bezier + end + local i = value * 4 + local cx1 = curve[i] + local cy1 = curve[i + 1] * scale + local cx2 = curve[i + 2] + local cy2 = curve[i + 3] * scale + timeline.setBezier(bezier, frame, value, time1, value1, cx1, cy1, cx2, cy2, time2, value2) + return bezier + 1 + end + getArray = function (map, name, scale) local list = map[name] local values = {} diff --git a/spine-lua/spine-lua/TransformConstraint.lua b/spine-lua/spine-lua/TransformConstraint.lua index 0e6deae51..fb0da967a 100644 --- a/spine-lua/spine-lua/TransformConstraint.lua +++ b/spine-lua/spine-lua/TransformConstraint.lua @@ -97,7 +97,7 @@ function TransformConstraint:applyAbsoluteWorld () local tb = target.b local tc = target.c local td = target.d - local degRadReflect = 0; + local degRadReflect = 0 if ta * td - tb * tc > 0 then degRadReflect = utils.degRad else degRadReflect = -utils.degRad end local offsetRotation = self.data.offsetRotation * degRadReflect local offsetShearY = self.data.offsetShearY * degRadReflect @@ -184,7 +184,7 @@ function TransformConstraint:applyRelativeWorld () local tb = target.b local tc = target.c local td = target.d - local degRadReflect = 0; + local degRadReflect = 0 if ta * td - tb * tc > 0 then degRadReflect = utils.degRad else degRadReflect = -utils.degRad end local offsetRotation = self.data.offsetRotation * degRadReflect local offsetShearY = self.data.offsetShearY * degRadReflect @@ -242,7 +242,7 @@ function TransformConstraint:applyRelativeWorld () end local b = bone.b local d = bone.d - r = math_atan2(d, b) + (r - math_pi / 2 + offsetShearY) * shearMix; + r = math_atan2(d, b) + (r - math_pi / 2 + offsetShearY) * shearMix local s = math_sqrt(b * b + d * d) bone.b = math_cos(r) * s bone.d = math_sin(r) * s @@ -297,7 +297,7 @@ function TransformConstraint:applyAbsoluteLocal () bone.shearY = bone.shearY + r * shearMix end - bone:updateWorldTransformWith(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY); + bone:updateWorldTransformWith(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY) end end diff --git a/spine-lua/spine-lua/Triangulator.lua b/spine-lua/spine-lua/Triangulator.lua index 93a73033b..f5ac9ea8b 100644 --- a/spine-lua/spine-lua/Triangulator.lua +++ b/spine-lua/spine-lua/Triangulator.lua @@ -74,7 +74,7 @@ function Triangulator:triangulate (verticesArray) end self.triangles = {} - local triangles = self.triangles; + local triangles = self.triangles while vertexCount > 3 do -- Find ear tip. @@ -122,7 +122,7 @@ function Triangulator:triangulate (verticesArray) if _next == 0 then repeat if not isConcave[i] then - break; + break end i = i - 1 until i == 0 @@ -171,10 +171,10 @@ function Triangulator:decompose(verticesArray, triangles) local vertices = verticesArray self.convexPolygons = {} - local convexPolygons = self.convexPolygons; + local convexPolygons = self.convexPolygons self.convexPolygonsIndices = {} - local convexPolygonsIndices = self.convexPolygonsIndices; + local convexPolygonsIndices = self.convexPolygonsIndices local polygonIndices = {} local polygon = {} @@ -197,12 +197,12 @@ function Triangulator:decompose(verticesArray, triangles) local y3 = vertices[t3 + 1] -- If the base of the last triangle is the same as this triangle, check if they form a convex polygon (triangle fan). - local merged = false; + local merged = false if fanBaseIndex == t1 then - local o = #polygon - 4 + 1; - local p = polygon; - local winding1 = self:winding(p[o], p[o + 1], p[o + 2], p[o + 3], x3, y3); - local winding2 = self:winding(x3, y3, p[1], p[2], p[3], p[4]); + local o = #polygon - 4 + 1 + local p = polygon + local winding1 = self:winding(p[o], p[o + 1], p[o + 2], p[o + 3], x3, y3) + local winding2 = self:winding(x3, y3, p[1], p[2], p[3], p[4]) if winding1 == lastWinding and winding2 == lastWinding then table_insert(polygon, x3) table_insert(polygon, y3) @@ -226,8 +226,8 @@ function Triangulator:decompose(verticesArray, triangles) table_insert(polygon, y3) polygonIndices = {} table_insert(polygonIndices, t1) - table_insert(polygonIndices, t2); - table_insert(polygonIndices, t3); + table_insert(polygonIndices, t2) + table_insert(polygonIndices, t3) lastWinding = self:winding(x1, y1, x2, y2, x3, y3) fanBaseIndex = t1 end @@ -266,11 +266,11 @@ function Triangulator:decompose(verticesArray, triangles) if ii ~= i then local otherIndices = convexPolygonsIndices[ii] if (#otherIndices == 3) then - local otherFirstIndex = otherIndices[1]; - local otherSecondIndex = otherIndices[2]; - local otherLastIndex = otherIndices[3]; + local otherFirstIndex = otherIndices[1] + local otherSecondIndex = otherIndices[2] + local otherLastIndex = otherIndices[3] - local otherPoly = convexPolygons[ii]; + local otherPoly = convexPolygons[ii] local x3 = otherPoly[#otherPoly - 2 + 1] local y3 = otherPoly[#otherPoly - 1 + 1] @@ -308,14 +308,14 @@ function Triangulator:decompose(verticesArray, triangles) i = i - 1 end - return convexPolygons; + return convexPolygons end function Triangulator:isConcave(index, vertexCount, vertices, indices) - local previous = indices[(vertexCount + index - 1) % vertexCount] * 2 + 1; - local current = indices[index] * 2 + 1; - local _next = indices[(index + 1) % vertexCount] * 2 + 1; - return not self:positiveArea(vertices[previous], vertices[previous + 1], vertices[current], vertices[current + 1], vertices[_next],vertices[_next + 1]); + local previous = indices[(vertexCount + index - 1) % vertexCount] * 2 + 1 + local current = indices[index] * 2 + 1 + local _next = indices[(index + 1) % vertexCount] * 2 + 1 + return not self:positiveArea(vertices[previous], vertices[previous + 1], vertices[current], vertices[current + 1], vertices[_next],vertices[_next + 1]) end function Triangulator:positiveArea(p1x, p1y, p2x, p2y, p3x, p3y) @@ -328,7 +328,7 @@ function Triangulator:winding(p1x, p1y, p2x, p2y, p3x, p3y) if p3x * py - p3y * px + px * p1y - p1x * py >= 0 then return 1 else - return -1; + return -1 end end diff --git a/spine-lua/spine-lua/attachments/MeshAttachment.lua b/spine-lua/spine-lua/attachments/MeshAttachment.lua index a8da65fc4..4807687fe 100644 --- a/spine-lua/spine-lua/attachments/MeshAttachment.lua +++ b/spine-lua/spine-lua/attachments/MeshAttachment.lua @@ -84,8 +84,8 @@ function MeshAttachment:updateUVs () local i = 0 local n = #uvs while i < n do - uvs[i + 1] = u + regionUVs[i + 2] * width; - uvs[i + 2] = v + (1 - regionUVs[i + 1]) * height; + uvs[i + 1] = u + regionUVs[i + 2] * width + uvs[i + 2] = v + (1 - regionUVs[i + 1]) * height i = i + 2 end elseif region.degrees == 180 then @@ -96,8 +96,8 @@ function MeshAttachment:updateUVs () local i = 0 local n = #uvs while i < n do - uvs[i + 1] = u + (1 - regionUVs[i + 1]) * width; - uvs[i + 2] = v + (1 - regionUVs[i + 2]) * height; + uvs[i + 1] = u + (1 - regionUVs[i + 1]) * width + uvs[i + 2] = v + (1 - regionUVs[i + 2]) * height i = i + 2 end elseif region.degrees == 270 then @@ -108,20 +108,20 @@ function MeshAttachment:updateUVs () local i = 0 local n = #uvs while i < n do - uvs[i + 1] = u + (1 - regionUVs[i + 2]) * width; - uvs[i + 2] = v + regionUVs[i + 1] * height; + uvs[i + 1] = u + (1 - regionUVs[i + 2]) * width + uvs[i + 2] = v + regionUVs[i + 1] * height i = i + 2 end else - u = region.u - region.offsetX / textureWidth; - v = region.v - (region.originalHeight - region.offsetY - region.height) / textureHeight; - width = region.originalWidth / textureWidth; - height = region.originalHeight / textureHeight; + u = region.u - region.offsetX / textureWidth + v = region.v - (region.originalHeight - region.offsetY - region.height) / textureHeight + width = region.originalWidth / textureWidth + height = region.originalHeight / textureHeight local i = 0 local n = #uvs while i < n do - uvs[i + 1] = u + regionUVs[i + 1] * width; - uvs[i + 2] = v + regionUVs[i + 2] * height; + uvs[i + 1] = u + regionUVs[i + 1] * width + uvs[i + 2] = v + regionUVs[i + 2] * height i = i + 2 end end diff --git a/spine-lua/spine-lua/attachments/RegionAttachment.lua b/spine-lua/spine-lua/attachments/RegionAttachment.lua index 0eeb9264d..b7d2aac43 100644 --- a/spine-lua/spine-lua/attachments/RegionAttachment.lua +++ b/spine-lua/spine-lua/attachments/RegionAttachment.lua @@ -155,6 +155,7 @@ function RegionAttachment.new (name) end function RegionAttachment:updateOffset () + if not self.region then return end local regionScaleX = self.width / self.region.originalWidth * self.scaleX local regionScaleY = self.height / self.region.originalHeight * self.scaleY local localX = -self.width / 2 * self.scaleX + self.region.offsetX * regionScaleX diff --git a/spine-lua/spine-lua/attachments/VertexAttachment.lua b/spine-lua/spine-lua/attachments/VertexAttachment.lua index 452438414..75dd90da9 100644 --- a/spine-lua/spine-lua/attachments/VertexAttachment.lua +++ b/spine-lua/spine-lua/attachments/VertexAttachment.lua @@ -36,8 +36,8 @@ local utils = require "spine-lua.utils" local AttachmentType = require "spine-lua.attachments.AttachmentType" local Attachment = require "spine-lua.attachments.Attachment" -local nextID = 0; -local SHL_11 = 2048; +local nextID = 0 +local SHL_11 = 2048 local VertexAttachment = {} VertexAttachment.__index = VertexAttachment