spine-runtimes/spine-lua/IkConstraint.lua
Mario Zechner 5b1814cff3 spine-lua, spine-love, spine-corona update to 3.4.02 (#722)
The spine-lua API has been updated to be compatible with Spine version 3.4.02 (latest stable). The spine-lua API now supports path constraints, transform constraints, uses the new way we encode meshes etc. There are no API breaking changes, only API additions, such as PathConstraints and TransformConstraints as well as additional methods to Skeleton and similar classes. The internals of the spine-lua API have also been updated to follow Lua best performance practices by localizing heavily and using meta tables for "class methods". The spine-lua API now also loads texture atlases as exported by Spine. All that is required for a consumer is to supply an image loading function for their specific engine/framework. We provide implementations for spine-love and spine-corona.

The spine-love API can now render all Spine attachment types, including meshes and linked meshes. The API has changed. Where previously a "class" Skeleton existed with a draw function, the new spine-love API introduces a new SkeletonRenderer. See the example on API usage.

The spine-corona API can now also render all Spine attachment types. The API has not changed.
2016-10-11 16:33:25 +02:00

276 lines
7.5 KiB
Lua

-------------------------------------------------------------------------------
-- Spine Runtimes Software License
-- Version 2.3
--
-- Copyright (c) 2013-2015, 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 (the "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 otherwise create derivative works, improvements of the
-- Software or develop new applications using the Software 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; LOSS OF USE, DATA, OR PROFITS;
-- OR BUSINESS INTERRUPTION) 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,
bendDirection = data.bendDirection,
level = 0
}
setmetatable(self, IkConstraint)
local self_bones = self.bones
for i,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.mix)
elseif boneCount == 2 then
self:apply2(bones[1], bones[2], target.worldX, target.worldY, self.bendDirection, self.mix)
end
end
function IkConstraint:apply1 (bone, targetX, targetY, alpha)
local pp = bone.parent
local id = 1 / (pp.a * pp.d - pp.b * pp.c)
local x = targetX - pp.worldX
local y = targetY - pp.worldY
local tx = (x * pp.d - y * pp.b) * id - bone.x
local ty = (y * pp.a - x * pp.c) * id - bone.y
local rotationIK = math_deg(math_atan2(ty, tx)) - bone.shearX - bone.rotation
if bone.scaleX < 0 then rotationIK = rotationIK + 180 end
if rotationIK > 180 then
rotationIK = rotationIK - 360
elseif (rotationIK < -180) then
rotationIK = rotationIK + 360
end
bone:updateWorldTransformWith(bone.x, bone.y, bone.rotation + rotationIK * alpha, bone.scaleX, bone.scaleY, bone.shearX, bone.shearY)
end
function IkConstraint:apply2 (parent, child, targetX, targetY, bendDir, alpha)
if alpha == 0 then
child:updateWorldTransform()
return
end
local px = parent.x
local py = parent.y
local psx = parent.scaleX
local psy = parent.scaleY
local csx = child.scaleX
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.x
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.y
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
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 = (tx * tx + ty * ty - l1 * l1 - l2 * l2) / (2 * l1 * l2)
if cos < -1 then
cos = -1
elseif cos > 1 then
cos = 1
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 dd = tx * tx + ty * ty
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 = 0
local minDist = 9999999999
local minX = 0
local minY = 0
local maxAngle = 0
local maxDist = 0
local maxX = 0
local maxY = 0
x = l1 + a
d = x * x
if d > maxDist then
maxAngle = 0
maxDist = d
maxX = x
end
x = l1 - a
d = x * x
if d < minDist then
minAngle = math_pi
minDist = d
minX = x
end
local angle = math_acos(-a * l1 / (aa - bb))
x = a * math_cos(angle) + l1
y = b * math_sin(angle)
d = x * x + y * y
if d < minDist then
minAngle = angle
minDist = d
minX = x
minY = y
end
if d > maxDist then
maxAngle = angle
maxDist = d
maxX = x
maxY = y
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.rotation
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, parent.scaleX, parent.scaleY, 0, 0)
rotation = child.rotation
a2 = (math_deg(a2 + os) - child.shearX) * 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.scaleX, child.scaleY, child.shearX, child.shearY);
end
return IkConstraint