TransformConstraint.lua 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. -------------------------------------------------------------------------------
  2. -- Spine Runtimes License Agreement
  3. -- Last updated January 1, 2020. Replaces all prior versions.
  4. --
  5. -- Copyright (c) 2013-2020, Esoteric Software LLC
  6. --
  7. -- Integration of the Spine Runtimes into software or otherwise creating
  8. -- derivative works of the Spine Runtimes is permitted under the terms and
  9. -- conditions of Section 2 of the Spine Editor License Agreement:
  10. -- http://esotericsoftware.com/spine-editor-license
  11. --
  12. -- Otherwise, it is permitted to integrate the Spine Runtimes into software
  13. -- or otherwise create derivative works of the Spine Runtimes (collectively,
  14. -- "Products"), provided that each user of the Products must obtain their own
  15. -- Spine Editor license and redistribution of the Products in any form must
  16. -- include this license and copyright notice.
  17. --
  18. -- THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
  19. -- EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  20. -- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  21. -- DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
  22. -- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  23. -- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
  24. -- BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
  25. -- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  26. -- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  27. -- THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. -------------------------------------------------------------------------------
  29. local setmetatable = setmetatable
  30. local utils = require "spine-lua.utils"
  31. local math_pi = math.pi
  32. local math_pi2 = math.pi * 2
  33. local math_atan2 = math.atan2
  34. local math_sqrt = math.sqrt
  35. local math_acos = math.acos
  36. local math_sin = math.sin
  37. local math_cos = math.cos
  38. local table_insert = table.insert
  39. local math_deg = math.deg
  40. local math_rad = math.rad
  41. local math_abs = math.abs
  42. local math_floor = math.floor
  43. local TransformConstraint = {}
  44. TransformConstraint.__index = TransformConstraint
  45. function TransformConstraint.new (data, skeleton)
  46. if not data then error("data cannot be nil", 2) end
  47. if not skeleton then error("skeleton cannot be nil", 2) end
  48. local self = {
  49. data = data,
  50. bones = {},
  51. target = nil,
  52. rotateMix = data.rotateMix, translateMix = data.translateMix, scaleMix = data.scaleMix, shearMix = data.shearMix,
  53. temp = { 0, 0 },
  54. active = false
  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