------------------------------------------------------------------------------- -- 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, bendDirection = data.bendDirection, } 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) 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 bone:updateWorldTransformWith(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, bone.ascaleX, bone.ascaleY, bone.ashearX, bone.ashearY) end function IkConstraint:apply2 (parent, child, targetX, targetY, bendDir, 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 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 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 = 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, parent.ascaleX, 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