Flip timelines for spine-lua, spine-corona, spine-love.

This commit is contained in:
NathanSweet 2014-11-16 00:04:53 +01:00
parent 631add64fb
commit 59204db7f8
32 changed files with 286 additions and 331 deletions

View File

@ -38,4 +38,3 @@ Runtime:addEventListener("enterFrame", function (event)
state:apply(skeleton)
skeleton:updateWorldTransform()
end)

View File

@ -1,290 +0,0 @@
dragon.png
format: RGBA4444
filter: Nearest,Nearest
repeat: none
L_rear_thigh
rotate: false
xy: 895, 20
size: 91, 148
orig: 91, 149
offset: 0, 0
index: -1
L_wing01
rotate: false
xy: 814, 672
size: 191, 256
orig: 191, 256
offset: 0, 0
index: -1
L_wing02
rotate: false
xy: 714, 189
size: 179, 269
orig: 179, 269
offset: 0, 0
index: -1
L_wing03
rotate: false
xy: 785, 463
size: 186, 207
orig: 186, 207
offset: 0, 0
index: -1
L_wing05
rotate: true
xy: 2, 9
size: 218, 213
orig: 218, 213
offset: 0, 0
index: -1
L_wing06
rotate: false
xy: 2, 229
size: 192, 331
orig: 192, 331
offset: 0, 0
index: -1
R_wing01
rotate: true
xy: 502, 709
size: 219, 310
orig: 219, 310
offset: 0, 0
index: -1
R_wing02
rotate: true
xy: 204, 463
size: 203, 305
orig: 203, 305
offset: 0, 0
index: -1
R_wing03
rotate: false
xy: 511, 460
size: 272, 247
orig: 272, 247
offset: 0, 0
index: -1
R_wing05
rotate: false
xy: 196, 232
size: 251, 229
orig: 251, 229
offset: 0, 0
index: -1
R_wing06
rotate: false
xy: 2, 562
size: 200, 366
orig: 200, 366
offset: 0, 0
index: -1
R_wing07
rotate: true
xy: 449, 258
size: 200, 263
orig: 200, 263
offset: 0, 0
index: -1
R_wing08
rotate: false
xy: 467, 2
size: 234, 254
orig: 234, 254
offset: 0, 0
index: -1
R_wing09
rotate: false
xy: 217, 26
size: 248, 204
orig: 248, 204
offset: 0, 0
index: -1
back
rotate: false
xy: 703, 2
size: 190, 185
orig: 190, 185
offset: 0, 0
index: -1
chest
rotate: true
xy: 895, 170
size: 136, 122
orig: 136, 122
offset: 0, 0
index: -1
front_toeA
rotate: false
xy: 976, 972
size: 29, 50
orig: 29, 50
offset: 0, 0
index: -1
head
rotate: false
xy: 204, 668
size: 296, 260
orig: 296, 260
offset: 0, 0
index: -1
logo
rotate: false
xy: 2, 930
size: 897, 92
orig: 897, 92
offset: 0, 0
index: -1
tail01
rotate: false
xy: 895, 308
size: 120, 153
orig: 120, 153
offset: 0, 0
index: -1
tail03
rotate: false
xy: 901, 930
size: 73, 92
orig: 73, 92
offset: 0, 0
index: -1
dragon2.png
format: RGBA4444
filter: Nearest,Nearest
repeat: none
L_front_leg
rotate: true
xy: 391, 141
size: 84, 57
orig: 84, 57
offset: 0, 0
index: -1
L_front_thigh
rotate: false
xy: 446, 269
size: 84, 72
orig: 84, 72
offset: 0, 0
index: -1
L_rear_leg
rotate: true
xy: 888, 342
size: 168, 132
orig: 206, 177
offset: 19, 20
index: -1
L_wing04
rotate: false
xy: 256, 227
size: 188, 135
orig: 188, 135
offset: 0, 0
index: -1
L_wing07
rotate: false
xy: 2, 109
size: 159, 255
orig: 159, 255
offset: 0, 0
index: -1
L_wing08
rotate: true
xy: 705, 346
size: 164, 181
orig: 164, 181
offset: 0, 0
index: -1
L_wing09
rotate: false
xy: 499, 343
size: 204, 167
orig: 204, 167
offset: 0, 0
index: -1
R_front_leg
rotate: false
xy: 273, 34
size: 101, 89
orig: 101, 89
offset: 0, 0
index: -1
R_front_thigh
rotate: false
xy: 163, 106
size: 108, 108
orig: 108, 108
offset: 0, 0
index: -1
R_rear_leg
rotate: false
xy: 273, 125
size: 116, 100
orig: 116, 100
offset: 0, 0
index: -1
R_rear_thigh
rotate: false
xy: 163, 216
size: 91, 148
orig: 91, 149
offset: 0, 0
index: -1
R_wing04
rotate: false
xy: 2, 366
size: 279, 144
orig: 279, 144
offset: 0, 0
index: -1
chin
rotate: false
xy: 283, 364
size: 214, 146
orig: 214, 146
offset: 0, 0
index: -1
front_toeB
rotate: false
xy: 590, 284
size: 56, 57
orig: 56, 57
offset: 0, 0
index: -1
rear-toe
rotate: true
xy: 2, 2
size: 105, 77
orig: 109, 77
offset: 0, 0
index: -1
tail02
rotate: true
xy: 151, 9
size: 95, 120
orig: 95, 120
offset: 0, 0
index: -1
tail04
rotate: false
xy: 532, 270
size: 56, 71
orig: 56, 71
offset: 0, 0
index: -1
tail05
rotate: false
xy: 648, 282
size: 52, 59
orig: 52, 59
offset: 0, 0
index: -1
tail06
rotate: true
xy: 81, 12
size: 95, 68
orig: 95, 68
offset: 0, 0
index: -1

