TransformConstraint.lua 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. -------------------------------------------------------------------------------
  2. -- Spine Runtimes Software License v2.5
  3. --
  4. -- Copyright (c) 2013-2016, Esoteric Software
  5. -- All rights reserved.
  6. --
  7. -- You are granted a perpetual, non-exclusive, non-sublicensable, and
  8. -- non-transferable license to use, install, execute, and perform the Spine
  9. -- Runtimes software and derivative works solely for personal or internal
  10. -- use. Without the written permission of Esoteric Software (see Section 2 of
  11. -- the Spine Software License Agreement), you may not (a) modify, translate,
  12. -- adapt, or develop new applications using the Spine Runtimes or otherwise
  13. -- create derivative works or improvements of the Spine Runtimes or (b) remove,
  14. -- delete, alter, or obscure any trademarks or any copyright, trademark, patent,
  15. -- or other intellectual property or proprietary rights notices on or in the
  16. -- Software, including any copy thereof. Redistributions in binary or source
  17. -- form must include this license and terms.
  18. --
  19. -- THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
  20. -- IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  21. -- MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
  22. -- EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  23. -- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  24. -- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF
  25. -- USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
  26. -- IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  27. -- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  28. -- POSSIBILITY OF SUCH DAMAGE.
  29. -------------------------------------------------------------------------------
  30. local setmetatable = setmetatable
  31. local utils = require "spine-lua.utils"
  32. local math_pi = math.pi
  33. local math_pi2 = math.pi * 2
  34. local math_atan2 = math.atan2
  35. local math_sqrt = math.sqrt
  36. local math_acos = math.acos
  37. local math_sin = math.sin
  38. local math_cos = math.cos
  39. local table_insert = table.insert
  40. local math_deg = math.deg
  41. local math_rad = math.rad
  42. local math_abs = math.abs
  43. local math_floor = math.floor
  44. local TransformConstraint = {}
  45. TransformConstraint.__index = TransformConstraint
  46. function TransformConstraint.new (data, skeleton)
  47. if not data then error("data cannot be nil", 2) end
  48. if not skeleton then error("skeleton cannot be nil", 2) end
  49. local self = {
  50. data = data,
  51. bones = {},
  52. target = nil,
  53. rotateMix = data.rotateMix, translateMix = data.translateMix, scaleMix = data.scaleMix, shearMix = data.shearMix,
  54. temp = { 0, 0 }
  55. }
  56. setmetatable(self, TransformConstraint)
  57. for _,bone in ipairs(data.bones) do
  58. table_insert(self.bones, skeleton:findBone(bone.name))
  59. end
  60. self.target = skeleton:findBone(data.target.name)
  61. return self
  62. end
  63. function TransformConstraint:apply ()
  64. self:update()
  65. end
  66. function TransformConstraint:update ()
  67. if self.data.local_ then
  68. if self.data.relative then
  69. self:applyRelativeLocal()
  70. else
  71. self:applyAbsoluteLocal()
  72. end
  73. else
  74. if self.data.relative then
  75. self:applyRelativeWorld()
  76. else
  77. self:applyAbsoluteWorld()
  78. end
  79. end
  80. end
  81. function TransformConstraint:applyAbsoluteWorld ()
  82. local rotateMix = self.rotateMix
  83. local translateMix = self.translateMix
  84. local scaleMix = self.scaleMix
  85. local shearMix = self.shearMix
  86. local target = self.target
  87. local ta = target.a
  88. local tb = target.b
  89. local tc = target.c
  90. local td = target.d
  91. local degRadReflect = 0;
  92. if ta * td - tb * tc > 0 then degRadReflect = utils.degRad else degRadReflect = -utils.degRad end
  93. local offsetRotation = self.data.offsetRotation * degRadReflect
  94. local offsetShearY = self.data.offsetShearY * degRadReflect
  95. local bones = self.bones
  96. for _, bone in ipairs(bones) do
  97. local modified = false
  98. if rotateMix ~= 0 then
  99. local a = bone.a
  100. local b = bone.b
  101. local c = bone.c
  102. local d = bone.d
  103. local r = math_atan2(tc, ta) - math_atan2(c, a) + offsetRotation
  104. if r > math_pi then
  105. r = r - math_pi2
  106. elseif r < -math_pi then
  107. r = r + math_pi2
  108. end
  109. r = r * rotateMix
  110. local cos = math_cos(r)
  111. local sin = math_sin(r)
  112. bone.a = cos * a - sin * c
  113. bone.b = cos * b - sin * d
  114. bone.c = sin * a + cos * c
  115. bone.d = sin * b + cos * d
  116. modified = true
  117. end
  118. if translateMix ~= 0 then
  119. local temp = self.temp
  120. temp[1] = self.data.offsetX
  121. temp[2] = self.data.offsetY
  122. target:localToWorld(temp)
  123. bone.worldX = bone.worldX + (temp[1] - bone.worldX) * translateMix
  124. bone.worldY = bone.worldY + (temp[2] - bone.worldY) * translateMix
  125. modified = true
  126. end
  127. if scaleMix > 0 then
  128. local s = math_sqrt(bone.a * bone.a + bone.c * bone.c)
  129. local ts = math_sqrt(ta * ta + tc * tc)
  130. if s > 0.00001 then
  131. s = (s + (ts - s + self.data.offsetScaleX) * scaleMix) / s
  132. end
  133. bone.a = bone.a * s
  134. bone.c = bone.c * s
  135. s = math_sqrt(bone.b * bone.b + bone.d * bone.d)
  136. ts = math_sqrt(tb * tb + td * td)
  137. if s > 0.00001 then
  138. s = (s + (ts - s + self.data.offsetScaleY) * scaleMix) / s
  139. end
  140. bone.b = bone.b * s
  141. bone.d = bone.d * s
  142. modified = true
  143. end
  144. if shearMix > 0 then
  145. local b = bone.b
  146. local d = bone.d
  147. local by = math_atan2(d, b)
  148. local r = math_atan2(td, tb) - math_atan2(tc, ta) - (by - math_atan2(bone.c, bone.a))
  149. if r > math_pi then
  150. r = r - math_pi2
  151. elseif r < -math_pi then
  152. r = r + math_pi2
  153. end
  154. r = by + (r + offsetShearY) * shearMix
  155. local s = math_sqrt(b * b + d * d)
  156. bone.b = math_cos(r) * s
  157. bone.d = math_sin(r) * s
  158. modified = true
  159. end
  160. if modified then bone.appliedValid = false end
  161. end
  162. end
  163. function TransformConstraint:applyRelativeWorld ()
  164. local rotateMix = self.rotateMix
  165. local translateMix = self.translateMix
  166. local scaleMix = self.scaleMix
  167. local shearMix = self.shearMix
  168. local target = self.target
  169. local ta = target.a
  170. local tb = target.b
  171. local tc = target.c
  172. local td = target.d
  173. local degRadReflect = 0;
  174. if ta * td - tb * tc > 0 then degRadReflect = utils.degRad else degRadReflect = -utils.degRad end
  175. local offsetRotation = self.data.offsetRotation * degRadReflect
  176. local offsetShearY = self.data.offsetShearY * degRadReflect
  177. local bones = self.bones
  178. for _, bone in ipairs(bones) do
  179. local modified = false
  180. if rotateMix ~= 0 then
  181. local a = bone.a
  182. local b = bone.b
  183. local c = bone.c
  184. local d = bone.d
  185. local r = math_atan2(tc, ta) + offsetRotation
  186. if r > math_pi then
  187. r = r - math_pi2
  188. elseif r < -math_pi then
  189. r = r + math_pi2
  190. end
  191. r = r * rotateMix
  192. local cos = math_cos(r)
  193. local sin = math_sin(r)
  194. bone.a = cos * a - sin * c
  195. bone.b = cos * b - sin * d
  196. bone.c = sin * a + cos * c
  197. bone.d = sin * b + cos * d
  198. modified = true
  199. end
  200. if translateMix ~= 0 then
  201. local temp = self.temp
  202. temp[1] = self.data.offsetX
  203. temp[2] = self.data.offsetY
  204. target:localToWorld(temp)
  205. bone.worldX = bone.worldX + temp[1] * translateMix
  206. bone.worldY = bone.worldY + temp[2] * translateMix
  207. modified = true
  208. end
  209. if scaleMix > 0 then
  210. local s = (math_sqrt(ta * ta + tc * tc) - 1 + self.data.offsetScaleX) * scaleMix + 1
  211. bone.a = bone.a * s
  212. bone.c = bone.c * s
  213. s = (math_sqrt(tb * tb + td * td) - 1 + self.data.offsetScaleY) * scaleMix + 1
  214. bone.b = bone.b * s
  215. bone.d = bone.d * s
  216. modified = true
  217. end
  218. if shearMix > 0 then
  219. local r = math_atan2(td, tb) - math_atan2(tc, ta)
  220. if r > math_pi then
  221. r = r - math_pi2
  222. elseif r < -math_pi then
  223. r = r + math_pi2
  224. end
  225. local b = bone.b
  226. local d = bone.d
  227. r = math_atan2(d, b) + (r - math_pi / 2 + offsetShearY) * shearMix;
  228. local s = math_sqrt(b * b + d * d)
  229. bone.b = math_cos(r) * s
  230. bone.d = math_sin(r) * s
  231. modified = true
  232. end
  233. if modified then bone.appliedValid = false end
  234. end
  235. end
  236. function TransformConstraint:applyAbsoluteLocal ()
  237. local rotateMix = self.rotateMix
  238. local translateMix = self.translateMix
  239. local scaleMix = self.scaleMix
  240. local shearMix = self.shearMix
  241. local target = self.target
  242. if not target.appliedValid then target:updatedAppliedTransform() end
  243. local bones = self.bones
  244. for _, bone in ipairs(bones) do
  245. local modified = false
  246. if not bone.appliedValid then bone:updateAppliedTransform() end
  247. local rotation = bone.arotation
  248. if rotateMix ~= 0 then
  249. local r = target.arotation - rotation + self.data.offsetRotation
  250. r = r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360
  251. rotation = rotation + r * rotateMix
  252. end
  253. local x = bone.ax
  254. local y = bone.ay
  255. if translateMix ~= 0 then
  256. x = x + (target.ax - x + self.data.offsetX) * translateMix
  257. y = x + (target.ay - y + self.data.offsetY) * translateMix
  258. end
  259. local scaleX = bone.ascaleX
  260. local scaleY = bone.ascaleY
  261. if scaleMix ~= 0 then
  262. if scaleX > 0.00001 then
  263. scaleX = (scaleX + (target.ascaleX - scaleX + self.data.offsetScaleX) * scaleMix) / scaleX
  264. end
  265. if scaleY > 0.00001 then
  266. scaleY = (scaleY + (target.ascaleY - scaleY + self.data.offsetScaleY) * scaleMix) / scaleY
  267. end
  268. end
  269. local shearY = bone.ashearY
  270. if shearMix ~= 0 then
  271. local r = target.ashearY - shearY + self.data.offsetShearY
  272. r = r - (16384 - math_floor(16384.499999999996 - r / 360)) * 360
  273. bone.shearY = bone.shearY + r * shearMix
  274. end
  275. bone:updateWorldTransformWith(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY);
  276. end
  277. end
  278. function TransformConstraint:applyRelativeLocal ()
  279. local rotateMix = self.rotateMix
  280. local translateMix = self.translateMix
  281. local scaleMix = self.scaleMix
  282. local shearMix = self.shearMix
  283. local target = self.target
  284. if not target.appliedValid then target:updateAppliedTransform() end
  285. local bones = self.bones
  286. for _, bone in ipairs(bones) do
  287. if not bone.appliedValid then bone:updateAppliedTransform() end
  288. local rotation = bone.arotation
  289. if rotateMix ~= 0 then rotation = rotation + (target.arotation + self.data.offsetRotation) * rotateMix end
  290. local x = bone.ax
  291. local y = bone.ay
  292. if translateMix ~= 0 then
  293. x = x + (target.ax + self.data.offsetX) * translateMix
  294. y = y + (target.ay + self.data.offsetY) * translateMix
  295. end
  296. local scaleX = bone.ascaleX
  297. local scaleY = bone.ascaleY
  298. if scaleMix ~= 0 then
  299. if scaleX > 0.00001 then
  300. scaleX = scaleX * (((target.ascaleX - 1 + self.data.offsetScaleX) * scaleMix) + 1)
  301. end
  302. if scaleY > 0.00001 then
  303. scaleY = scaleY * (((target.ascaleY - 1 + self.data.offsetScaleY) * scaleMix) + 1)
  304. end
  305. end
  306. local shearY = bone.ashearY
  307. if shearMix ~= 0 then shearY = shearY + (target.ashearY + self.data.offsetShearY) * shearMix end
  308. bone:updateWorldTransformWith(x, y, rotation, scaleX, scaleY, bone.ashearX, shearY)
  309. end
  310. end
  311. return TransformConstraint