mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2025-12-20 17:26:01 +08:00
529 lines
16 KiB
Lua
529 lines
16 KiB
Lua
-------------------------------------------------------------------------------
|
|
-- Spine Runtimes Software License v2.5
|
|
--
|
|
-- Copyright (c) 2013-2016, Esoteric Software
|
|
-- All rights reserved.
|
|
--
|
|
-- You are granted a perpetual, non-exclusive, non-sublicensable, and
|
|
-- non-transferable license to use, install, execute, and perform the Spine
|
|
-- Runtimes software and derivative works solely for personal or internal
|
|
-- use. Without the written permission of Esoteric Software (see Section 2 of
|
|
-- the Spine Software License Agreement), you may not (a) modify, translate,
|
|
-- adapt, or develop new applications using the Spine Runtimes or otherwise
|
|
-- create derivative works or improvements of the Spine Runtimes or (b) remove,
|
|
-- delete, alter, or obscure any trademarks or any copyright, trademark, patent,
|
|
-- or other intellectual property or proprietary rights notices on or in the
|
|
-- Software, including any copy thereof. Redistributions in binary or source
|
|
-- form must include this license and terms.
|
|
--
|
|
-- THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
|
|
-- IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
-- MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
|
-- EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
-- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
-- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, 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 THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
-- POSSIBILITY OF SUCH DAMAGE.
|
|
-------------------------------------------------------------------------------
|
|
|
|
-- FIXME the logic in this file uses 0-based indexing. Each array
|
|
-- access adds 1 to the calculated index. We should switch the logic
|
|
-- to 1-based indexing eventually.
|
|
|
|
local setmetatable = setmetatable
|
|
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_atan2 = math.atan2
|
|
local math_sqrt = math.sqrt
|
|
local math_acos = math.acos
|
|
local math_sin = math.sin
|
|
local math_cos = math.cos
|
|
local table_insert = table.insert
|
|
local math_deg = math.deg
|
|
local math_rad = math.rad
|
|
local math_abs = math.abs
|
|
local math_max = math.max
|
|
|
|
local PathConstraint = {}
|
|
PathConstraint.__index = PathConstraint
|
|
|
|
PathConstraint.NONE = -1
|
|
PathConstraint.BEFORE = -2
|
|
PathConstraint.AFTER = -3
|
|
|
|
function PathConstraint.new (data, skeleton)
|
|
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,
|
|
bones = {},
|
|
target = skeleton:findSlot(data.target.name),
|
|
position = data.position,
|
|
spacing = data.spacing,
|
|
rotateMix = data.rotateMix,
|
|
translateMix = data.translateMix,
|
|
spaces = {},
|
|
positions = {},
|
|
world = {},
|
|
curves = {},
|
|
lengths = {},
|
|
segments = {}
|
|
}
|
|
setmetatable(self, PathConstraint)
|
|
|
|
for i,boneData in ipairs(data.bones) do
|
|
table_insert(self.bones, skeleton:findBone(boneData.name))
|
|
end
|
|
|
|
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
|
|
|
|
local data = self.data;
|
|
local spacingMode = data.spacingMode
|
|
local lengthSpacing = spacingMode == PathConstraintData.SpacingMode.length
|
|
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 spaces = utils.setArraySize(self.spaces, spacesCount)
|
|
local lengths = nil
|
|
local spacing = self.spacing
|
|
if scale or lengthSpacing then
|
|
if scale then lengths = utils.setArraySize(self.lengths, boneCount) end
|
|
local i = 0
|
|
local n = spacesCount - 1
|
|
while i < n do
|
|
local bone = bones[i + 1];
|
|
local setupLength = bone.data.length
|
|
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
|
|
i = i + 1
|
|
if lengthSpacing then spaces[i + 1] = (setupLength + spacing) * length / setupLength else spaces[i + 1] = spacing * length / setupLength end
|
|
end
|
|
else
|
|
local i = 1
|
|
while i < spacesCount do
|
|
spaces[i + 1] = spacing
|
|
i = i + 1
|
|
end
|
|
end
|
|
|
|
local positions = self:computeWorldPositions(attachment, spacesCount, tangents, data.positionMode == PathConstraintData.PositionMode.percent, spacingMode == PathConstraintData.SpacingMode.percent)
|
|
local boneX = positions[1]
|
|
local boneY = positions[2]
|
|
local offsetRotation = data.offsetRotation
|
|
local tip = false;
|
|
if offsetRotation == 0 then
|
|
tip = rotateMode == PathConstraintData.RotateMode.chain
|
|
else
|
|
tip = false;
|
|
local p = self.target.bone;
|
|
if p.a * p.d - p.b * p.c > 0 then
|
|
offsetRotation = offsetRotation * utils.degRad
|
|
else
|
|
offsetRotation = offsetRotation * -utils.degRad
|
|
end
|
|
end
|
|
|
|
local i = 0
|
|
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
|
|
local x = positions[p + 1]
|
|
local y = positions[p + 2]
|
|
local dx = x - boneX
|
|
local dy = y - boneY
|
|
if scale then
|
|
local length = lengths[i + 1]
|
|
if length ~= 0 then
|
|
local s = (math_sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1
|
|
bone.a = bone.a * s
|
|
bone.c = bone.c * s
|
|
end
|
|
end
|
|
boneX = x
|
|
boneY = y
|
|
if rotate then
|
|
local a = bone.a
|
|
local b = bone.b
|
|
local c = bone.c
|
|
local d = bone.d
|
|
local r = 0
|
|
local cos = 0
|
|
local sin = 0
|
|
if tangents then
|
|
r = positions[p - 1 + 1]
|
|
elseif spaces[i + 1 + 1] == 0 then
|
|
r = positions[p + 2 + 1]
|
|
else
|
|
r = math_atan2(dy, dx)
|
|
end
|
|
r = r - math_atan2(c, a)
|
|
if tip then
|
|
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;
|
|
else
|
|
r = r + offsetRotation
|
|
end
|
|
if r > math_pi then
|
|
r = r - math_pi2
|
|
elseif r < -math_pi then
|
|
r = r + math_pi2
|
|
end
|
|
r = r * rotateMix
|
|
cos = math_cos(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
|
|
i = i + 1
|
|
p = p + 3
|
|
end
|
|
end
|
|
|
|
function PathConstraint:computeWorldPositions (path, spacesCount, tangents, percentPosition, percentSpacing)
|
|
local target = self.target
|
|
local position = self.position
|
|
local spaces = self.spaces
|
|
local out = utils.setArraySize(self.positions, spacesCount * 3 + 2)
|
|
local world = nil
|
|
local closed = path.closed
|
|
local verticesLength = path.worldVerticesLength
|
|
local curveCount = verticesLength / 6
|
|
local prevCurve = PathConstraint.NONE
|
|
|
|
if not path.constantSpeed then
|
|
local lengths = path.lengths
|
|
if closed then curveCount = curveCount - 1 else curveCount = curveCount - 2 end
|
|
local pathLength = lengths[curveCount + 1];
|
|
if percentPosition then position = position * pathLength end
|
|
if percentSpacing then
|
|
local i = 0
|
|
while i < spacesCount do
|
|
spaces[i + 1] = spaces[i + 1] * pathLength
|
|
i = i + 1
|
|
end
|
|
end
|
|
world = utils.setArraySize(self.world, 8);
|
|
local i = 0
|
|
local o = 0
|
|
local curve = 0
|
|
while i < spacesCount do
|
|
local space = spaces[i + 1];
|
|
position = position + space
|
|
local p = position
|
|
|
|
local skip = false
|
|
if closed then
|
|
p = p % pathLength
|
|
if p < 0 then p = p + pathLength end
|
|
curve = 0
|
|
elseif p < 0 then
|
|
if prevCurve ~= PathConstraint.BEFORE then
|
|
prevCurve = PathConstraint.BEFORE
|
|
path:computeWorldVertices(target, 2, 4, world, 0, 2)
|
|
end
|
|
self:addBeforePosition(p, world, 0, out, o)
|
|
skip = true
|
|
elseif p > pathLength then
|
|
if prevCurve ~= PathConstraint.AFTER then
|
|
prevCurve = PathConstraint.AFTER
|
|
path:computeWorldVertices(target, verticesLength - 6, 4, world, 0, 2)
|
|
end
|
|
self:addAfterPosition(p - pathLength, world, 0, out, o)
|
|
skip = true
|
|
end
|
|
|
|
if not skip then
|
|
-- Determine curve containing position.
|
|
while true do
|
|
local length = lengths[curve + 1]
|
|
if p <= length then
|
|
if curve == 0 then
|
|
p = p / length
|
|
else
|
|
local prev = lengths[curve - 1 + 1]
|
|
p = (p - prev) / (length - prev)
|
|
end
|
|
break
|
|
end
|
|
curve = curve + 1
|
|
end
|
|
if curve ~= prevCurve then
|
|
prevCurve = curve
|
|
if closed and curve == curveCount then
|
|
path:computeWorldVertices(target, verticesLength - 4, 4, world, 0, 2)
|
|
path:computeWorldVertices(target, 0, 4, world, 4, 2)
|
|
else
|
|
path:computeWorldVertices(target, curve * 6 + 2, 8, world, 0, 2)
|
|
end
|
|
end
|
|
self:addCurvePosition(p, world[1], world[2], world[3], world[4], world[5], world[6], world[7], world[8], out, o, tangents or (i > 0 and space == 0))
|
|
end
|
|
|
|
i = i + 1
|
|
o = o + 3
|
|
end
|
|
return out
|
|
end
|
|
|
|
-- World vertices.
|
|
if closed then
|
|
verticesLength = verticesLength + 2
|
|
world = utils.setArraySize(self.world, verticesLength)
|
|
path:computeWorldVertices(target, 2, verticesLength - 4, world, 0, 2)
|
|
path:computeWorldVertices(target, 0, 2, world, verticesLength - 4, 2)
|
|
world[verticesLength - 2 + 1] = world[0 + 1]
|
|
world[verticesLength - 1 + 1] = world[1 + 1]
|
|
else
|
|
curveCount = curveCount - 1
|
|
verticesLength = verticesLength - 4;
|
|
world = utils.setArraySize(self.world, verticesLength)
|
|
path:computeWorldVertices(target, 2, verticesLength, world, 0, 2)
|
|
end
|
|
|
|
-- Curve lengths.
|
|
local curves = utils.setArraySize(self.curves, curveCount)
|
|
local pathLength = 0;
|
|
local x1 = world[0 + 1]
|
|
local y1 = world[1 + 1]
|
|
local cx1 = 0
|
|
local cy1 = 0
|
|
local cx2 = 0
|
|
local cy2 = 0
|
|
local x2 = 0
|
|
local y2 = 0
|
|
local tmpx = 0
|
|
local tmpy = 0
|
|
local dddfx = 0
|
|
local dddfy = 0
|
|
local ddfx = 0
|
|
local ddfy = 0
|
|
local dfx = 0
|
|
local dfy = 0
|
|
i = 0
|
|
local w = 2
|
|
while i < curveCount do
|
|
cx1 = world[w + 1]
|
|
cy1 = world[w + 2]
|
|
cx2 = world[w + 3]
|
|
cy2 = world[w + 4]
|
|
x2 = world[w + 5]
|
|
y2 = world[w + 6]
|
|
tmpx = (x1 - cx1 * 2 + cx2) * 0.1875
|
|
tmpy = (y1 - cy1 * 2 + cy2) * 0.1875
|
|
dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375
|
|
dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375
|
|
ddfx = tmpx * 2 + dddfx
|
|
ddfy = tmpy * 2 + dddfy
|
|
dfx = (cx1 - x1) * 0.75 + tmpx + dddfx * 0.16666667
|
|
dfy = (cy1 - y1) * 0.75 + tmpy + dddfy * 0.16666667
|
|
pathLength = pathLength + math_sqrt(dfx * dfx + dfy * dfy)
|
|
dfx = dfx + ddfx
|
|
dfy = dfy + ddfy
|
|
ddfx = ddfx + dddfx
|
|
ddfy = ddfy + dddfy
|
|
pathLength = pathLength + math_sqrt(dfx * dfx + dfy * dfy)
|
|
dfx = dfx + ddfx
|
|
dfy = dfy + ddfy
|
|
pathLength = pathLength + math_sqrt(dfx * dfx + dfy * dfy)
|
|
dfx = dfx + ddfx + dddfx
|
|
dfy = dfy + ddfy + dddfy
|
|
pathLength = pathLength + math_sqrt(dfx * dfx + dfy * dfy)
|
|
curves[i + 1] = pathLength
|
|
x1 = x2
|
|
y1 = y2
|
|
i = i + 1
|
|
w = w + 6
|
|
end
|
|
if percentPosition then position = position * pathLength end
|
|
if percentSpacing then
|
|
local i = 0
|
|
while i < spacesCount do
|
|
spaces[i + 1] = spaces[i + 1] * pathLength
|
|
i = i + 1
|
|
end
|
|
end
|
|
|
|
local segments = self.segments
|
|
local curveLength = 0
|
|
local i = 0
|
|
local o = 0
|
|
local curve = 0
|
|
local segment = 0
|
|
while i < spacesCount do
|
|
local space = spaces[i + 1]
|
|
position = position + space
|
|
local p = position
|
|
|
|
local skip = false
|
|
if closed then
|
|
p = p % pathLength
|
|
if p < 0 then p = p + pathLength end
|
|
curve = 0
|
|
elseif p < 0 then
|
|
self:addBeforePosition(p, world, 0, out, o)
|
|
skip = true
|
|
elseif p > pathLength then
|
|
self:addAfterPosition(p - pathLength, world, verticesLength - 4, out, o)
|
|
skip = true
|
|
end
|
|
|
|
if not skip then
|
|
-- Determine curve containing position.
|
|
while true do
|
|
local length = curves[curve + 1]
|
|
if p <= length then
|
|
if curve == 0 then
|
|
p = p / length
|
|
else
|
|
local prev = curves[curve - 1 + 1]
|
|
p = (p - prev) / (length - prev)
|
|
end
|
|
break
|
|
end
|
|
curve = curve + 1
|
|
end
|
|
|
|
-- Curve segment lengths.
|
|
if curve ~= prevCurve then
|
|
prevCurve = curve
|
|
local ii = curve * 6
|
|
x1 = world[ii + 1]
|
|
y1 = world[ii + 2]
|
|
cx1 = world[ii + 3]
|
|
cy1 = world[ii + 4]
|
|
cx2 = world[ii + 5]
|
|
cy2 = world[ii + 6]
|
|
x2 = world[ii + 7]
|
|
y2 = world[ii + 8]
|
|
tmpx = (x1 - cx1 * 2 + cx2) * 0.03
|
|
tmpy = (y1 - cy1 * 2 + cy2) * 0.03
|
|
dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006
|
|
dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.006
|
|
ddfx = tmpx * 2 + dddfx
|
|
ddfy = tmpy * 2 + dddfy
|
|
dfx = (cx1 - x1) * 0.3 + tmpx + dddfx * 0.16666667
|
|
dfy = (cy1 - y1) * 0.3 + tmpy + dddfy * 0.16666667
|
|
curveLength = math_sqrt(dfx * dfx + dfy * dfy)
|
|
segments[1] = curveLength
|
|
ii = 1
|
|
while ii < 8 do
|
|
dfx = dfx + ddfx
|
|
dfy = dfy + ddfy
|
|
ddfx = ddfx + dddfx
|
|
ddfy = ddfy + dddfy
|
|
curveLength = curveLength + math_sqrt(dfx * dfx + dfy * dfy)
|
|
segments[ii + 1] = curveLength
|
|
ii = ii + 1
|
|
end
|
|
dfx = dfx + ddfx
|
|
dfy = dfy + ddfy
|
|
curveLength = curveLength + math_sqrt(dfx * dfx + dfy * dfy)
|
|
segments[9] = curveLength
|
|
dfx = dfx + ddfx + dddfx
|
|
dfy = dfy + ddfy + dddfy
|
|
curveLength = curveLength + math_sqrt(dfx * dfx + dfy * dfy)
|
|
segments[10] = curveLength
|
|
segment = 0
|
|
end
|
|
|
|
-- Weight by segment length.
|
|
p = p * curveLength
|
|
while true do
|
|
local length = segments[segment + 1]
|
|
if p <= length then
|
|
if segment == 0 then
|
|
p = p / length
|
|
else
|
|
local prev = segments[segment - 1 + 1]
|
|
p = segment + (p - prev) / (length - prev)
|
|
end
|
|
break;
|
|
end
|
|
segment = segment + 1
|
|
end
|
|
self:addCurvePosition(p * 0.1, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents or (i > 0 and space == 0))
|
|
end
|
|
|
|
i = i + 1
|
|
o = o + 3
|
|
end
|
|
return out
|
|
end
|
|
|
|
function PathConstraint:addBeforePosition (p, temp, i, out, o)
|
|
local x1 = temp[i + 1]
|
|
local y1 = temp[i + 2]
|
|
local dx = temp[i + 3] - x1
|
|
local dy = temp[i + 4] - y1
|
|
local r = math_atan2(dy, dx)
|
|
out[o + 1] = x1 + p * math_cos(r)
|
|
out[o + 2] = y1 + p * math_sin(r)
|
|
out[o + 3] = r
|
|
end
|
|
|
|
function PathConstraint:addAfterPosition(p, temp, i, out, o)
|
|
local x1 = temp[i + 3]
|
|
local y1 = temp[i + 4]
|
|
local dx = x1 - temp[i + 1]
|
|
local dy = y1 - temp[i + 2]
|
|
local r = math_atan2(dy, dx)
|
|
out[o + 1] = x1 + p * math_cos(r)
|
|
out[o + 2] = y1 + p * math_sin(r)
|
|
out[o + 3] = r
|
|
end
|
|
|
|
function PathConstraint:addCurvePosition(p, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents)
|
|
if p == 0 or (p ~= p) then p = 0.0001 end
|
|
local tt = p * p
|
|
local ttt = tt * p
|
|
local u = 1 - p
|
|
local uu = u * u
|
|
local uuu = uu * u
|
|
local ut = u * p
|
|
local ut3 = ut * 3
|
|
local uut3 = u * ut3
|
|
local utt3 = ut3 * p
|
|
local x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt
|
|
local y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt
|
|
out[o + 1] = x
|
|
out[o + 2] = y
|
|
if tangents then out[o + 3] = math_atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)) end
|
|
end
|
|
|
|
return PathConstraint
|