View File

@ -0,0 +1,39 @@
-- This skeleton uses IK for the feet.
local spine = require "spine-corona.spine"
local json = spine.SkeletonJson.new()
local skeletonData = json:readSkeletonDataFile("examples/hero/hero.json")
local skeleton = spine.Skeleton.new(skeletonData)
function skeleton:createImage (attachment)
return display.newImage("examples/hero/images/" .. attachment.name .. ".png")
end
skeleton.group.x = 195
skeleton.group.y = 385
skeleton.flipX = false
skeleton.flipY = false
skeleton.debug = true -- Omit or set to false to not draw debug lines on top of the images.
skeleton:setToSetupPose()
-- AnimationStateData defines crossfade durations between animations.
local stateData = spine.AnimationStateData.new(skeletonData)
-- AnimationState has a queue of animations and can apply them with crossfading.
local state = spine.AnimationState.new(stateData)
--state:setAnimationByName(0, "Idle", true, 0)
state:setAnimationByName(0, "Walk", true, 0)
local lastTime = 0
local animationTime = 0
Runtime:addEventListener("enterFrame", function (event)
-- Compute time in seconds since last frame.
local currentTime = event.time / 1000
local delta = currentTime - lastTime
lastTime = currentTime
-- Update the state with the delta time, apply it, and update the world transforms.
state:update(delta)
state:apply(skeleton)
skeleton:updateWorldTransform()
end)

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -0,0 +1,5 @@
Copyright (c) 2014, XDTech
The project file and images in this "Hero" project are provided for
demonstration purposes only and may not be redistributed for any reason nor
used as the basis for derivative work.

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -1,4 +1,5 @@
require "examples.spineboy"
-- require "examples.spineboy"
-- require "examples.goblins"
-- require "examples.dragon"
require "examples.hero"

View File

