123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563 |
- -------------------------------------------------------------------------------
- -- 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
- PathConstraint.epsilon = 0.00001
- 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 _,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 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 spaces = utils.setArraySize(self.spaces, spacesCount)
- local lengths = nil
- 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
- local x = setupLength * bone.a
- local y = setupLength * bone.c
- local length = math_sqrt(x * x + y * y)
- lengths[i + 1] = length
- end
- i = i + 1
- spaces[i + 1] = 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 + 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
- 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, percentSpacing)
- 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
- local i = 0
- 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
- i = 1
- while i < spacesCount do
- spaces[i + 1] = spaces[i + 1] * pathLength
- i = i + 1
- end
- end
- world = utils.setArraySize(self.world, 8);
- 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
- 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
- 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
- end
- local segments = self.segments
- local curveLength = 0
- 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
- out[o + 1] = x1
- out[o + 2] = y1
- out[o + 3] = math_atan2(cy1 - y1, cx1 - x1)
- return;
- 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
- if p < 0.001 then
- out[o + 3] = math_atan2(cy1 - y1, cx1 - x1)
- else
- out[o + 3] = math_atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt))
- end
- end
- end
- return PathConstraint
|