diff --git a/spine-love/main.lua b/spine-love/main.lua index 73cf2845d..fa3f2bbcc 100644 --- a/spine-love/main.lua +++ b/spine-love/main.lua @@ -113,8 +113,8 @@ end function love.load(arg) if arg[#arg] == "-debug" then require("mobdebug").start() end skeletonRenderer = spine.SkeletonRenderer.new(true) - table.insert(skeletons, loadSkeleton("mix-and-match-pro", "mix-and-match", "dance", nil, 0.5, 400, 500)) table.insert(skeletons, loadSkeleton("spineboy-pro", "spineboy", "walk", nil, 0.5, 400, 500)) + table.insert(skeletons, loadSkeleton("mix-and-match-pro", "mix-and-match", "dance", nil, 0.5, 400, 500)) table.insert(skeletons, loadSkeleton("stretchyman-pro", "stretchyman", "sneak", nil, 0.5, 200, 500)) table.insert(skeletons, loadSkeleton("coin-pro", "coin", "animation", nil, 0.5, 400, 300)) table.insert(skeletons, loadSkeleton("raptor-pro", "raptor", "walk", nil, 0.3, 400, 500)) diff --git a/spine-lua/spine-lua/Animation.lua b/spine-lua/spine-lua/Animation.lua index 0e8278234..adf1d2295 100644 --- a/spine-lua/spine-lua/Animation.lua +++ b/spine-lua/spine-lua/Animation.lua @@ -37,7 +37,7 @@ local utils = require "spine-lua.utils" local AttachmentType = require "spine-lua.attachments.AttachmentType" local setmetatable = setmetatable -local math_floor = math_floor +local math_floor = math.floor local math_abs = math.abs local math_signum = utils.signum @@ -64,14 +64,14 @@ function Animation.new (name, timelines, duration) self.timelineIds = {} for i,timeline in ipairs(self.timelines) do for _,id in ipairs(timeline.propertyIds) do - timelineIds[id] = true + self.timelineIds[id] = true end end end function self:hasTimeline (ids) for _,id in ipairs(ids) do - if timelineIds[id] then return true end + if self.timelineIds[id] then return true end end return false end @@ -134,11 +134,28 @@ Animation.Property = { } local Property = Animation.Property +Animation.TimelineType = { + rotate = 0, + translate = 1, translateX = 2, translateY = 3, + scale = 4, scaleX = 5, scaleY = 6, + shear = 7, shearX = 8, shearY = 9, + rgba = 10, rgb = 11, alpha = 12, rgba2 = 13, rgb2 = 14, + attachment = 15, + deform = 16, + event = 17, + drawOrder = 18, + ikConstraint = 19, + transformConstraint = 20, + pathConstraintPosition = 21, pathConstraintSpacing = 22, pathConstraintMix = 23 +} +local TimelineType = Animation.TimelineType + Animation.Timeline = {} -function Animation.Timeline.new (frameCount, propertyIds) +function Animation.Timeline.new (timelineType, frameEntries, frameCount, propertyIds) local self = { + timelineType = timelineType, propertyIds = propertyIds, - frames = utils.newNumberArrayZero((frameCount - 1) * self:getFrameEntries()) + frames = utils.newNumberArrayZero((frameCount - 1) * frameEntries) } function self:getFrameEntries () @@ -158,7 +175,8 @@ end local function search1 (frames, time) local n = zlen(frames) - while i <= n do + local i = 1 + while i < n do if frames[i] > time then return i - 1 end i = i + 1 end @@ -169,7 +187,7 @@ Animation.Timeline.search1 = search1 local function search (frames, time, step) local n = zlen(frames) local i = step - while i <= n do + while i < n do if frames[i] > time then return i - step end i = i + step end @@ -182,14 +200,15 @@ local BEZIER = 2 local BEZIER_SIZE = 18 Animation.CurveTimeline = {} -function Animation.CurveTimeline.new (frameCount, bezierCount, propertyIds) +function Animation.CurveTimeline.new (timelineType, frameEntries, frameCount, bezierCount, propertyIds) local LINEAR = 0 local STEPPED = 1 local BEZIER = 2 local BEZIER_SIZE = 10 * 2 - 1 - local self = Animation.Timeline.new(frameCount, propertyIds) + local self = Animation.Timeline.new(timelineType, frameEntries, frameCount, propertyIds) self.curves = utils.newNumberArrayZero(frameCount + bezierCount * BEZIER_SIZE) + self.curves[frameCount - 1] = STEPPED function self:getFrameCount () return math_floor(zlen(self.curves) / BEZIER_SIZE) + 1 @@ -261,11 +280,11 @@ function Animation.CurveTimeline.new (frameCount, bezierCount, propertyIds) end Animation.CurveTimeline1 = {} -function Animation.CurveTimeline1.new (frameCount, bezierCount, propertyId) +function Animation.CurveTimeline1.new (timelineType, frameCount, bezierCount, propertyId) local ENTRIES = 2 local VALUE = 1 - local self = Animation.CurveTimeline.new(frameCount, bezierCount, { propertyId }) + local self = Animation.CurveTimeline.new(timelineType, ENTRIES, frameCount, bezierCount, { propertyId }) function self:getFrameEntries () return ENTRIES @@ -303,12 +322,12 @@ function Animation.CurveTimeline1.new (frameCount, bezierCount, propertyId) end Animation.CurveTimeline2 = {} -function Animation.CurveTimeline2.new (frameCount, bezierCount, propertyId1, propertyId2) +function Animation.CurveTimeline2.new (timelineType, frameCount, bezierCount, propertyId1, propertyId2) local ENTRIES = 3 local VALUE1 = 1 local VALUE2 = 2 - local self = Animation.CurveTimeline.new(frameCount, bezierCount, { propertyId1, propertyId2 }) + local self = Animation.CurveTimeline.new(timelineType, ENTRIES, frameCount, bezierCount, { propertyId1, propertyId2 }) function self:getFrameEntries () return ENTRIES @@ -326,7 +345,7 @@ end Animation.RotateTimeline = {} function Animation.RotateTimeline.new (frameCount, bezierCount, boneIndex) - local self = Animation.CurveTimeline1.new(frameCount, bezierCount, Property.rotate.."|"..boneIndex) + local self = Animation.CurveTimeline1.new(TimelineType.rotate, frameCount, bezierCount, Property.rotate.."|"..boneIndex) self.boneIndex = boneIndex function self:apply (skeleton, lastTime, time, events, alpha, blend, direction) @@ -358,7 +377,11 @@ end Animation.TranslateTimeline = {} function Animation.TranslateTimeline.new (frameCount, bezierCount, boneIndex) - local self = Animation.CurveTimeline2.new(frameCount, bezierCount, + local ENTRIES = 3 + local VALUE1 = 1 + local VALUE2 = 2 + + local self = Animation.CurveTimeline2.new(TimelineType.translate, frameCount, bezierCount, Property.x.."|"..boneIndex, Property.y.."|"..boneIndex ) @@ -382,7 +405,7 @@ function Animation.TranslateTimeline.new (frameCount, bezierCount, boneIndex) local x = 0 local y = 0 - local frame = search2(frames, time, ENTRIES) + local i = search(frames, time, ENTRIES) local curveType = self.curves[math_floor(i / ENTRIES)] if curveType == LINEAR then local before = frames[i] @@ -416,7 +439,7 @@ end Animation.TranslateXTimeline = {} function Animation.TranslateXTimeline.new (frameCount, bezierCount, boneIndex) - local self = Animation.CurveTimeline1.new(frameCount, bezierCount, Property.x.."|"..boneIndex) + local self = Animation.CurveTimeline1.new(TimelineType.translateX, frameCount, bezierCount, Property.x.."|"..boneIndex) self.boneIndex = boneIndex function self:apply (skeleton, lastTime, time, events, alpha, blend, direction) @@ -448,7 +471,7 @@ end Animation.TranslateYTimeline = {} function Animation.TranslateYTimeline.new (frameCount, bezierCount, boneIndex) - local self = Animation.CurveTimeline1.new(frameCount, bezierCount, Property.x.."|"..boneIndex) + local self = Animation.CurveTimeline1.new(TimelineType.translateY, frameCount, bezierCount, Property.x.."|"..boneIndex) self.boneIndex = boneIndex function self:apply (skeleton, lastTime, time, events, alpha, blend, direction) @@ -480,7 +503,11 @@ end Animation.ScaleTimeline = {} function Animation.ScaleTimeline.new (frameCount, bezierCount, boneIndex) - local self = Animation.CurveTimeline2.new(frameCount, bezierCount, + local ENTRIES = 3 + local VALUE1 = 1 + local VALUE2 = 2 + + local self = Animation.CurveTimeline2.new(TimelineType.scale, frameCount, bezierCount, Property.scaleX.."|"..boneIndex, Property.scaleY.."|"..boneIndex ) @@ -504,7 +531,7 @@ function Animation.ScaleTimeline.new (frameCount, bezierCount, boneIndex) local x = 0 local y = 0 - local i = search2(frames, time, ENTRIES) + local i = search(frames, time, ENTRIES) local curveType = self.curves[math_floor(i / ENTRIES)] if curveType == LINEAR then local before = frames[i] @@ -577,7 +604,7 @@ end Animation.ScaleXTimeline = {} function Animation.ScaleXTimeline.new (frameCount, bezierCount, boneIndex) - local self = Animation.CurveTimeline1.new(frameCount, bezierCount, Property.scaleX.."|"..boneIndex) + local self = Animation.CurveTimeline1.new(TimelineType.scaleX, frameCount, bezierCount, Property.scaleX.."|"..boneIndex) self.boneIndex = boneIndex function self:apply (skeleton, lastTime, time, events, alpha, blend, direction) @@ -634,7 +661,7 @@ end Animation.ScaleYTimeline = {} function Animation.ScaleYTimeline.new (frameCount, bezierCount, boneIndex) - local self = Animation.CurveTimeline1.new(frameCount, bezierCount, Property.scaleY.."|"..boneIndex) + local self = Animation.CurveTimeline1.new(TimelineType.scaleY, frameCount, bezierCount, Property.scaleY.."|"..boneIndex) self.boneIndex = boneIndex function self:apply (skeleton, lastTime, time, events, alpha, blend, direction) @@ -690,8 +717,12 @@ function Animation.ScaleYTimeline.new (frameCount, bezierCount, boneIndex) end Animation.ShearTimeline = {} -function Animation.ShearTimeline.new (frameCount) - local self = Animation.CurveTimeline2.new(frameCount, bezierCount, +function Animation.ShearTimeline.new (frameCount, bezierCount, boneIndex) + local ENTRIES = 3 + local VALUE1 = 1 + local VALUE2 = 2 + + local self = Animation.CurveTimeline2.new(TimelineType.shear, frameCount, bezierCount, Property.shearX.."|"..boneIndex, Property.shearY.."|"..boneIndex ) @@ -715,7 +746,7 @@ function Animation.ShearTimeline.new (frameCount) local x = 0 local y = 0 - local i = search2(frames, time, ENTRIES) + local i = search(frames, time, ENTRIES) local curveType = self.curves[math_floor(i / ENTRIES)] if curveType == LINEAR then local before = frames[i] @@ -748,8 +779,8 @@ function Animation.ShearTimeline.new (frameCount) end Animation.ShearXTimeline = {} -function Animation.ShearXTimeline.new (frameCount) - local self = Animation.CurveTimeline1.new(frameCount, bezierCount, Property.shearX.."|"..boneIndex) +function Animation.ShearXTimeline.new (frameCount, bezierCount, boneIndex) + local self = Animation.CurveTimeline1.new(TimelineType.shearX, frameCount, bezierCount, Property.shearX.."|"..boneIndex) self.boneIndex = boneIndex function self:apply (skeleton, lastTime, time, events, alpha, blend, direction) @@ -780,8 +811,8 @@ function Animation.ShearXTimeline.new (frameCount) end Animation.ShearYTimeline = {} -function Animation.ShearYTimeline.new (frameCount) - local self = Animation.CurveTimeline1.new(frameCount, bezierCount, Property.shearY.."|"..boneIndex) +function Animation.ShearYTimeline.new (frameCount, bezierCount, boneIndex) + local self = Animation.CurveTimeline1.new(TimelineType.shearY, frameCount, bezierCount, Property.shearY.."|"..boneIndex) self.boneIndex = boneIndex function self:apply (skeleton, lastTime, time, events, alpha, blend, direction) @@ -819,7 +850,7 @@ function Animation.RGBATimeline.new (frameCount, bezierCount, slotIndex) local B = 3 local A = 4 - local self = Animation.CurveTimeline.new(frameCount, bezierCount, { + local self = Animation.CurveTimeline.new(TimelineType.rgba, ENTRIES, frameCount, bezierCount, { Property.rgb.."|"..slotIndex, Property.alpha.."|"..slotIndex }) @@ -856,7 +887,7 @@ function Animation.RGBATimeline.new (frameCount, bezierCount, slotIndex) end local r, g, b, a - local i = search2(frames, time, ENTRIES) + local i = search(frames, time, ENTRIES) local curveType = self.curves[i / ENTRIES] if curveType == LINEAR then local before = frames[i] @@ -899,7 +930,7 @@ function Animation.RGBTimeline.new (frameCount, bezierCount, slotIndex) local G = 2 local B = 3 - local self = Animation.CurveTimeline.new(frameCount, bezierCount, { Property.rgb.."|"..slotIndex }) + local self = Animation.CurveTimeline.new(TimelineType.rgb, ENTRIES, frameCount, bezierCount, { Property.rgb.."|"..slotIndex }) self.slotIndex = slotIndex function self:getFrameEntries () @@ -935,7 +966,7 @@ function Animation.RGBTimeline.new (frameCount, bezierCount, slotIndex) end local r, g, b - local i = search2(frames, time, ENTRIES) + local i = search(frames, time, ENTRIES) local curveType = self.curves[i / ENTRIES] if curveType == LINEAR then local before = frames[i] @@ -978,7 +1009,7 @@ end Animation.AlphaTimeline = {} function Animation.AlphaTimeline.new (frameCount, bezierCount, slotIndex) - local self = Animation.CurveTimeline1.new(frameCount, bezierCount, Property.alpha.."|"..slotIndex) + local self = Animation.CurveTimeline1.new(TimelineType.alpha, frameCount, bezierCount, Property.alpha.."|"..slotIndex) self.slotIndex = slotIndex function self:apply (skeleton, lastTime, time, events, alpha, blend, direction) @@ -1020,7 +1051,7 @@ function Animation.RGBA2Timeline.new (frameCount, bezierCount, slotIndex) local G2 = 6 local B2 = 7 - local self = Animation.CurveTimeline.new(frameCount, bezierCount, { + local self = Animation.CurveTimeline.new(TimelineType.rgba2, ENTRIES, frameCount, bezierCount, { Property.rgb.."|"..slotIndex, Property.alpha.."|"..slotIndex, Property.rgb2.."|"..slotIndex @@ -1069,7 +1100,7 @@ function Animation.RGBA2Timeline.new (frameCount, bezierCount, slotIndex) end local r, g, b, a, r2, g2, b2 - local i = search2(frames, time, ENTRIES) + local i = search(frames, time, ENTRIES) local curveType = self.curves[math_floor(i / ENTRIES)] if curveType == LINEAR then local before = frames[i] @@ -1139,7 +1170,7 @@ function Animation.RGB2Timeline.new (frameCount, bezierCount, slotIndex) local G2 = 5 local B2 = 6 - local self = Animation.CurveTimeline.new(frameCount, bezierCount, { + local self = Animation.CurveTimeline.new(TimelineType.rgb2, ENTRIES, frameCount, bezierCount, { Property.rgb.."|"..slotIndex, Property.rgb2.."|"..slotIndex }) @@ -1189,7 +1220,7 @@ function Animation.RGB2Timeline.new (frameCount, bezierCount, slotIndex) end local r, g, b, r2, g2, b2 - local i = search2(frames, time, ENTRIES) + local i = search(frames, time, ENTRIES) local curveType = self.curves[math_floor(i / ENTRIES)] if curveType == LINEAR then local before = frames[i] @@ -1254,7 +1285,7 @@ end Animation.AttachmentTimeline = {} function Animation.AttachmentTimeline.new (frameCount, bezierCount, slotIndex) - local self = Animation.Timeline.new(frameCount, { Property.attachment + "|" + slotIndex }) + local self = Animation.Timeline.new(TimelineType.attachment, 1, frameCount, { Property.attachment.."|"..slotIndex }) self.slotIndex = slotIndex self.attachmentNames = {} @@ -1311,7 +1342,7 @@ end Animation.DeformTimeline = {} function Animation.DeformTimeline.new (frameCount, bezierCount, slotIndex, attachment) - local self = Animation.CurveTimeline.new(frameCount, bezierCount, { Property.deform + "|" + slotIndex + "|" + attachment.id }) + local self = Animation.CurveTimeline.new(TimelineType.deform, 1, frameCount, bezierCount, { Property.deform.."|"..slotIndex.."|"..attachment.id }) self.slotIndex = slotIndex self.attachment = attachment self.vertices = {} @@ -1353,7 +1384,7 @@ function Animation.DeformTimeline.new (frameCount, bezierCount, slotIndex, attac end end - function getCurvePercent (time, frame) + function self:getCurvePercent (time, frame) local curves = self.curves local i = curves[frame] if i == LINEAR then @@ -1387,7 +1418,7 @@ function Animation.DeformTimeline.new (frameCount, bezierCount, slotIndex, attac if not slot.bone.active then return end local vertexAttachment = slot.attachment - if not vertexAttachment or not vertexAttachment.vertexAttachment or vertexAttachment.deformAttachment ~= self.attachment then return end + if not vertexAttachment or not vertexAttachment.isVertexAttachment or vertexAttachment.deformAttachment ~= self.attachment then return end local frames = self.frames local deform = slot.deform @@ -1604,7 +1635,7 @@ end Animation.EventTimeline = {} local eventPropertyIds = { Property.event } function Animation.EventTimeline.new (frameCount) - local self = Animation.Timeline.new(frameCount, eventPropertyIds) + local self = Animation.Timeline.new(TimelineType.event, 1, frameCount, eventPropertyIds) self.events = {} function self:getFrameCount () @@ -1635,7 +1666,7 @@ function Animation.EventTimeline.new (frameCount) if lastTime < frames[0] then i = 0 else - i = binarySearch1(frames, lastTime) + i = search1(frames, lastTime) + 1 local i = frames[i] while i > 0 do -- Fire multiple events with the same frame. if frames[i - 1] ~= i then break end @@ -1654,7 +1685,7 @@ end Animation.DrawOrderTimeline = {} local drawOrderPropertyIds = { Property.drawOrder } function Animation.DrawOrderTimeline.new (frameCount) - local self = Animation.Timeline.new(frameCount, drawOrderPropertyIds) + local self = Animation.Timeline.new(TimelineType.drawOrder, 1, frameCount, drawOrderPropertyIds) self.drawOrders = {} function self:getFrameCount () @@ -1667,6 +1698,9 @@ function Animation.DrawOrderTimeline.new (frameCount) end function self:apply (skeleton, lastTime, time, events, alpha, blend, direction) + local drawOrder = skeleton.drawOrder + local slots = skeleton.slots + if direction == MixDirection.mixOut then if blend == MixBlend.setup then for i,slot in ipairs(slots) do @@ -1691,8 +1725,6 @@ function Animation.DrawOrderTimeline.new (frameCount) drawOrder[i] = slots[i] end else - local drawOrder = skeleton.drawOrder - local slots = skeleton.slots for i,setupIndex in ipairs(drawOrderToSetupIndex) do drawOrder[i] = skeleton.slots[setupIndex] end @@ -1711,7 +1743,7 @@ function Animation.IkConstraintTimeline.new (frameCount, bezierCount, ikConstrai local COMPRESS = 4 local STRETCH = 5 - local self = Animation.CurveTimeline.new(frameCount, bezierCount, { Property.ikConstraint + "|" + ikConstraintIndex }) + local self = Animation.CurveTimeline.new(TimelineType.ikConstraint, ENTRIES, frameCount, bezierCount, { Property.ikConstraint.."|"..ikConstraintIndex }) self.ikConstraintIndex = ikConstraintIndex function self:getFrameEntries () @@ -1804,7 +1836,7 @@ function Animation.IkConstraintTimeline.new (frameCount, bezierCount, ikConstrai end Animation.TransformConstraintTimeline = {} -function Animation.TransformConstraintTimeline.new (frameCount, transformConstraintIndex) +function Animation.TransformConstraintTimeline.new (frameCount, bezierCount, transformConstraintIndex) local ENTRIES = 7 local ROTATE = 1 local X = 2 @@ -1813,7 +1845,7 @@ function Animation.TransformConstraintTimeline.new (frameCount, transformConstra local SCALEY = 5 local SHEARY = 6 - local self = Animation.CurveTimeline.new(frameCount, bezierCount, { Property.transformConstraint + "|" + transformConstraintIndex }) + local self = Animation.CurveTimeline.new(TimelineType.transformConstraint, ENTRIES, frameCount, bezierCount, { Property.transformConstraint.."|"..transformConstraintIndex }) self.transformConstraintIndex = transformConstraintIndex function self:getFrameEntries () @@ -1918,7 +1950,7 @@ end Animation.PathConstraintPositionTimeline = {} function Animation.PathConstraintPositionTimeline.new (frameCount, bezierCount, pathConstraintIndex) - local self = Animation.CurveTimeline1.new(frameCount, bezierCount, Property.pathConstraintPosition.."|"..pathConstraintIndex) + local self = Animation.CurveTimeline1.new(TimelineType.pathConstraintPosition, frameCount, bezierCount, Property.pathConstraintPosition.."|"..pathConstraintIndex) self.pathConstraintIndex = pathConstraintIndex function self:apply (skeleton, lastTime, time, events, alpha, blend, direction) @@ -1948,7 +1980,7 @@ end Animation.PathConstraintSpacingTimeline = {} function Animation.PathConstraintSpacingTimeline.new (frameCount, bezierCount, pathConstraintIndex) - local self = Animation.CurveTimeline1.new(frameCount, bezierCount, Property.pathConstraintSpacing.."|"..pathConstraintIndex) + local self = Animation.CurveTimeline1.new(TimelineType.pathConstraintSpacing, frameCount, bezierCount, Property.pathConstraintSpacing.."|"..pathConstraintIndex) self.pathConstraintIndex = pathConstraintIndex function self:apply (skeleton, lastTime, time, events, alpha, blend, direction) @@ -1983,7 +2015,7 @@ function Animation.PathConstraintMixTimeline.new (frameCount, bezierCount, pathC local X = 2 local Y = 3 - local self = Animation.CurveTimeline.new(frameCount, bezierCount, Property.pathConstraintMix.."|"..pathConstraintIndex) + local self = Animation.CurveTimeline.new(TimelineType.pathConstraintMix, ENTRIES, frameCount, bezierCount, Property.pathConstraintMix.."|"..pathConstraintIndex) self.pathConstraintIndex = pathConstraintIndex function self:getFrameEntries () @@ -2006,13 +2038,13 @@ function Animation.PathConstraintMixTimeline.new (frameCount, bezierCount, pathC local frames = self.frames if time < frames[0] then if blend == MixBlend.setup then - constraint.mixRotate = constraint.data.mixRotate; - constraint.mixX = constraint.data.mixX; - constraint.mixY = constraint.data.mixY; + constraint.mixRotate = constraint.data.mixRotate + constraint.mixX = constraint.data.mixX + constraint.mixY = constraint.data.mixY elseif blend == MixBlend.first then - constraint.mixRotate = constraint.mixRotate + (constraint.data.mixRotate - constraint.mixRotate) * alpha; - constraint.mixX = constraint.mixX + (constraint.data.mixX - constraint.mixX) * alpha; - constraint.mixY = constraint.mixY + (constraint.data.mixY - constraint.mixY) * alpha; + constraint.mixRotate = constraint.mixRotate + (constraint.data.mixRotate - constraint.mixRotate) * alpha + constraint.mixX = constraint.mixX + (constraint.data.mixX - constraint.mixX) * alpha + constraint.mixY = constraint.mixY + (constraint.data.mixY - constraint.mixY) * alpha end return end @@ -2020,36 +2052,36 @@ function Animation.PathConstraintMixTimeline.new (frameCount, bezierCount, pathC local rotate local x local y - local i = search(frames, time, ENTRIES); - local curveType = self.curves[math_floor(i / 4)]; + local i = search(frames, time, ENTRIES) + local curveType = self.curves[math_floor(i / 4)] if curveType == LINEAR then - local before = frames[i]; - rotate = frames[i + ROTATE]; - x = frames[i + X]; - y = frames[i + Y]; - local t = (time - before) / (frames[i + ENTRIES] - before); - rotate = rotate + (frames[i + ENTRIES + ROTATE] - rotate) * t; - x = x + (frames[i + ENTRIES + X] - x) * t; - y = y + (frames[i + ENTRIES + Y] - y) * t; + local before = frames[i] + rotate = frames[i + ROTATE] + x = frames[i + X] + y = frames[i + Y] + local t = (time - before) / (frames[i + ENTRIES] - before) + rotate = rotate + (frames[i + ENTRIES + ROTATE] - rotate) * t + x = x + (frames[i + ENTRIES + X] - x) * t + y = y + (frames[i + ENTRIES + Y] - y) * t elseif curveType == STEPPED then - rotate = frames[i + ROTATE]; - x = frames[i + X]; - y = frames[i + Y]; + rotate = frames[i + ROTATE] + x = frames[i + X] + y = frames[i + Y] else - rotate = this.getBezierValue(time, i, ROTATE, curveType - BEZIER); - x = this.getBezierValue(time, i, X, curveType + BEZIER_SIZE - BEZIER); - y = this.getBezierValue(time, i, Y, curveType + BEZIER_SIZE * 2 - BEZIER); + rotate = this.getBezierValue(time, i, ROTATE, curveType - BEZIER) + x = this.getBezierValue(time, i, X, curveType + BEZIER_SIZE - BEZIER) + y = this.getBezierValue(time, i, Y, curveType + BEZIER_SIZE * 2 - BEZIER) end if blend == MixBlend.setup then - local data = constraint.data; - constraint.mixRotate = data.mixRotate + (rotate - data.mixRotate) * alpha; - constraint.mixX = data.mixX + (x - data.mixX) * alpha; - constraint.mixY = data.mixY + (y - data.mixY) * alpha; + local data = constraint.data + constraint.mixRotate = data.mixRotate + (rotate - data.mixRotate) * alpha + constraint.mixX = data.mixX + (x - data.mixX) * alpha + constraint.mixY = data.mixY + (y - data.mixY) * alpha else - constraint.mixRotate = constraint.mixRotate + (rotate - constraint.mixRotate) * alpha; - constraint.mixX = constraint.mixX + (x - constraint.mixX) * alpha; - constraint.mixY = constraint.mixY + (y - constraint.mixY) * alpha; + constraint.mixRotate = constraint.mixRotate + (rotate - constraint.mixRotate) * alpha + constraint.mixX = constraint.mixX + (x - constraint.mixX) * alpha + constraint.mixY = constraint.mixY + (y - constraint.mixY) * alpha end end diff --git a/spine-lua/spine-lua/AnimationState.lua b/spine-lua/spine-lua/AnimationState.lua index b40574cf1..00834ff3d 100644 --- a/spine-lua/spine-lua/AnimationState.lua +++ b/spine-lua/spine-lua/AnimationState.lua @@ -198,12 +198,22 @@ function TrackEntry:getAnimationTime () return math_min(self.trackTime + self.animationStart, self.animationEnd) end +function TrackEntry:getTrackComplete () + local duration = self.animationEnd - self.animationStart + if duration ~= 0 then + if self.loop then return duration * (1 + math_floor(self.trackTime / duration)) end -- Completion of next loop. + if self.trackTime < duration then return duration end -- Before duration. + end + return self.trackTime -- Next update. +end + function TrackEntry:resetRotationDirections () self.timelinesRotation = {} end local AnimationState = {} AnimationState.__index = AnimationState +AnimationState.TrackEntry = TrackEntry function AnimationState.new (data) if not data then error("data cannot be nil", 2) end @@ -225,8 +235,6 @@ function AnimationState.new (data) return self end -AnimationState.TrackEntry = TrackEntry - function AnimationState:update (delta) delta = delta * self.timeScale local tracks = self.tracks @@ -359,31 +367,35 @@ function AnimationState:apply (skeleton) -- Apply current entry. local animationLast = current.animationLast local animationTime = current:getAnimationTime() + local applyTime = animationTime + local applyEvents = self.events + if current.reverse then + applyTime = current.animation.duration - applyTime + applyEvents = nil + end local timelines = current.animation.timelines if (i == 0 and mix == 1) or blend == MixBlend.add then for i,timeline in ipairs(timelines) do if timeline.type == Animation.TimelineType.attachment then - self:applyAttachmentTimeline(timeline, skeleton, animationTime, blend, true) + self:applyAttachmentTimeline(timeline, skeleton, applyTime, blend, true) else - timeline:apply(skeleton, animationLast, animationTime, self.events, mix, blend, MixDirection.mixIn) + timeline:apply(skeleton, animationLast, applyTime, applyEvents, mix, blend, MixDirection.mixIn) end end else local timelineMode = current.timelineMode - local firstFrame = #current.timelinesRotation == 0 - local timelinesRotation = current.timelinesRotation + local firstFrame = #current.timelinesRotation ~= #timelines * 2 for ii,timeline in ipairs(timelines) do local timelineBlend = MixBlend.setup if timelineMode[ii] == SUBSEQUENT then timelineBlend = blend end if timeline.type == Animation.TimelineType.rotate then - self:applyRotateTimeline(timeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation, ii * 2, - firstFrame) + self:applyRotateTimeline(timeline, skeleton, applyTime, mix, timelineBlend, current.timelinesRotation, ii * 2, firstFrame) elseif timeline.type == Animation.TimelineType.attachment then - self:applyAttachmentTimeline(timeline, skeleton, animationTime, timelineBlend, true) + self:applyAttachmentTimeline(timeline, skeleton, applyTime, timelineBlend, true) else - timeline:apply(skeleton, animationLast, animationTime, self.events, mix, timelineBlend, MixDirection.mixIn) + timeline:apply(skeleton, animationLast, applyTime, applyEvents, mix, timelineBlend, MixDirection.mixIn) end end end @@ -401,7 +413,7 @@ function AnimationState:apply (skeleton) -- the time is before the first key). local setupState = self.unkeyedState + SETUP local slots = skeleton.slots - for _, slot in ipairs(slots) do + for _,slot in ipairs(slots) do if slot.attachmentState == setupState then local attachmentName = slot.data.attachmentName if attachmentName == nil then @@ -432,25 +444,31 @@ function AnimationState:applyMixingFrom (to, skeleton, blend) if blend ~= MixBlend.first then blend = from.mixBlend end end - local events = nil - if mix < from.eventThreshold then events = self.events end + local attachments = mix < from.attachmentThreshold local drawOrder = mix < from.drawOrderThreshold - local animationLast = from.animationLast - local animationTime = from:getAnimationTime() local timelines = from.animation.timelines local alphaHold = from.alpha * to.interruptAlpha local alphaMix = alphaHold * (1 - mix) + local animationLast = from.animationLast + local animationTime = from:getAnimationTime() + local applyTime = animationTime + local events = nil + if from.reverse then + applyTime = from.animation.duration - applyTime + elseif mix < from.eventThreshold then + events = self.events + end if blend == MixBlend.add then for i,timeline in ipairs(timelines) do - timeline:apply(skeleton, animationLast, animationTime, events, alphaMix, blend, MixDirection.mixOut) + timeline:apply(skeleton, animationLast, applyTime, events, alphaMix, blend, MixDirection.mixOut) end else local timelineMode = from.timelineMode local timelineHoldMix = from.timelineHoldMix - local firstFrame = #from.timelinesRotation == 0 + local firstFrame = #from.timelinesRotation ~= #timelines local timelinesRotation = from.timelinesRotation from.totalAlpha = 0 @@ -473,7 +491,7 @@ function AnimationState:applyMixingFrom (to, skeleton, blend) elseif timelineMode[i] == HOLD_FIRST then timelineBlend = MixBlend.setup alpha = alphaHold - else + else -- HOLD_MIX timelineBlend = MixBlend.setup local holdMix = timelineHoldMix[i] alpha = alphaHold * math_max(0, 1 - holdMix.mixtime / holdMix.mixDuration) @@ -482,14 +500,14 @@ function AnimationState:applyMixingFrom (to, skeleton, blend) if not skipSubsequent then from.totalAlpha = from.totalAlpha + alpha if timeline.type == Animation.TimelineType.rotate then - self:applyRotateTimeline(timeline, skeleton, animationTime, alpha, timelineBlend, timelinesRotation, i * 2, firstFrame) + self:applyRotateTimeline(timeline, skeleton, applyTime, alpha, timelineBlend, timelinesRotation, i * 2, firstFrame) elseif timeline.type == Animation.TimelineType.attachment then - self:applyAttachmentTimeline(timeline, skeleton, animationTime, timelineBlend, attachments) + self:applyAttachmentTimeline(timeline, skeleton, applyTime, timelineBlend, attachments) else if drawOrder and timeline.type == Animation.TimelineType.drawOrder and timelineBlend == MixBlend.setup then direction = MixDirection.mixIn end - timeline:apply(skeleton, animationLast, animationTime, self.events, alpha, timelineBlend, direction) + timeline:apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction) end end end @@ -509,19 +527,12 @@ function AnimationState:applyAttachmentTimeline(timeline, skeleton, time, blend, 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 time < timeline.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) end else - local frameIndex = 0 - if time >= frames[zlen(frames) - 1] then -- Time is after last frame. - frameIndex = zlen(frames) - 1 - else - frameIndex = Animation.binarySearch(frames, time, 1) - 1 - end - self:setAttachment(skeleton, slot, timeline.attachmentNames[frameIndex], attachments) + self:setAttachment(skeleton, slot, timeline.attachmentNames[Timeline.search1(timeline.frames, time)], attachments) end -- If an attachment wasn't set (ie before the first frame or attachments is false), set the setup attachment later. @@ -548,10 +559,9 @@ function AnimationState:applyRotateTimeline (timeline, skeleton, time, alpha, bl return end - local rotateTimeline = timeline - local frames = rotateTimeline.frames - local bone = skeleton.bones[rotateTimeline.boneIndex] + local bone = skeleton.bones[timeline.boneIndex] if not bone.active then return end + local frames = timeline.frames local r1 = 0 local r2 = 0 if time < frames[0] then @@ -570,21 +580,7 @@ function AnimationState:applyRotateTimeline (timeline, skeleton, time, alpha, bl else r1 = bone.rotation end - if time >= frames[zlen(frames) - Animation.RotateTimeline.ENTRIES] then -- Time is after last frame. - r2 = bone.data.rotation + frames[zlen(frames) + Animation.RotateTimeline.PREV_ROTATION] - else - -- Interpolate between the previous frame and the current frame. - local frame = Animation.binarySearch(frames, time, Animation.RotateTimeline.ENTRIES) - local prevRotation = frames[frame + Animation.RotateTimeline.PREV_ROTATION] - local frameTime = frames[frame] - local percent = rotateTimeline:getCurvePercent(math_floor(frame / 2) - 1, - 1 - (time - frameTime) / (frames[frame + Animation.RotateTimeline.PREV_TIME] - frameTime)) - - r2 = frames[frame + Animation.RotateTimeline.ROTATION] - prevRotation - r2 = r2 - (16384 - math_floor(16384.499999999996 - r2 / 360)) * 360 - r2 = prevRotation + r2 * percent + bone.data.rotation - r2 = r2 - (16384 - math_floor(16384.499999999996 - r2 / 360)) * 360 - end + r2 = bone.data.rotation + timeline:getCurveValue(time) end -- Mix between rotations using the direction of the shortest route on the first frame while detecting crosses. @@ -616,8 +612,7 @@ function AnimationState:applyRotateTimeline (timeline, skeleton, time, alpha, bl timelinesRotation[i] = total end timelinesRotation[i + 1] = diff - r1 = r1 + total * alpha - bone.rotation = r1 - (16384 - math_floor(16384.499999999996 - r1 / 360)) * 360 + bone.rotation = r1 + total * alpha end function AnimationState:queueEvents (entry, animationTime) @@ -654,7 +649,7 @@ function AnimationState:queueEvents (entry, animationTime) -- Queue events after complete. while i <= n do local event = events[i] - if not (event.time < animationStart) then --// Discard events outside animation start/end. + if event.time >= animationStart then -- Discard events outside animation start/end. queue:event(entry, event) end i = i + 1 @@ -701,14 +696,17 @@ function AnimationState:clearTrack (trackIndex) queue:drain() end +function AnimationState:clearNext (entry) + self:disposeNext(entry.next) +end + function AnimationState:setCurrent (index, current, interrupt) local from = self:expandToIndex(index) - local tracks = self.tracks - local queue = self.queue - tracks[index] = current + self.tracks[index] = current + current.previous = nil if from then - if interrupt then queue:interrupt(from) end + if interrupt then self.queue:interrupt(from) end current.mixingFrom = from from.mixingTo = current current.mixTime = 0 @@ -720,7 +718,7 @@ function AnimationState:setCurrent (index, current, interrupt) from.timelinesRotation = {} end - queue:start(current) + self.queue:start(current) end function AnimationState:setAnimationByName (trackIndex, animationName, loop) @@ -779,19 +777,8 @@ function AnimationState:addAnimation (trackIndex, animation, loop, delay) queue:drain() else last.next = entry - if delay <= 0 then - local duration = last.animationEnd - last.animationStart - if duration ~= 0 then - if last.loop then - delay = delay + duration * (1 + math_floor(last.trackTime / duration)) - else - delay = delay + math_max(duration, last.trackTime) - end - delay = delay - data:getMix(last.animation, animation) - else - delay = last.trackTime - end - end + entry.previous = last + if delay <= 0 then delay = delay + last:getTrackComplete() - entry.mixDuration end end entry.delay = delay @@ -806,10 +793,14 @@ function AnimationState:setEmptyAnimation (trackIndex, mixDuration) end function AnimationState:addEmptyAnimation (trackIndex, mixDuration, delay) - if delay <= 0 then delay = delay - mixDuration end - local entry = self:addAnimation(trackIndex, EMPTY_ANIMATION, false, delay) + local addDelay = 1 + if delay > 0 then addDelay = delay end + local entry = self:addAnimation(trackIndex, EMPTY_ANIMATION, false, addDelay) entry.mixDuration = mixDuration entry.trackEnd = mixDuration + if delay <= 0 and entry.previous then + entry.delay = entry.previous:getTrackComplete() - entry.mixDuration + delay + end return entry end @@ -895,28 +886,19 @@ function AnimationState:_animationsChanged () self.animationsChanged = false self.propertyIDs = {} - - local highestIndex = -1 local tracks = self.tracks - local numTracks = getNumTracks(tracks) local i = 0 - while i <= numTracks do - entry = tracks[i] + local n = zlen(tracks) + while i < n do + local entry = tracks[i] if entry then - if i > highestIndex then highestIndex = i end - - if entry then - while entry.mixingFrom do - entry = entry.mixingFrom - end - - repeat - if entry.mixingTo == nil or entry.mixBlend ~= MixBlend.add then - self:computeHold(entry) - end - entry = entry.mixingTo - until (entry == nil) + while entry.mixingFrom do + entry = entry.mixingFrom end + repeat + if not entry.mixingTo or entry.mixBlend ~= MixBlend.add then self:computeHold(entry) end + entry = entry.mixingTo + until not entry end i = i + 1 end @@ -925,57 +907,57 @@ end function AnimationState:computeHold(entry) local to = entry.mixingTo local timelines = entry.animation.timelines - local timelinesCount = #entry.animation.timelines local timelineMode = entry.timelineMode local timelineHoldMix = entry.timelineHoldMix local propertyIDs = self.propertyIDs if to and to.holdPrevious then - local i = 1 - while i <= timelinesCount do - local id = "" .. timelines[i]:getPropertyId() - if propertyIDs[id] == nil then - propertyIDs[id] = id - timelineMode[i] = HOLD_FIRST - else - timelineMode[i] = HOLD_SUBSEQUENT + for i,timeline in ipairs(timelines) do + local mode = HOLD_SUBSEQUENT + for _,id in ipairs(timeline.propertyIds) do + if not propertyIDs[id] then + propertyIDs[id] = true + mode = HOLD_FIRST + end end + timelineMode[i] = mode end return end - local i = 1 - local skip - while i <= timelinesCount do - local id = "" .. timelines[i]:getPropertyId() - if propertyIDs[id] then - timelineMode[i] = SUBSEQUENT - else - propertyIDs[id] = id - local timeline = timelines[i] - if to == nil or timeline.type == Animation.TimelineType.attachment - or timeline.type == Animation.TimelineType.drawOrder - or timeline.type == Animation.TimelineType.event - or not to.animation:hasTimeline(id) then - timelineMode[i] = FIRST - else - local next = to.mixingTo - skip = false - while next do - if not next.animation:hasTimeline(id) then - if entry.mixDuration > 0 then - timelineMode[i] = HOLD_MIX - timelineHoldMix[i] = next - skip = true - break - end - end - next = next.mixingTo - end - if not skip then timelineMode[i] = HOLD_FIRST end + for i,timeline in ipairs(timelines) do + local ids = timeline.propertyIds + local added = false + for _,id in ipairs(ids) do + if not propertyIDs[id] then + propertyIDs[id] = true + added = true end end - i = i + 1 + if not added then + timelineMode[i] = SUBSEQUENT + elseif not to + or timeline.type == Animation.TimelineType.attachment + or timeline.type == Animation.TimelineType.drawOrder + or timeline.type == Animation.TimelineType.event + or not to.animation:hasTimeline(ids) then + timelineMode[i] = FIRST + else + local next = to.mixingTo + local set + while next do + if not next.animation:hasTimeline(ids) then + if next.mixDuration > 0 then + timelineMode[i] = HOLD_MIX + timelineHoldMix[i] = next + set = true + end + break + end + next = next.mixingTo + end + if not set then timelineMode[i] = HOLD_FIRST end + end end end diff --git a/spine-lua/spine-lua/Atlas.lua b/spine-lua/spine-lua/Atlas.lua deleted file mode 100644 index 3a10a9d8f..000000000 --- a/spine-lua/spine-lua/Atlas.lua +++ /dev/null @@ -1,104 +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 Atlas = {} - -function Atlas.parse(atlasPath, atlasBase) - local function parseIntTuple4( l ) - local a,b,c,d = string.match( l , " ? ?%a+: ([+-]?%d+), ?([+-]?%d+), ?([+-]?%d+), ?([+-]?%d+)" ) - a,b,c,d = tonumber( a ), tonumber( b ), tonumber( c ), tonumber( d ) - return a and b and c and d and {a, b, c ,d} - end - - local function parseIntTuple2( l ) - local a,b = string.match( l , " ? ?%a+: ([+-]?%d+), ?([+-]?%d+)" ) - a,b = tonumber( a ), tonumber( b ) - return a and b and {a, b} - end - - if not atlasPath then - error("Error: " .. atlasPath .. ".atlas" .. " doesn't exist!", 2) - return nil - end - - local atlasLines = spine.utils.readFile( atlasPath, atlasBase ) - if not atlasLines then - error("Error: " .. atlasPath .. ".atlas" .. " unable to read!", 2) - return nil - end - - local pages = {} - - - local it = string.gmatch(atlasLines, "(.-)\r?\n") -- iterate over lines - for l in it do - if #l == 0 then - l = it() - if l then - local page = { name = l } - l = it() - page.size = parseIntTuple2( l ) - if page.size then - l = it() - end - page.format = string.match( l, "%a+: (.+)" ) - page.filter = {string.match( it(), "%a+: (.+),(.+)" )} - page.wrap = string.match( it(), "%a+: (.+)" ) - page.regions = {} - table.insert( pages, page ) - else - break - end - else - local region = {name = l} - - region.rotate = string.match( it(), "%a+: (.+)" ) == "true" - region.xy = parseIntTuple2( it() ) - region.size = parseIntTuple2( it() ) - l = it() - region.splits = parseIntTuple4(l) - if region.splits then - l = it() - region.pad = parseIntTuple4(l) - if region.pad then - l = it() - end - end - region.orig = parseIntTuple2( l ) - region.offset = parseIntTuple2( it() ) - region.index = tonumber( string.match( it() , "%a+: ([+-]?%d+)" ) ) - - table.insert( pages[#pages].regions, region ) - end - end - - return pages -end - -return Atlas diff --git a/spine-lua/spine-lua/Bone.lua b/spine-lua/spine-lua/Bone.lua index 43965571d..aa4a23b19 100644 --- a/spine-lua/spine-lua/Bone.lua +++ b/spine-lua/spine-lua/Bone.lua @@ -27,6 +27,8 @@ -- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ------------------------------------------------------------------------------- +local TransformMode = require "spine-lua.TransformMode" + local setmetatable = setmetatable local math_rad = math.rad local math_deg = math.deg @@ -37,19 +39,6 @@ local math_sqrt = math.sqrt local math_abs = math.abs local math_pi = math.pi -local TransformMode = require "spine-lua.TransformMode" - -function math.sign(x) - if x < 0 then - return -1 - elseif x > 0 then - return 1 - end - return 0 -end - -local math_sign = math.sign - local Bone = {} Bone.__index = Bone @@ -64,7 +53,6 @@ function Bone.new (data, skeleton, parent) children = { }, x = 0, y = 0, rotation = 0, scaleX = 1, scaleY = 1, shearX = 0, shearY = 0, ax = 0, ay = 0, arotation = 0, ascaleX = 0, ascaleY = 0, ashearX = 0, ashearY = 0, - appliedValid = false, a = 0, b = 0, worldX = 0, -- a b x c = 0, d = 0, worldY = 0, -- c d y @@ -78,7 +66,7 @@ function Bone.new (data, skeleton, parent) end function Bone:update () - self:updateWorldTransformWith(self.x, self.y, self.rotation, self.scaleX, self.scaleY, self.shearX, self.shearY) + self:updateWorldTransformWith(self.ax, self.ay, self.arotation, self.ascaleX, self.ascaleY, self.ashearX, self.ashearY) end function Bone:updateWorldTransform () @@ -93,13 +81,12 @@ function Bone:updateWorldTransformWith (x, y, rotation, scaleX, scaleY, shearX, self.ascaleY = scaleY self.ashearX = shearX self.ashearY = shearY - self.appliedValid = true local sx = self.skeleton.scaleX local sy = self.skeleton.scaleY local parent = self.parent - if parent == nil then + if not parent then local rotationY = rotation + 90 + shearY local rotationRad = math_rad(rotation + shearX) local rotationYRad = math_rad(rotationY) @@ -225,7 +212,7 @@ end function Bone:updateAppliedTransform () local parent = self.parent - if parent == nil then + if not parent then self.ax = self.worldX self.ay = self.worldY self.arotation = math_deg(math_atan2(self.c, self.a)) @@ -268,15 +255,11 @@ function Bone:updateAppliedTransform () end function Bone:worldToLocal (world) - local a = self.a - local b = self.b - local c = self.c - local d = self.d - local invDet = 1 / (a * d - b * c) + local invDet = 1 / (self.a * self.d - self.b * self.c) local x = world[1] - self.worldX local y = world[2] - self.worldY - world[1] = (x * d * invDet - y * b * invDet) - world[2] = (y * a * invDet - x * c * invDet) + world[1] = x * self.d * invDet - y * self.b * invDet + world[2] = y * self.a * invDet - x * self.c * invDet return world end @@ -313,7 +296,6 @@ function Bone:rotateWorld (degrees) self.b = cos * b - sin * d self.c = sin * a + cos * c self.d = sin * b + cos * d - self.appliedValid = false end return Bone diff --git a/spine-lua/spine-lua/IkConstraint.lua b/spine-lua/spine-lua/IkConstraint.lua index 29e0715fa..1521f29bd 100644 --- a/spine-lua/spine-lua/IkConstraint.lua +++ b/spine-lua/spine-lua/IkConstraint.lua @@ -76,6 +76,7 @@ function IkConstraint:apply () end function IkConstraint:update () + if self.mix == 0 then return end local target = self.target local bones = self.bones local boneCount = #bones @@ -87,9 +88,7 @@ function IkConstraint:update () end function IkConstraint:apply1 (bone, targetX, targetY, compress, stretch, uniform, alpha) - if not bone.appliedValid then bone:updateAppliedTransform() end local p = bone.parent - local pa = p.a local pb = p.b local pc = p.c @@ -148,12 +147,6 @@ function IkConstraint:apply1 (bone, targetX, targetY, compress, stretch, uniform end function IkConstraint:apply2 (parent, child, targetX, targetY, bendDir, stretch, softness, alpha) - if alpha == 0 then - child:updateWorldTransform() - return - end - if not parent.appliedValid then parent:updateAppliedTransform() end - if not child.appliedValid then child:updateAppliedTransform() end local px = parent.ax local py = parent.ay local psx = parent.ascaleX diff --git a/spine-lua/spine-lua/PathConstraint.lua b/spine-lua/spine-lua/PathConstraint.lua index 6467043de..5faffc921 100644 --- a/spine-lua/spine-lua/PathConstraint.lua +++ b/spine-lua/spine-lua/PathConstraint.lua @@ -36,7 +36,7 @@ local AttachmentType = require "spine-lua.attachments.AttachmentType" local PathConstraintData = require "spine-lua.PathConstraintData" local utils = require "spine-lua.utils" local math_pi = math.pi -local math_pi2 = math.pi * 2 +local math_pi2 = math_pi * 2 local math_atan2 = math.atan2 local math_sqrt = math.sqrt local math_acos = math.acos @@ -51,10 +51,10 @@ local math_max = math.max local PathConstraint = {} PathConstraint.__index = PathConstraint -PathConstraint.NONE = -1 -PathConstraint.BEFORE = -2 -PathConstraint.AFTER = -3 -PathConstraint.epsilon = 0.00001 +local NONE = -1 +local BEFORE = -2 +local AFTER = -3 +local epsilon = 0.00001 function PathConstraint.new (data, skeleton) if not data then error("data cannot be nil", 2) end @@ -66,8 +66,9 @@ function PathConstraint.new (data, skeleton) target = skeleton:findSlot(data.target.name), position = data.position, spacing = data.spacing, - rotateMix = data.rotateMix, - translateMix = data.translateMix, + mixRotate = data.mixRotate, + mixX = data.mixX, + mixY = data.mixY, spaces = {}, positions = {}, world = {}, @@ -85,81 +86,113 @@ function PathConstraint.new (data, skeleton) return self end -function PathConstraint:apply () - self:update() -end - function PathConstraint:update () local attachment = self.target.attachment - if not attachment or not (attachment.type == AttachmentType.path) then return end - - local rotateMix = self.rotateMix - local translateMix = self.translateMix - local translate = translateMix > 0 - local rotate = rotateMix > 0 - if not translate and not rotate then return end + if not attachment or attachment.type ~= AttachmentType.path then return end + local mixRotate = self.mixRotate + local mixX = self.mixX + local mixY = self.mixY + if mixRotate == 0 and mixX == 0 and mixY == 0 then return end + local data = self.data - local percentSpacing = data.spacingMode == PathConstraintData.SpacingMode.percent - local rotateMode = data.rotateMode local tangents = rotateMode == PathConstraintData.RotateMode.tangent local scale = rotateMode == PathConstraintData.RotateMode.chainscale + local bones = self.bones local boneCount = #bones - local spacesCount = boneCount + 1 - if tangents then spacesCount = boneCount end + local spacesCount = boneCount + if tangents then spacesCount = spacesCount + 1 end local spaces = utils.setArraySize(self.spaces, spacesCount) local lengths = nil + if scale then lengths = Utils.setArraySize(this.lengths, boneCount) end local spacing = self.spacing - if scale or not percentSpacing then - if scale then lengths = utils.setArraySize(self.lengths, boneCount) end - local lengthSpacing = data.spacingMode == PathConstraintData.SpacingMode.length - local i = 0 - local n = spacesCount - 1 - while i < n do - local bone = bones[i + 1] - local setupLength = bone.data.length - if setupLength < PathConstraint.epsilon then - if scale then lengths[i + 1] = 0 end - i = i + 1 - spaces[i + 1] = 0 - elseif percentSpacing then - if scale then + + if data.spacingMode == PathConstraintData.SpacingMode.percent then + if scale then + local i = 0 + local n = spacesCount - 1 + while i < n do + local bone = bones[i] + local setupLength = bone.data.length + if setupLength < epsilon then + lengths[i] = 0 + else local x = setupLength * bone.a local y = setupLength * bone.c - local length = math_sqrt(x * x + y * y) - lengths[i + 1] = length + lengths[i] = math_sqrt(x * x + y * y) end i = i + 1 - spaces[i + 1] = spacing + end + end + local i = 1 + while i < spacesCount do + spaces[i] = spacing + i = i + 1 + end + elseif data.spacingMode == PathConstraintData.SpacingMode.proportional then + local sum = 0 + local i = 0 + while i < boneCount do + local bone = bones[i] + local setupLength = bone.data.length + if setupLength < epsilon then + if scale then lengths[i] = 0 end + i = i + 1 + spaces[i] = spacing else - local x = setupLength * bone.a + local x = setupLength * bone.a local y = setupLength * bone.c local length = math_sqrt(x * x + y * y) - if scale then lengths[i + 1] = length end + if scale then lengths[i] = length end + i = i + 1 + spaces[i] = length + sum = sum + length + end + end + if sum > 0 then + sum = spacesCount / sum * spacing + local i = 1 + while i < spacesCount do + spaces[i] = spaces[i] * sum i = i + 1 - if lengthSpacing then - spaces[i + 1] = (setupLength + spacing) * length / setupLength - else - spaces[i + 1] = spacing * length / setupLength - end end end else + local lengthSpacing = data.spacingMode == PathConstraintData.SpacingMode.length local i = 1 - while i < spacesCount do - spaces[i + 1] = spacing - i = i + 1 + local n = spacesCount - 1 + while i < n do + local bone = bones[i] + local setupLength = bone.data.length + if setupLength < epsilon then + if scale then lengths[i] = 0 end + i = i + 1 + spaces[i] = spacing + else + local x = setupLength * bone.a + local y = setupLength * bone.c + local length = math_sqrt(x * x + y * y) + if scale then lengths[i] = length end + i = i + 1 + local s + if lengthSpacing then + s = setupLength + spacing + else + s = spacing + end + spaces[i] = s * length / setupLength + end end end - local positions = self:computeWorldPositions(attachment, spacesCount, tangents, data.positionMode == PathConstraintData.PositionMode.percent, percentSpacing) + local positions = self:computeWorldPositions(attachment, spacesCount, tangents) local boneX = positions[1] local boneY = positions[2] local offsetRotation = data.offsetRotation local tip = false if offsetRotation == 0 then - tip = rotateMode == PathConstraintData.RotateMode.chain + tip = data.rotateMode == PathConstraintData.RotateMode.chain else tip = false local p = self.target.bone @@ -174,8 +207,8 @@ function PathConstraint:update () local p = 3 while i < boneCount do local bone = bones[i + 1] - bone.worldX = bone.worldX + (boneX - bone.worldX) * translateMix - bone.worldY = bone.worldY + (boneY - bone.worldY) * translateMix + bone.worldX = bone.worldX + (boneX - bone.worldX) * mixX + bone.worldY = bone.worldY + (boneY - bone.worldY) * mixY local x = positions[p + 1] local y = positions[p + 2] local dx = x - boneX @@ -183,14 +216,14 @@ function PathConstraint:update () if scale then local length = lengths[i + 1] if length ~= 0 then - local s = (math_sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1 + local s = (math_sqrt(dx * dx + dy * dy) / length - 1) * mixRotate + 1 bone.a = bone.a * s bone.c = bone.c * s end end boneX = x boneY = y - if rotate then + if mixRotate then local a = bone.a local b = bone.b local c = bone.c @@ -210,8 +243,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) * mixRotate + boneY = boneY + (length * (sin * a + cos * c) - dy) * mixRotate else r = r + offsetRotation end @@ -220,21 +253,21 @@ function PathConstraint:update () elseif r < -math_pi then r = r + math_pi2 end - r = r * rotateMix + r = r * mixRotate cos = math_cos(r) - sin = math.sin(r) + sin = math_sin(r) bone.a = cos * a - sin * c bone.b = cos * b - sin * d bone.c = sin * a + cos * c bone.d = sin * b + cos * d end - bone.appliedValid = false + bone:updateAppliedTransform() i = i + 1 p = p + 3 end end -function PathConstraint:computeWorldPositions (path, spacesCount, tangents, percentPosition, percentSpacing) +function PathConstraint:computeWorldPositions (path, spacesCount, tangents) local target = self.target local position = self.position local spaces = self.spaces @@ -250,20 +283,20 @@ function PathConstraint:computeWorldPositions (path, spacesCount, tangents, perc local lengths = path.lengths if closed then curveCount = curveCount - 1 else curveCount = curveCount - 2 end local pathLength = lengths[curveCount + 1] - if percentPosition then position = position * pathLength end - if percentSpacing then - i = 1 - while i < spacesCount do - spaces[i + 1] = spaces[i + 1] * pathLength - i = i + 1 - end + if self.data.positionMode == PathConstraintData.PositionMode.percent then position = position * pathLength end + + local multiplier = 1 + if self.data.spacingMode == PathConstraintData.SpacingMode.percent then + multiplier = pathLength + elseif self.data.spacingMode == PathConstraintData.SpacingMode.proportional then + multiplier = pathLength / spacesCount end 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] * multiplier position = position + space local p = position @@ -389,17 +422,14 @@ function PathConstraint:computeWorldPositions (path, spacesCount, tangents, perc i = i + 1 w = w + 6 end - if percentPosition then - position = position * pathLength - else - position = position * pathLength / path.lengths[curveCount] - end - if percentSpacing then - i = 1 - while i < spacesCount do - spaces[i + 1] = spaces[i + 1] * pathLength - i = i + 1 - end + + if self.data.positionMode == PathConstraintData.PositionMode.percent then position = position * pathLength end + + local multiplier = 1 + if self.data.spacingMode == PathConstraintData.SpacingMode.percent then + multiplier = pathLength + elseif self.data.spacingMode == PathConstraintData.SpacingMode.proportional then + multiplier = pathLength / spacesCount end local segments = self.segments @@ -409,7 +439,7 @@ function PathConstraint:computeWorldPositions (path, spacesCount, tangents, perc local curve = 0 local segment = 0 while i < spacesCount do - local space = spaces[i + 1] + local space = spaces[i + 1] * multiplier position = position + space local p = position diff --git a/spine-lua/spine-lua/PathConstraintData.lua b/spine-lua/spine-lua/PathConstraintData.lua index 2d5bb1115..4d4aa2600 100644 --- a/spine-lua/spine-lua/PathConstraintData.lua +++ b/spine-lua/spine-lua/PathConstraintData.lua @@ -43,8 +43,9 @@ function PathConstraintData.new (name) offsetRotation = 0, position = 0, spacing = 0, - rotateMix = 0, - translateMix = 0 + mixRotate = 0, + mixX = 0, + mixY = 0 } return self @@ -58,7 +59,8 @@ PathConstraintData.PositionMode = { PathConstraintData.SpacingMode = { length = 0, fixed = 1, - percent = 2 + percent = 2, + proportional = 3 } PathConstraintData.RotateMode = { diff --git a/spine-lua/spine-lua/Skeleton.lua b/spine-lua/spine-lua/Skeleton.lua index 88be5675f..cca6779df 100644 --- a/spine-lua/spine-lua/Skeleton.lua +++ b/spine-lua/spine-lua/Skeleton.lua @@ -59,7 +59,6 @@ function Skeleton.new (data) transformConstraints = {}, pathConstraints = {}, _updateCache = {}, - updateCacheReset = {}, skin = nil, color = Color.newWith(1, 1, 1, 1), time = 0, @@ -110,7 +109,6 @@ end function Skeleton:updateCache () local updateCache = {} self._updateCache = updateCache - self.updateCacheReset = {} local bones = self.bones for _, bone in ipairs(bones) do @@ -122,11 +120,11 @@ function Skeleton:updateCache () local skinBones = self.skin.bones for i, boneData in ipairs(skinBones) do local bone = bones[boneData.index] - while bone do + repeat bone.sorted = false bone.active = true bone = bone.parent - end + until not bone end end @@ -196,22 +194,18 @@ function Skeleton:sortIkConstraint (constraint) local parent = constrained[1] self:sortBone(parent) - if #constrained > 1 then + if #constrained == 1 then + table_insert(self._updateCache, constraint) + self:sortReset(parent.children) + else local child = constrained[#constrained] - local contains = false - for _, updatable in ipairs(self._updateCache) do - if updatable == child then - contains = true - break - end - end - if not contains then table_insert(self.updateCacheReset, child) end + self:sortBone(child) + + table_insert(self._updateCache, constraint) + + self:sortReset(parent.children) + child.sorted = true end - - table_insert(self._updateCache, constraint) - - self:sortReset(parent.children) - constrained[#constrained].sorted = true end function Skeleton:sortPathConstraint(constraint) @@ -266,7 +260,7 @@ function Skeleton:sortTransformConstraint(constraint) break end end - if not contains then table_insert(self.updateCacheReset, child) end + self:sortBone(child) end else for _,bone in ipairs(constrained) do @@ -279,7 +273,6 @@ function Skeleton:sortTransformConstraint(constraint) for _,bone in ipairs(constrained) do self:sortReset(bone.children) end - for _,bone in ipairs(constrained) do bone.sorted = true end @@ -333,8 +326,7 @@ end -- Updates the world transform for each bone and applies IK constraints. function Skeleton:updateWorldTransform () - local updateCacheReset = self.updateCacheReset - for _,bone in ipairs(updateCacheReset) do + for _,bone in ipairs(self.bones) do bone.ax = bone.x bone.ay = bone.y bone.arotation = bone.rotation @@ -342,11 +334,9 @@ function Skeleton:updateWorldTransform () bone.ascaleY = bone.scaleY bone.ashearX = bone.shearX bone.ashearY = bone.shearY - bone.appliedValid = true end - local updateCache = self._updateCache - for _, updatable in ipairs(updateCache) do + for _, updatable in ipairs(self._updateCache) do updatable:update() end end @@ -372,10 +362,12 @@ function Skeleton:setBonesToSetupPose () local transformConstraints = self.transformConstraints for _, constraint in ipairs(transformConstraints) do local data = constraint.data - constraint.rotateMix = data.rotateMix - constraint.translateMix = data.translateMix - constraint.scaleMix = data.scaleMix - constraint.shearMix = data.shearMix + constraint.mixRotate = data.mixRotate + constraint.mixX = data.mixX + constraint.mixY = data.mixY + constraint.mixScaleX = data.mixScaleX + constraint.mixScaleY = data.mixScaleY + constraint.mixShearY = data.mixShearY end local pathConstraints = self.pathConstraints @@ -383,8 +375,9 @@ function Skeleton:setBonesToSetupPose () local data = constraint.data constraint.position = data.position constraint.spacing = data.spacing - constraint.rotateMix = data.rotateMix - constraint.translateMix = data.translateMix + constraint.mixRotate = data.mixRotate + constraint.mixX = data.mixX + constraint.mixY = data.mixY end end diff --git a/spine-lua/spine-lua/SkeletonJson.lua b/spine-lua/spine-lua/SkeletonJson.lua index 63d815895..41d13bcb8 100644 --- a/spine-lua/spine-lua/SkeletonJson.lua +++ b/spine-lua/spine-lua/SkeletonJson.lua @@ -558,9 +558,9 @@ function SkeletonJson.new (attachmentLoader) for timelineName,timelineMap in pairs(slotMap) do if not timelineMap then elseif timelineName == "attachment" then - local timeline = Animation.AttachmentTimeline.new(#timelineMap, slotIndex) + local timeline = Animation.AttachmentTimeline.new(#timelineMap, #timelineMap, slotIndex) for i,keyMap in ipairs(timelineMap) do - timeline:setFrame(i + 1, getValue(keyMap, "time", 0), keyMap["name"]) + timeline:setFrame(i - 1, getValue(keyMap, "time", 0), keyMap["name"]) end table_insert(timelines, timeline) elseif timelineName == "rgba" then @@ -765,7 +765,7 @@ function SkeletonJson.new (attachmentLoader) 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)) + table_insert(timelines, readTimeline2(timelineMap, timeline, "x", "y", 1, 1)) elseif timelineName == "scalex" then local timeline = Animation.ScaleXTimeline.new(#timelineMap, #timelineMap, boneIndex) table_insert(timelines, readTimeline1(timelineMap, timeline, 1, 1)) @@ -774,7 +774,7 @@ function SkeletonJson.new (attachmentLoader) 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)) + table_insert(timelines, readTimeline2(timelineMap, timeline, "x", "y", 0, 1)) elseif timelineName == "shearx" then local timeline = Animation.ShearXTimeline.new(#timelineMap, #timelineMap, boneIndex) table_insert(timelines, readTimeline1(timelineMap, timeline, 0, 1)) @@ -901,7 +901,7 @@ function SkeletonJson.new (attachmentLoader) if map.path then for constraintName,constraintMap in pairs(map.path) do local constraint, constraintIndex = -1 - for i,other in pairs(skeletonData.transformConstraints) do + for i,other in pairs(skeletonData.pathConstraints) do if other.name == constraintName then constraintIndex = i constraint = other @@ -914,12 +914,12 @@ function SkeletonJson.new (attachmentLoader) if timelineName == "position" then local timeline = Animation.PathConstraintPositionTimeline.new(#timelineMap, #timelineMap, constraintIndex) local timelineScale = 1 - if constraint.positionMode == PositionMode.fixed then timelineScale = scale end + if constraint.positionMode == PathConstraintData.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 + if data.spacingMode == PathConstraintData.SpacingMode.Length or data.spacingMode == PathConstraintData.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) @@ -978,6 +978,7 @@ function SkeletonJson.new (attachmentLoader) if weighted then deformLength = math_floor(deformLength / 3) * 2 end local timeline = Animation.DeformTimeline.new(#timelineMap, #timelineMap, slotIndex, attachment) + local time = getValue(keyMap, "time", 0) local bezier = 0 for i,keyMap in ipairs(timelineMap) do local deform = nil @@ -1007,7 +1008,7 @@ function SkeletonJson.new (attachmentLoader) end end local frame = i - 1 - timeline:setFrame(frame, time, mixRotate, mixX, mixY) + timeline:setFrame(frame, time, deform) local nextMap = timelineMap[frame + 1] if not nextMap then timeline:shrink(bezier) @@ -1172,15 +1173,15 @@ function SkeletonJson.new (attachmentLoader) readCurve = function (curve, timeline, bezier, frame, value, time1, time2, value1, value2, scale) if curve == "stepped" then - if value ~= 0 then timeline.setStepped(frame) end + if value ~= 0 then timeline:setStepped(frame) end return bezier end - local i = value * 4 + local i = value * 4 + 1 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) + timeline:setBezier(bezier, frame, value, time1, value1, cx1, cy1, cx2, cy2, time2, value2) return bezier + 1 end diff --git a/spine-lua/spine-lua/Slot.lua b/spine-lua/spine-lua/Slot.lua index f681cb7ec..3bf84983e 100644 --- a/spine-lua/spine-lua/Slot.lua +++ b/spine-lua/spine-lua/Slot.lua @@ -60,9 +60,16 @@ end function Slot:setAttachment (attachment) if self.attachment == attachment then return end + if not attachment + or not attachment.isVertexAttachment + or not self.attachment + or not self.attachment.isVertexAttachment + or attachment.deformAttachment ~= self.attachment.deformAttachment + then + self.deform = {} + end self.attachment = attachment self.attachmentTime = self.bone.skeleton.time - self.deform = {} end function Slot:setAttachmentTime (time) diff --git a/spine-lua/spine-lua/TextureAtlas.lua b/spine-lua/spine-lua/TextureAtlas.lua index 02b1cb50f..e970f32ff 100644 --- a/spine-lua/spine-lua/TextureAtlas.lua +++ b/spine-lua/spine-lua/TextureAtlas.lua @@ -7,7 +7,7 @@ -- 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 +-- 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, @@ -28,6 +28,7 @@ ------------------------------------------------------------------------------- local setmetatable = setmetatable +local tonumber = tonumber local table_insert = table.insert local math_abs = math.abs @@ -47,7 +48,8 @@ function TextureAtlasPage.new () vWrap = nil, texture = nil, width = 0, - height = 0 + height = 0, + pma = false } setmetatable(self, TextureAtlasPage) return self @@ -73,168 +75,178 @@ function TextureAtlas:parse (atlasContent, imageLoader) if not atlasContent then error("atlasContent cannot be nil.", 2) end if not imageLoader then error("imageLoader cannot be nil.", 2) end - function lineIterator(s) - if s:sub(-1)~="\n" then s=s.."\n" end - return s:gmatch("(.-)\n") + local readLine = atlasContent:gmatch("[ \t]*(.-)[ \t]*\r?\n") + + local trim = function (value) + return value:match("^%s*(.-)%s*$") end - local lines = {} - local index = 0 - local numLines = 0 - for line in lineIterator(atlasContent) do - lines[numLines] = line - numLines = numLines + 1 - end + local entry = {} + local readEntry = function (entry, line) + if not line then return 0 end + if line:len() == 0 then return 0 end - local readLine = function () - if index >= numLines then return nil end - local line = lines[index] - index = index + 1 - return line - end - - local readValue = function () - local line = readLine() - local idx = line:find(":") - if not idx then error("Invalid line: " .. line, 2) end - return line:sub(idx + 1):match'^%s*(.*%S)' or '' - end - - local readTuple = function () - local line = readLine() - local idx = line:find(":") - if not idx then - error("Invalid line: " .. line, 2) - end + local colon = line:find(":") + if not colon then return 0 end + entry[0] = trim(line:sub(1, colon)) + local lastMatch = colon + 1 local i = 1 - local lastMatch = idx + 1 - local tuple = {} - while i <= 3 do + while true do local comma = line:find(",", lastMatch) - if not comma then break end - tuple[i] = line:sub(lastMatch, comma - 1):match'^%s*(.*%S)' or '' + if not comma then + entry[i] = trim(line:sub(lastMatch)) + return i + end + entry[i] = trim(line:sub(lastMatch, comma - lastMatch)) lastMatch = comma + 1 + if i == 4 then return 4 end i = i + 1 end - tuple[i] = line:sub(lastMatch):match'^%s*(.*%S)' or '' - return tuple end - local parseInt = function (str) - return tonumber(str) + local page + local region + + local pageFields = {} + pageFields["size"] = function () + page.width = tonumber(entry[1]) + page.height = tonumber(entry[2]) + end + pageFields["format"] = function () + -- page.format = Format[tuple[0]] we don't need format in Lua + end + pageFields["filter"] = function () + page.minFilter = TextureFilter[entry[1]] + page.magFilter = TextureFilter[entry[2]] + end + pageFields["repeat"] = function () + if entry[1]:find("x") then page.uWrap = TextureWrap.Repeat end + if entry[1]:find("y") then page.vWrap = TextureWrap.Repeat end + end + pageFields["pma"] = function () + page.pma = entry[1] == "true" end - local filterFromString = function (str) - str = str:lower() - if str == "nearest" then return TextureFilter.Nearest - elseif str == "linear" then return TextureFilter.Linear - elseif str == "mipmap" then return TextureFilter.MipMap - elseif str == "mipmapnearestnearest" then return TextureFilter.MipMapNearestNearest - elseif str == "mipmaplinearnearest" then return TextureFilter.MipMapLinearNearest - elseif str == "mipmapnearestlinear" then return TextureFilter.MipMapNearestLinear - elseif str == "mipmaplinearlinear" then return TextureFilter.MipMapLinearLinear - else error("Unknown texture wrap: " .. str, 2) + local regionFields = {} + regionFields["xy"] = function () -- Deprecated, use bounds. + region.x = tonumber(entry[1]) + region.y = tonumber(entry[2]) + end + regionFields["size"] = function () -- Deprecated, use bounds. + region.width = tonumber(entry[1]) + region.height = tonumber(entry[2]) + end + regionFields["bounds"] = function () + region.x = tonumber(entry[1]) + region.y = tonumber(entry[2]) + region.width = tonumber(entry[3]) + region.height = tonumber(entry[4]) + end + regionFields["offset"] = function () -- Deprecated, use offsets. + region.offsetX = tonumber(entry[1]) + region.offsetY = tonumber(entry[2]) + end + regionFields["orig"] = function () -- Deprecated, use offsets. + region.originalWidth = tonumber(entry[1]) + region.originalHeight = tonumber(entry[2]) + end + regionFields["offsets"] = function () + region.offsetX = tonumber(entry[1]) + region.offsetY = tonumber(entry[2]) + region.originalWidth = tonumber(entry[3]) + region.originalHeight = tonumber(entry[4]) + end + regionFields["rotate"] = function () + local value = entry[1] + if value == "true" then + region.degrees = 90 + elseif value ~= "false" then + region.degrees = tonumber(value) end end + regionFields["index"] = function () + region.index = tonumber(entry[1]) + end - local page = nil + local line = readLine() + -- Ignore empty lines before first entry. + while line and line:len() == 0 do + line = readLine() + end + -- Header entries. + while true do + if not line or line:len() == 0 then break end + if readEntry(entry, line) == 0 then break end -- Silently ignore all header fields. + line = readLine() + end + + -- Page and region entries. + local names + local values while true do - local line = readLine() if not line then break end - line = line:match'^%s*(.*%S)' or '' if line:len() == 0 then page = nil + line = readLine() elseif not page then page = TextureAtlasPage.new() - page.name = line - - local tuple = readTuple() - if #tuple == 2 then - page.width = parseInt(tuple[1]) - page.height = parseInt(tuple[2]) - tuple = readTuple() - else - -- We only support atlases that have the page width/height - -- encoded in them. That way we don't rely on any special - -- wrapper objects for images to get the page size from - error("Atlas must specify page width/height. Please export to the latest atlas format", 2) + page.name = trim(line) + while true do + line = readLine() + if readEntry(entry, line) == 0 then break end + local field = pageFields[entry[0]] + if field then field() end end - - tuple = readTuple() - page.minFilter = filterFromString(tuple[1]) - page.magFilter = filterFromString(tuple[2]) - - local direction = readValue() - page.uWrap = TextureWrap.ClampToEdge - page.vWrap = TextureWrap.ClampToEdge - if direction == "x" then - page.uWrap = TextureWrap.Repeat - elseif direction == "y" then - page.vWrap = TextureWrap.Repeat - elseif direction == "xy" then - page.uWrap = TextureWrap.Repeat - page.vWrap = TextureWrap.Repeat - end - - page.texture = imageLoader(line) - -- FIXME page.texture:setFilters(page.minFilter, page.magFilter) - -- FIXME page.texture:setWraps(page.uWrap, page.vWrap) + page.texture = imageLoader(page.name) + -- FIXME - Apply the filter and wrap settings to the texture. + -- page.texture:setFilters(page.minFilter, page.magFilter) + -- page.texture:setWraps(page.uWrap, page.vWrap) table_insert(self.pages, page) else - local region = TextureAtlasRegion.new() - region.name = line + region = TextureAtlasRegion.new() region.page = page - - local rotateValue = readValue() - if rotateValue == "true" then - region.degrees = 90 - elseif rotateValue == "false" then - region.degrees = 0 - else - region.degrees = tonumber(rotateValue) - end - if region.degrees == 90 then region.rotate = true end - - local tuple = readTuple() - local x = parseInt(tuple[1]) - local y = parseInt(tuple[2]) - - tuple = readTuple() - local width = parseInt(tuple[1]) - local height = parseInt(tuple[2]) - - region.u = x / page.width - region.v = y / page.height - if region.rotate then - region.u2 = (x + height) / page.width - region.v2 = (y + width) / page.height - else - region.u2 = (x + width) / page.width - region.v2 = (y + height) / page.height - end - - region.x = x - region.y = y - region.width = math_abs(width) - region.height = math_abs(height) - - -- Read and skip optional splits - tuple = readTuple() - if #tuple == 4 then - tuple = readTuple() - if #tuple == 4 then - readTuple() + region.name = line + while true do + line = readLine() + local count = readEntry(entry, line) + if count == 0 then break end + local field = regionFields[entry[0]] + if field then + field() + else + if not names then + names = {} + values = {} + end + table_insert(names, entry[0]) + local entryValues = {} + local i = 0 + while i < count do + table_insert(entryValues, tonumber(entry[i + 1])) + i = i + 1 + end + table_insert(values, entryValues) end end - - region.originalWidth = parseInt(tuple[1]) - region.originalHeight = parseInt(tuple[2]) - - tuple = readTuple() - region.offsetX = parseInt(tuple[1]) - region.offsetY = parseInt(tuple[2]) - - region.index = parseInt(readValue()) + if region.originalWidth == 0 and region.originalHeight == 0 then + region.originalWidth = region.width + region.originalHeight = region.height + end + if names and #names > 0 then + region.names = names + region.values = values + names = nil + values = nil + end + region.u = region.x / page.width + region.v = region.y / page.height + if region.degrees == 90 then + region.u2 = (region.x + region.height) / page.width + region.v2 = (region.y + region.width) / page.height + else + region.u2 = (region.x + region.width) / page.width + region.v2 = (region.y + region.height) / page.height + end region.texture = page.texture table_insert(self.regions, region) end diff --git a/spine-lua/spine-lua/TextureAtlasRegion.lua b/spine-lua/spine-lua/TextureAtlasRegion.lua index 2c33064e0..8a279d9d1 100644 --- a/spine-lua/spine-lua/TextureAtlasRegion.lua +++ b/spine-lua/spine-lua/TextureAtlasRegion.lua @@ -42,9 +42,10 @@ function TextureAtlasRegion.new () self.x = 0 self.y = 0 self.index = 0 - self.rotate = false self.degrees = 0 self.texture = nil + self.names = nil + self.values = nil setmetatable(self, TextureAtlasRegion) return self diff --git a/spine-lua/spine-lua/TransformConstraint.lua b/spine-lua/spine-lua/TransformConstraint.lua index fb0da967a..ce25974d3 100644 --- a/spine-lua/spine-lua/TransformConstraint.lua +++ b/spine-lua/spine-lua/TransformConstraint.lua @@ -53,7 +53,7 @@ function TransformConstraint.new (data, skeleton) data = data, bones = {}, target = nil, - rotateMix = data.rotateMix, translateMix = data.translateMix, scaleMix = data.scaleMix, shearMix = data.shearMix, + mixRotate = data.mixRotate, mixX = data.mixX, mixY = data.mixY, mixScaleX = data.mixScaleX, mixScaleY = data.mixScaleY, mixShearY = data.mixShearY, temp = { 0, 0 }, active = false } @@ -67,11 +67,9 @@ function TransformConstraint.new (data, skeleton) return self end -function TransformConstraint:apply () - self:update() -end - function TransformConstraint:update () + if self.mixRotate == 0 and self.mixX == 0 and self.mixY == 0 and self.mixScaleX == 0 and self.mixScaleX == 0 and self.mixShearY == 0 then return end + if self.data.local_ then if self.data.relative then self:applyRelativeLocal() @@ -88,10 +86,14 @@ function TransformConstraint:update () end function TransformConstraint:applyAbsoluteWorld () - local rotateMix = self.rotateMix - local translateMix = self.translateMix - local scaleMix = self.scaleMix - local shearMix = self.shearMix + local mixRotate = self.mixRotate + local mixX = self.mixX + local mixY = self.mixY + local mixScaleX = self.mixScaleX + local mixScaleY = self.mixScaleY + local mixShearY = self.mixShearY + local translate = mixX ~= 0 or mixY ~= 0 + local target = self.target local ta = target.a local tb = target.b @@ -101,10 +103,10 @@ function TransformConstraint:applyAbsoluteWorld () 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 + local bones = self.bones for _, bone in ipairs(bones) do - local modified = false - if rotateMix ~= 0 then + if mixRotate ~= 0 then local a = bone.a local b = bone.b local c = bone.c @@ -115,45 +117,38 @@ function TransformConstraint:applyAbsoluteWorld () elseif r < -math_pi then r = r + math_pi2 end - r = r * rotateMix + r = r * mixRotate local cos = math_cos(r) local sin = math_sin(r) bone.a = cos * a - sin * c bone.b = cos * b - sin * d bone.c = sin * a + cos * c bone.d = sin * b + cos * d - modified = true end - if translateMix ~= 0 then + if translate then local temp = self.temp temp[1] = self.data.offsetX temp[2] = self.data.offsetY target:localToWorld(temp) - bone.worldX = bone.worldX + (temp[1] - bone.worldX) * translateMix - bone.worldY = bone.worldY + (temp[2] - bone.worldY) * translateMix - modified = true + bone.worldX = bone.worldX + (temp[1] - bone.worldX) * mixX + bone.worldY = bone.worldY + (temp[2] - bone.worldY) * mixY end - if scaleMix > 0 then + if mixScaleX ~= 0 then local s = math_sqrt(bone.a * bone.a + bone.c * bone.c) - local ts = math_sqrt(ta * ta + tc * tc) - if s > 0.00001 then - s = (s + (ts - s + self.data.offsetScaleX) * scaleMix) / s - end + if s ~= 0 then s = (s + (math_sqrt(ta * ta + tc * tc) - s + self.data.offsetScaleX) * mixScaleX) / s end bone.a = bone.a * s bone.c = bone.c * s - s = math_sqrt(bone.b * bone.b + bone.d * bone.d) - ts = math_sqrt(tb * tb + td * td) - if s > 0.00001 then - s = (s + (ts - s + self.data.offsetScaleY) * scaleMix) / s - end + end + if mixScaleY ~= 0 then + local s = math_sqrt(bone.b * bone.b + bone.d * bone.d) + if s ~= 0 then s = (s + (math_sqrt(tb * tb + td * td) - s + self.data.offsetScaleY) * mixScaleY) / s end bone.b = bone.b * s bone.d = bone.d * s - modified = true end - if shearMix > 0 then + if mixShearY > 0 then local b = bone.b local d = bone.d local by = math_atan2(d, b) @@ -163,22 +158,25 @@ function TransformConstraint:applyAbsoluteWorld () elseif r < -math_pi then r = r + math_pi2 end - r = by + (r + offsetShearY) * shearMix + r = by + (r + offsetShearY) * mixShearY local s = math_sqrt(b * b + d * d) bone.b = math_cos(r) * s bone.d = math_sin(r) * s - modified = true end - if modified then bone.appliedValid = false end + bone:updateAppliedTransform() end end function TransformConstraint:applyRelativeWorld () - local rotateMix = self.rotateMix - local translateMix = self.translateMix - local scaleMix = self.scaleMix - local shearMix = self.shearMix + local mixRotate = self.mixRotate + local mixX = self.mixX + local mixY = self.mixY + local mixScaleX = self.mixScaleX + local mixScaleY = self.mixScaleY + local mixShearY = self.mixShearY + local translate = mixX ~= 0 or mixY ~= 0 + local target = self.target local ta = target.a local tb = target.b @@ -188,11 +186,12 @@ function TransformConstraint:applyRelativeWorld () 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 + local bones = self.bones for _, bone in ipairs(bones) do local modified = false - if rotateMix ~= 0 then + if mixRotate ~= 0 then local a = bone.a local b = bone.b local c = bone.c @@ -203,37 +202,36 @@ function TransformConstraint:applyRelativeWorld () elseif r < -math_pi then r = r + math_pi2 end - r = r * rotateMix + r = r * mixRotate local cos = math_cos(r) local sin = math_sin(r) bone.a = cos * a - sin * c bone.b = cos * b - sin * d bone.c = sin * a + cos * c bone.d = sin * b + cos * d - modified = true end - if translateMix ~= 0 then + if translate then local temp = self.temp temp[1] = self.data.offsetX temp[2] = self.data.offsetY target:localToWorld(temp) - bone.worldX = bone.worldX + temp[1] * translateMix - bone.worldY = bone.worldY + temp[2] * translateMix - modified = true + bone.worldX = bone.worldX + temp[1] * mixX + bone.worldY = bone.worldY + temp[2] * mixY end - if scaleMix > 0 then - local s = (math_sqrt(ta * ta + tc * tc) - 1 + self.data.offsetScaleX) * scaleMix + 1 + if mixScaleX ~= 0 then + local s = (math_sqrt(ta * ta + tc * tc) - 1 + self.data.offsetScaleX) * mixScaleX + 1 bone.a = bone.a * s bone.c = bone.c * s - s = (math_sqrt(tb * tb + td * td) - 1 + self.data.offsetScaleY) * scaleMix + 1 + end + if mixScaleY ~= 0 then + local s = (math_sqrt(tb * tb + td * td) - 1 + self.data.offsetScaleY) * mixScaleY + 1 bone.b = bone.b * s bone.d = bone.d * s - modified = true end - if shearMix > 0 then + if mixShearY > 0 then local r = math_atan2(td, tb) - math_atan2(tc, ta) if r > math_pi then r = r - math_pi2 @@ -242,59 +240,54 @@ 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) * mixShearY local s = math_sqrt(b * b + d * d) bone.b = math_cos(r) * s bone.d = math_sin(r) * s - modified = true end - if modified then bone.appliedValid = false end + bone:updateAppliedTransform() end end function TransformConstraint:applyAbsoluteLocal () - local rotateMix = self.rotateMix - local translateMix = self.translateMix - local scaleMix = self.scaleMix - local shearMix = self.shearMix + local mixRotate = self.mixRotate + local mixX = self.mixX + local mixY = self.mixY + local mixScaleX = self.mixScaleX + local mixScaleY = self.mixScaleY + local mixShearY = self.mixShearY + local target = self.target if not target.appliedValid then target:updatedAppliedTransform() end local bones = self.bones for _, bone in ipairs(bones) do - local modified = false - if not bone.appliedValid then bone:updateAppliedTransform() end - local rotation = bone.arotation - if rotateMix ~= 0 then + if mixRotate ~= 0 then local r = target.arotation - rotation + self.data.offsetRotation r = r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360 - rotation = rotation + r * rotateMix + rotation = rotation + r * mixRotate end local x = bone.ax local y = bone.ay - if translateMix ~= 0 then - x = x + (target.ax - x + self.data.offsetX) * translateMix - y = x + (target.ay - y + self.data.offsetY) * translateMix - end + x = x + (target.ax - x + self.data.offsetX) * mixX + y = x + (target.ay - y + self.data.offsetY) * mixX local scaleX = bone.ascaleX local scaleY = bone.ascaleY - if scaleMix ~= 0 then - if scaleX > 0.00001 then - scaleX = (scaleX + (target.ascaleX - scaleX + self.data.offsetScaleX) * scaleMix) / scaleX - end - if scaleY > 0.00001 then - scaleY = (scaleY + (target.ascaleY - scaleY + self.data.offsetScaleY) * scaleMix) / scaleY - end + if mixScaleX ~= 0 and scaleX ~= 0 then + scaleX = (scaleX + (target.ascaleX - scaleX + self.data.offsetScaleX) * mixScaleX) / scaleX + end + if mixScaleY ~= 0 and scaleY ~= 0 then + scaleY = (scaleY + (target.ascaleY - scaleY + self.data.offsetScaleY) * mixScaleY) / scaleY end local shearY = bone.ashearY - if shearMix ~= 0 then + if mixShearY ~= 0 then local r = target.ashearY - shearY + self.data.offsetShearY r = r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360 - bone.shearY = bone.shearY + r * shearMix + bone.shearY = bone.shearY + r * mixShearY end bone:updateWorldTransformWith(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY) @@ -302,40 +295,23 @@ function TransformConstraint:applyAbsoluteLocal () end function TransformConstraint:applyRelativeLocal () - local rotateMix = self.rotateMix - local translateMix = self.translateMix - local scaleMix = self.scaleMix - local shearMix = self.shearMix + local mixRotate = self.mixRotate + local mixX = self.mixX + local mixY = self.mixY + local mixScaleX = self.mixScaleX + local mixScaleY = self.mixScaleY + local mixShearY = self.mixShearY + local target = self.target - if not target.appliedValid then target:updateAppliedTransform() end + local bones = self.bones for _, bone in ipairs(bones) do - if not bone.appliedValid then bone:updateAppliedTransform() end - - local rotation = bone.arotation - if rotateMix ~= 0 then rotation = rotation + (target.arotation + self.data.offsetRotation) * rotateMix end - - local x = bone.ax - local y = bone.ay - if translateMix ~= 0 then - x = x + (target.ax + self.data.offsetX) * translateMix - y = y + (target.ay + self.data.offsetY) * translateMix - end - - local scaleX = bone.ascaleX - local scaleY = bone.ascaleY - if scaleMix ~= 0 then - if scaleX > 0.00001 then - scaleX = scaleX * (((target.ascaleX - 1 + self.data.offsetScaleX) * scaleMix) + 1) - end - if scaleY > 0.00001 then - scaleY = scaleY * (((target.ascaleY - 1 + self.data.offsetScaleY) * scaleMix) + 1) - end - end - - local shearY = bone.ashearY - if shearMix ~= 0 then shearY = shearY + (target.ashearY + self.data.offsetShearY) * shearMix end - + local rotation = bone.arotation + (target.arotation + this.data.offsetRotation) * mixRotate + local x = bone.ax + (target.ax + this.data.offsetX) * mixX + local y = bone.ay + (target.ay + this.data.offsetY) * mixY + local scaleX = (bone.ascaleX * ((target.ascaleX - 1 + this.data.offsetScaleX) * mixScaleX) + 1) + local scaleY = (bone.ascaleY * ((target.ascaleY - 1 + this.data.offsetScaleY) * mixScaleY) + 1) + local shearY = bone.ashearY + (target.ashearY + this.data.offsetShearY) * mixShearY bone:updateWorldTransformWith(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY) end end diff --git a/spine-lua/spine-lua/TransformConstraintData.lua b/spine-lua/spine-lua/TransformConstraintData.lua index 5866ef6e3..7491e8024 100644 --- a/spine-lua/spine-lua/TransformConstraintData.lua +++ b/spine-lua/spine-lua/TransformConstraintData.lua @@ -37,7 +37,7 @@ function TransformConstraintData.new (name) skinRequired = false, bones = {}, target = nil, - rotateMix = 0, translateMix = 0, scaleMix = 0, shearMix = 0, + mixRotate = 0, mixX = 0, mixY = 0, mixScaleX = 0, mixScaleY = 0, mixShearY = 0, offsetRotation = 0, offsetX = 0, offsetY = 0, offsetScaleX = 0, offsetScaleY = 0, offsetShearY = 0, relative = false, local_ = false diff --git a/spine-lua/spine-lua/attachments/VertexAttachment.lua b/spine-lua/spine-lua/attachments/VertexAttachment.lua index beda40fd1..348c92f3f 100644 --- a/spine-lua/spine-lua/attachments/VertexAttachment.lua +++ b/spine-lua/spine-lua/attachments/VertexAttachment.lua @@ -37,7 +37,6 @@ local AttachmentType = require "spine-lua.attachments.AttachmentType" local Attachment = require "spine-lua.attachments.Attachment" local nextID = 0 -local SHL_11 = 2048 local VertexAttachment = {} VertexAttachment.__index = VertexAttachment @@ -45,16 +44,16 @@ setmetatable(VertexAttachment, { __index = Attachment }) function VertexAttachment.new (name, attachmentType) local self = Attachment.new(name, attachmentType) - self.vertexAttachment = true + + self.id = nextID + nextID = nextID + 1 + + self.isVertexAttachment = true self.bones = nil self.vertices = nil self.worldVerticesLength = 0 - while nextID > 65535 do - nextID = nextID - 65535 - end - self.id = nextID * SHL_11 self.deformAttachment = self - nextID = nextID + 1 + setmetatable(self, VertexAttachment) return self end