@ -128,10 +128,11 @@ function spine.Skeleton.new (skeletonData, group)
end
-- Position image based on attachment and bone.
if image ~= spine.Skeleton.failed then
local flipX, flipY = ((self.flipX and -1) or 1), ((self.flipY and -1) or 1)
local bone = slot.bone
local flipX, flipY = ((bone.worldFlipX and -1) or 1), ((bone.worldFlipY and -1) or 1)
local x = slot.bone.worldX + attachment.x * slot.bone.m00 + attachment.y * slot.bone.m01
local y = -(slot.bone.worldY + attachment.x * slot.bone.m10 + attachment.y * slot.bone.m11)
local x = bone.worldX + attachment.x * bone.m00 + attachment.y * bone.m01
local y = -(bone.worldY + attachment.x * bone.m10 + attachment.y * bone.m11)
if not image.lastX then
image.x, image.y = x, y
image.lastX, image.lastY = x, y
@ -145,16 +146,16 @@ function spine.Skeleton.new (skeletonData, group)
-- Fix scaling when attachment is rotated 90 or -90.
local rotation = math.abs(attachment.rotation) % 180
if (rotation == 90) then
xScale = xScale * slot.bone.worldScaleY
yScale = yScale * slot.bone.worldScaleX
xScale = xScale * bone.worldScaleY
yScale = yScale * bone.worldScaleX
else
xScale = xScale * slot.bone.worldScaleX
yScale = yScale * slot.bone.worldScaleY
xScale = xScale * bone.worldScaleX
yScale = yScale * bone.worldScaleY
if rotation ~= 0 and xScale ~= yScale and not image.rotationWarning then
image.rotationWarning = true
print("WARNING: Non-uniform bone scaling with attachments not rotated to\n"
.." cardinal angles will not work as expected with Corona.\n"
.." Bone: "..slot.bone.data.name..", slot: "..slot.data.name..", attachment: "..attachment.name)
.." Bone: "..bone.data.name..", slot: "..slot.data.name..", attachment: "..attachment.name)
end
end
if not image.lastScaleX then
@ -165,7 +166,7 @@ function spine.Skeleton.new (skeletonData, group)
image.lastScaleX, image.lastScaleY = xScale, yScale
end
rotation = -(slot.bone.worldRotation + attachment.rotation) * flipX * flipY
rotation = -(bone.worldRotation + attachment.rotation) * flipX * flipY
if not image.lastRotation then
image.rotation = rotation
image.lastRotation = rotation
@ -199,13 +200,13 @@ function spine.Skeleton.new (skeletonData, group)
bone.line.x = bone.worldX
bone.line.y = -bone.worldY
bone.line.rotation = -bone.worldRotation
if self.flipX then
if bone.worldFlipX then
bone.line.xScale = -1
bone.line.rotation = -bone.line.rotation
else
bone.line.xScale = 1
end
if self.flipY then
if bone.worldFlipY then
bone.line.yScale = -1
bone.line.rotation = -bone.line.rotation
else

View File

