123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 |
- -------------------------------------------------------------------------------
- -- 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
|