mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2025-12-20 17:26:01 +08:00
282 lines
8.3 KiB
Lua
282 lines
8.3 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.
|
|
-------------------------------------------------------------------------------
|
|
|
|
local setmetatable = setmetatable
|
|
local math_pi = math.pi
|
|
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 IkConstraint = {}
|
|
IkConstraint.__index = IkConstraint
|
|
|
|
function IkConstraint.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 = nil,
|
|
mix = data.mix,
|
|
compress = data.compress,
|
|
stretch = data.stretch,
|
|
bendDirection = data.bendDirection,
|
|
}
|
|
setmetatable(self, IkConstraint)
|
|
|
|
local self_bones = self.bones
|
|
for _,boneData in ipairs(data.bones) do
|
|
table_insert(self_bones, skeleton:findBone(boneData.name))
|
|
end
|
|
self.target = skeleton:findBone(data.target.name)
|
|
|
|
return self
|
|
end
|
|
|
|
function IkConstraint:apply ()
|
|
self:update()
|
|
end
|
|
|
|
function IkConstraint:update ()
|
|
local target = self.target
|
|
local bones = self.bones
|
|
local boneCount = #bones
|
|
if boneCount == 1 then
|
|
self:apply1(bones[1], target.worldX, target.worldY, self.compress, self.stretch, self.data.uniform, self.mix)
|
|
elseif boneCount == 2 then
|
|
self:apply2(bones[1], bones[2], target.worldX, target.worldY, self.bendDirection, self.stretch, self.mix)
|
|
end
|
|
end
|
|
|
|
function IkConstraint:apply1 (bone, targetX, targetY, compress, stretch, uniform, alpha)
|
|
if not bone.appliedValid then bone:updateAppliedTransform() end
|
|
local p = bone.parent
|
|
local id = 1 / (p.a * p.d - p.b * p.c)
|
|
local x = targetX - p.worldX
|
|
local y = targetY - p.worldY
|
|
local tx = (x * p.d - y * p.b) * id - bone.ax
|
|
local ty = (y * p.a - x * p.c) * id - bone.ay
|
|
local rotationIK = math_deg(math_atan2(ty, tx)) - bone.ashearX - bone.arotation
|
|
if bone.ascaleX < 0 then rotationIK = rotationIK + 180 end
|
|
if rotationIK > 180 then
|
|
rotationIK = rotationIK - 360
|
|
elseif (rotationIK < -180) then
|
|
rotationIK = rotationIK + 360
|
|
end
|
|
local sx = bone.ascaleX
|
|
local sy = bone.ascaleY
|
|
if compress or stretch then
|
|
local b = bone.data.length * sx
|
|
local dd = math_sqrt(tx * tx + ty * ty)
|
|
if (compress and dd < b) or (stretch and dd > b) and b > 0.0001 then
|
|
local s = (dd / b - 1) * alpha + 1
|
|
sx = sx * s
|
|
if uniform then sy = sy * s end
|
|
end
|
|
end
|
|
bone:updateWorldTransformWith(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, sx, sy, bone.ashearX, bone.ashearY)
|
|
end
|
|
|
|
function IkConstraint:apply2 (parent, child, targetX, targetY, bendDir, stretch, 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
|
|
local sx = psx
|
|
local psy = parent.ascaleY
|
|
local csx = child.ascaleX
|
|
local os1 = 0
|
|
local os2 = 0
|
|
local s2 = 0
|
|
if psx < 0 then
|
|
psx = -psx
|
|
os1 = 180
|
|
s2 = -1
|
|
else
|
|
os1 = 0
|
|
s2 = 1
|
|
end
|
|
if psy < 0 then
|
|
psy = -psy
|
|
s2 = -s2
|
|
end
|
|
if csx < 0 then
|
|
csx = -csx
|
|
os2 = 180
|
|
else
|
|
os2 = 0
|
|
end
|
|
local cx = child.ax
|
|
local cy = 0
|
|
local cwx = 0
|
|
local cwy = 0
|
|
local a = parent.a
|
|
local b = parent.b
|
|
local c = parent.c
|
|
local d = parent.d
|
|
local u = math_abs(psx - psy) <= 0.0001
|
|
if not u then
|
|
cy = 0
|
|
cwx = a * cx + parent.worldX
|
|
cwy = c * cx + parent.worldY
|
|
else
|
|
cy = child.ay
|
|
cwx = a * cx + b * cy + parent.worldX
|
|
cwy = c * cx + d * cy + parent.worldY
|
|
end
|
|
local pp = parent.parent
|
|
a = pp.a
|
|
b = pp.b
|
|
c = pp.c
|
|
d = pp.d
|
|
local id = 1 / (a * d - b * c)
|
|
local x = targetX - pp.worldX
|
|
local y = targetY - pp.worldY
|
|
local tx = (x * d - y * b) * id - px
|
|
local ty = (y * a - x * c) * id - py
|
|
local dd = tx * tx + ty * ty
|
|
x = cwx - pp.worldX
|
|
y = cwy - pp.worldY
|
|
local dx = (x * d - y * b) * id - px
|
|
local dy = (y * a - x * c) * id - py
|
|
local l1 = math_sqrt(dx * dx + dy * dy)
|
|
local l2 = child.data.length * csx
|
|
local a1 = 0
|
|
local a2 = 0
|
|
|
|
if u then
|
|
l2 = l2 * psx
|
|
local cos = (dd - l1 * l1 - l2 * l2) / (2 * l1 * l2)
|
|
if cos < -1 then
|
|
cos = -1
|
|
elseif cos > 1 then
|
|
cos = 1
|
|
if stretch then sx = sx * ((math_sqrt(dd) / (l1 + l2) - 1) * alpha + 1) end
|
|
end
|
|
a2 = math_acos(cos) * bendDir
|
|
a = l1 + l2 * cos
|
|
b = l2 * math_sin(a2)
|
|
a1 = math_atan2(ty * a - tx * b, tx * a + ty * b)
|
|
else
|
|
local skip = false
|
|
a = psx * l2
|
|
b = psy * l2
|
|
local aa = a * a
|
|
local bb = b * b
|
|
local ta = math_atan2(ty, tx);
|
|
c = bb * l1 * l1 + aa * dd - aa * bb
|
|
local c1 = -2 * bb * l1
|
|
local c2 = bb - aa
|
|
d = c1 * c1 - 4 * c2 * c
|
|
if d >= 0 then
|
|
local q = math_sqrt(d);
|
|
if (c1 < 0) then q = -q end
|
|
q = -(c1 + q) / 2
|
|
local r0 = q / c2
|
|
local r1 = c / q
|
|
local r = r1
|
|
if math_abs(r0) < math_abs(r1) then r = r0 end
|
|
if r * r <= dd then
|
|
y = math_sqrt(dd - r * r) * bendDir
|
|
a1 = ta - math_atan2(y, r)
|
|
a2 = math_atan2(y / psy, (r - l1) / psx)
|
|
skip = true
|
|
end
|
|
end
|
|
if not skip then
|
|
local minAngle = math_pi
|
|
local minX = l1 - a
|
|
local minDist = minX * minX
|
|
local minY = 0;
|
|
local maxAngle = 0
|
|
local maxX = l1 + a
|
|
local maxDist = maxX * maxX
|
|
local maxY = 0
|
|
c = -a * l1 / (aa - bb)
|
|
if (c >= -1 and c <= 1) then
|
|
c = math_acos(c)
|
|
x = a * math_cos(c) + l1
|
|
y = b * math_sin(c)
|
|
d = x * x + y * y
|
|
if d < minDist then
|
|
minAngle = c
|
|
minDist = d
|
|
minX = x
|
|
minY = y
|
|
end
|
|
if d > maxDist then
|
|
maxAngle = c
|
|
maxDist = d
|
|
maxX = x
|
|
maxY = y
|
|
end
|
|
end
|
|
if dd <= (minDist + maxDist) / 2 then
|
|
a1 = ta - math_atan2(minY * bendDir, minX)
|
|
a2 = minAngle * bendDir
|
|
else
|
|
a1 = ta - math_atan2(maxY * bendDir, maxX)
|
|
a2 = maxAngle * bendDir
|
|
end
|
|
end
|
|
end
|
|
local os = math_atan2(cy, cx) * s2
|
|
local rotation = parent.arotation
|
|
a1 = math_deg(a1 - os) + os1 - rotation
|
|
if a1 > 180 then
|
|
a1 = a1 - 360
|
|
elseif a1 < -180 then
|
|
a1 = a1 + 360
|
|
end
|
|
parent:updateWorldTransformWith(px, py, rotation + a1 * alpha, sx, parent.ascaleY, 0, 0)
|
|
rotation = child.rotation
|
|
a2 = (math_deg(a2 + os) - child.ashearX) * s2 + os2 - rotation
|
|
if a2 > 180 then
|
|
a2 = a2 - 360
|
|
elseif a2 < -180 then
|
|
a2 = a2 + 360
|
|
end
|
|
child:updateWorldTransformWith(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY);
|
|
end
|
|
|
|
return IkConstraint
|