@ -83,6 +83,22 @@ local function binarySearch (values, target, step)
end
end
local function binarySearch1 (values, target)
local low = 0
local high = math.floor(#values - 1)
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)
for i = 0, #values, step do
if (values[i] > target) then return i end
@ -419,7 +435,7 @@ function Animation.AttachmentTimeline.new ()
if time >= frames[#frames] then -- Time is after last frame.
frameIndex = #frames
else
frameIndex = binarySearch(frames, time, 1) - 1
frameIndex = binarySearch1(frames, time) - 1
end
local attachmentName = self.attachmentNames[frameIndex]
@ -477,7 +493,7 @@ function Animation.EventTimeline.new ()
if lastTime < frames[0] then
frameIndex = 0
else
frameIndex = binarySearch(frames, lastTime, 1)
frameIndex = binarySearch1(frames, lastTime)
local frame = frames[frameIndex]
while frameIndex > 0 do -- Fire multiple events with the same frame.
if frames[frameIndex - 1] ~= frame then break end
@ -522,7 +538,7 @@ function Animation.DrawOrderTimeline.new ()
if time >= frames[#frames] then -- Time is after last frame.
frameIndex = #frames
else
frameIndex = binarySearch(frames, time, 1) - 1
frameIndex = binarySearch1(frames, time) - 1
end
local drawOrder = skeleton.drawOrder
@ -542,4 +558,147 @@ function Animation.DrawOrderTimeline.new ()
return self
end
Animation.FfdTimeline = {}
function Animation.FfdTimeline.new ()
local self = Animation.CurveTimeline.new()
self.frames = {}
self.frameVertices = {}
self.slotIndex = -1
function self:getDuration ()
return self.frames[#self.frames]
end
function self:getFrameCount ()
return #self.frames + 1
end
function self:setFrame (frameIndex, time, vertices)
self.frames[frameIndex] = time
self.frameVertices[frameIndex] = vertices
end
function self:apply (skeleton, lastTime, time, firedEvents, alpha)
local slot = skeleton.slots[self.slotIndex]
if slot.attachment ~= attachment then return end
local frames = self.frames
if time < frames[0] then -- Time is before first frame.
slot.attachmentVerticesCount = 0
return
end
local frameVertices = self.frameVertices
local vertexCount = #frameVertices[0]
local vertices = slot.attachmentVertices
if #vertices < vertexCount then
vertices = {}
vertices[vertexCount] = 0
slot.attachmentVertices = vertices
elseif #vertices < vertexCount then
alpha = 1 -- Don't mix from uninitialized slot vertices.
end
slot.attachmentVerticesCount = vertexCount
if time >= frames[#frames] then -- Time is after last frame.
local lastVertices = frameVertices[#frames.Length]
if alpha < 1 then
for i = 0, vertexCount do
local vertex = vertices[i]
vertices[i] = vertex + (lastVertices[i] - vertex) * alpha
end
else
for i = 0, vertexCount do
vertices[i] = lastVertices[i]
end
end
return
end
-- Interpolate between the previous frame and the current frame.
local frameIndex = binarySearch1(frames, time)
local frameTime = frames[frameIndex]
local percent = 1 - (time - frameTime) / (frames[frameIndex - 1] - frameTime)
if percent < 0 then percent = 0 elseif percent > 1 then percent = 1 end
percent = self:getCurvePercent(frameIndex - 1, percent)
local prevVertices = frameVertices[frameIndex - 1]
local nextVertices = frameVertices[frameIndex]
if alpha < 1 then
for i = 0, vertexCount do
local prev = prevVertices[i]
local vertices = vertices[i]
vertices[i] = vertices + (prev + (nextVertices[i] - prev) * percent - vertices) * alpha
end
else
for i = 0, vertexCount do
local prev = prevVertices[i]
vertices[i] = prev + (nextVertices[i] - prev) * percent
end
end
end
return self
end
Animation.FlipXTimeline = {}
function Animation.FlipXTimeline.new ()
local self = {
frames = {}, -- time, flip, ...
boneIndex = -1
}
function self:getDuration ()
return self.frames[#self.frames - 1]
end
function self:getFrameCount ()
return (#self.frames + 1) / 2
end
function self:setFrame (frameIndex, time, flip)
frameIndex = frameIndex * 2
self.frames[frameIndex] = time
self.frames[frameIndex + 1] = flip
end
function self:apply (skeleton, lastTime, time, firedEvents, alpha)
local frames = self.frames
if time < frames[0] then
if lastTime > time then self:apply(skeleton, lastTime, 999999, null, 0) end
return
elseif lastTime > time then
lastTime = -1
end
local frameIndex
if time >= frames[#frames - 1] then
frameIndex = #frames - 1
else
frameIndex = binarySearch(frames, time, 2) - 2
end
if frames[frameIndex] < lastTime then return end
self:setFlip(skeleton.bones[self.boneIndex], frames[frameIndex + 1])
end
function self:setFlip (bone, flip)
bone.flipX = flip
end
return self
end
Animation.FlipYTimeline = {}
function Animation.FlipYTimeline.new ()
local self = Animation.FlipXTimeline.new()
function self:setFlip (bone, flip)
bone.flipY = flip
end
return self
end
return Animation

View File

@ -30,19 +30,23 @@
local Bone = {}
function Bone.new (data, parent)
function Bone.new (data, skeleton, parent)
if not data then error("data cannot be nil", 2) end
if not skeleton then error("skeleton cannot be nil", 2) end
local self = {
data = data,
skeleton = skeleton,
parent = parent,
x = 0, y = 0,
rotation = 0,
scaleX = 1, scaleY = 1,
flipX = false, flipY = false,
m00 = 0, m01 = 0, worldX = 0, -- a b x
m10 = 0, m11 = 0, worldY = 0, -- c d y
worldRotation = 0,
worldScaleX = 1, worldScaleY = 1,
worldFlipX = false, worldFlipY = false,
}
function self:updateWorldTransform (flipX, flipY)
@ -62,13 +66,16 @@ function Bone.new (data, parent)
else
self.worldRotation = self.rotation
end
self.worldFlipX = parent.worldFlipX ~= self.flipX
self.worldFlipY = parent.worldFlipY ~= self.flipY
else
if flipX then
local skeletonFlipX, skeletonFlipY = self.skeleton.flipX, self.skeleton.flipY
if skeletonFlipX then
self.worldX = -self.x
else
self.worldX = self.x
end
if flipY then
if skeletonFlipY then
self.worldY = -self.y
else
self.worldY = self.y
@ -76,21 +83,25 @@ function Bone.new (data, parent)
self.worldScaleX = self.scaleX
self.worldScaleY = self.scaleY
self.worldRotation = self.rotation
self.worldFlipX = skeletonFlipX ~= self.flipX
self.worldFlipY = skeletonFlipY ~= self.flipY
end
local radians = math.rad(self.worldRotation)
local cos = math.cos(radians)
local sin = math.sin(radians)
self.m00 = cos * self.worldScaleX
self.m10 = sin * self.worldScaleX
self.m01 = -sin * self.worldScaleY
self.m11 = cos * self.worldScaleY
if flipX then
self.m00 = -self.m00
self.m01 = -self.m01
if self.worldFlipX then
self.m00 = -cos * self.worldScaleX
self.m01 = sin * self.worldScaleY
else
self.m00 = cos * self.worldScaleX
self.m01 = -sin * self.worldScaleY
end
if flipY then
self.m10 = -self.m10
self.m11 = -self.m11
if self.worldFlipY then
self.m10 = -sin * self.worldScaleX
self.m11 = -cos * self.worldScaleY
else
self.m10 = sin * self.worldScaleX
self.m11 = cos * self.worldScaleY
end
end
@ -101,6 +112,8 @@ function Bone.new (data, parent)
self.rotation = data.rotation
self.scaleX = data.scaleX
self.scaleY = data.scaleY
self.flipX = data.flipX
self.flipY = data.flipY
end
self:setToSetupPose()

View File

@ -51,7 +51,7 @@ function Skeleton.new (skeletonData)
function self:updateWorldTransform ()
for i,bone in ipairs(self.bones) do
bone:updateWorldTransform(self.flipX, self.flipY)
bone:updateWorldTransform()
end
end
@ -165,12 +165,12 @@ function Skeleton.new (skeletonData)
for i,boneData in ipairs(skeletonData.bones) do
local parent
if boneData.parent then parent = self.bones[spine.utils.indexOf(skeletonData.bones, boneData.parent)] end
table.insert(self.bones, Bone.new(boneData, parent))
table.insert(self.bones, Bone.new(boneData, self, parent))
end
for i,slotData in ipairs(skeletonData.slots) do
local bone = self.bones[spine.utils.indexOf(skeletonData.bones, slotData.boneData)]
local slot = Slot.new(slotData, self, bone)
local slot = Slot.new(slotData, bone)
table.insert(self.slots, slot)
self.slotsByName[slot.data.name] = slot
table.insert(self.drawOrder, slot)

View File

@ -86,6 +86,8 @@ function SkeletonJson.new (attachmentLoader)
else
boneData.scaleY = 1
end
boneData.flipX = boneMap["flipX"] or false
boneData.flipY = boneMap["flipY"] or false
if boneMap["inheritScale"] == false then
boneData.inheritScale = false
else
@ -375,6 +377,27 @@ function SkeletonJson.new (attachmentLoader)
table.insert(timelines, timeline)
duration = math.max(duration, timeline:getDuration())
elseif timelineName == "flipX" or timelineName == "flipY" then
local x = timelineName == "flipX"
local timeline, field
if x then
timeline = Animation.FlipXTimeline.new()
field = "x"
else
timeline = Animation.FlipYTimeline.new();
field = "y"
end
timeline.boneIndex = boneIndex
local frameIndex = 0
for i,valueMap in ipairs(values) do
local flip
timeline:setFrame(frameIndex, valueMap["time"], valueMap[field] or false)
frameIndex = frameIndex + 1
end
table.insert(timelines, timeline)
duration = math.max(duration, timeline:getDuration())
else
error("Invalid timeline type for a bone: " .. timelineName .. " (" .. boneName .. ")")
end

View File

@ -29,19 +29,18 @@
-------------------------------------------------------------------------------
local Slot = {}
function Slot.new (slotData, skeleton, bone)
function Slot.new (slotData, bone)
if not slotData then error("slotData cannot be nil", 2) end
if not skeleton then error("skeleton cannot be nil", 2) end
if not bone then error("bone cannot be nil", 2) end
local self = {
data = slotData,
skeleton = skeleton,
bone = bone,
r = 1, g = 1, b = 1, a = 1,
attachment = nil,
attachmentTime = 0,
attachmentVertices = nil
attachmentVertices = nil,
attachmentVerticesCount = 0
}
function self:setColor (r, g, b, a)
@ -53,15 +52,16 @@ function Slot.new (slotData, skeleton, bone)
function self:setAttachment (attachment)
self.attachment = attachment
self.attachmentTime = self.skeleton.time
self.attachmentTime = self.bone.skeleton.time
self.attachmentVerticesCount = 0
end
function self:setAttachmentTime (time)
self.attachmentTime = self.skeleton.time - time
self.attachmentTime = self.bone.skeleton.time - time
end
function self:getAttachmentTime ()
return self.skeleton.time - self.attachmentTime
return self.bone.skeleton.time - self.attachmentTime
end
function self:setToSetupPose ()
@ -71,7 +71,7 @@ function Slot.new (slotData, skeleton, bone)
local attachment
if data.attachmentName then
attachment = self.skeleton:getAttachment(data.name, data.attachmentName)
attachment = self.bone.skeleton:getAttachment(data.name, data.attachmentName)
end
self:setAttachment(attachment)
end