PathConstraint.lua 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. -------------------------------------------------------------------------------
  2. -- Spine Runtimes License Agreement
  3. -- Last updated May 1, 2019. Replaces all prior versions.
  4. --
  5. -- Copyright (c) 2013-2019, 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. -- THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS
  19. -- OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  20. -- OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
  21. -- NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT,
  22. -- INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  23. -- BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS
  24. -- INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY
  25. -- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  26. -- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
  27. -- EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. -------------------------------------------------------------------------------
  29. -- FIXME the logic in this file uses 0-based indexing. Each array
  30. -- access adds 1 to the calculated index. We should switch the logic
  31. -- to 1-based indexing eventually.
  32. local setmetatable = setmetatable
  33. local AttachmentType = require "spine-lua.attachments.AttachmentType"
  34. local PathConstraintData = require "spine-lua.PathConstraintData"
  35. local utils = require "spine-lua.utils"
  36. local math_pi = math.pi
  37. local math_pi2 = math.pi * 2
  38. local math_atan2 = math.atan2
  39. local math_sqrt = math.sqrt
  40. local math_acos = math.acos
  41. local math_sin = math.sin
  42. local math_cos = math.cos
  43. local table_insert = table.insert
  44. local math_deg = math.deg
  45. local math_rad = math.rad
  46. local math_abs = math.abs
  47. local math_max = math.max
  48. local PathConstraint = {}
  49. PathConstraint.__index = PathConstraint
  50. PathConstraint.NONE = -1
  51. PathConstraint.BEFORE = -2
  52. PathConstraint.AFTER = -3
  53. PathConstraint.epsilon = 0.00001
  54. function PathConstraint.new (data, skeleton)
  55. if not data then error("data cannot be nil", 2) end
  56. if not skeleton then error("skeleton cannot be nil", 2) end
  57. local self = {
  58. data = data,
  59. bones = {},
  60. target = skeleton:findSlot(data.target.name),
  61. position = data.position,
  62. spacing = data.spacing,
  63. rotateMix = data.rotateMix,
  64. translateMix = data.translateMix,
  65. spaces = {},
  66. positions = {},
  67. world = {},
  68. curves = {},
  69. lengths = {},
  70. segments = {}
  71. }
  72. setmetatable(self, PathConstraint)
  73. for _,boneData in ipairs(data.bones) do
  74. table_insert(self.bones, skeleton:findBone(boneData.name))
  75. end
  76. return self
  77. end
  78. function PathConstraint:apply ()
  79. self:update()
  80. end
  81. function PathConstraint:update ()
  82. local attachment = self.target.attachment
  83. if not attachment or not (attachment.type == AttachmentType.path) then return end
  84. local rotateMix = self.rotateMix
  85. local translateMix = self.translateMix
  86. local translate = translateMix > 0
  87. local rotate = rotateMix > 0
  88. if not translate and not rotate then return end
  89. local data = self.data;
  90. local percentSpacing = data.spacingMode == PathConstraintData.SpacingMode.percent
  91. local rotateMode = data.rotateMode
  92. local tangents = rotateMode == PathConstraintData.RotateMode.tangent
  93. local scale = rotateMode == PathConstraintData.RotateMode.chainscale
  94. local bones = self.bones
  95. local boneCount = #bones
  96. local spacesCount = boneCount + 1
  97. if tangents then spacesCount = boneCount end
  98. local spaces = utils.setArraySize(self.spaces, spacesCount)
  99. local lengths = nil
  100. local spacing = self.spacing
  101. if scale or not percentSpacing then
  102. if scale then lengths = utils.setArraySize(self.lengths, boneCount) end
  103. local lengthSpacing = data.spacingMode == PathConstraintData.SpacingMode.length
  104. local i = 0
  105. local n = spacesCount - 1
  106. while i < n do
  107. local bone = bones[i + 1];
  108. local setupLength = bone.data.length
  109. if setupLength < PathConstraint.epsilon then
  110. if scale then lengths[i + 1] = 0 end
  111. i = i + 1
  112. spaces[i + 1] = 0
  113. elseif percentSpacing then
  114. if scale then
  115. local x = setupLength * bone.a
  116. local y = setupLength * bone.c
  117. local length = math_sqrt(x * x + y * y)
  118. lengths[i + 1] = length
  119. end
  120. i = i + 1
  121. spaces[i + 1] = spacing
  122. else
  123. local x = setupLength * bone.a
  124. local y = setupLength * bone.c
  125. local length = math_sqrt(x * x + y * y)
  126. if scale then lengths[i + 1] = length end
  127. i = i + 1
  128. if lengthSpacing then
  129. spaces[i + 1] = (setupLength + spacing) * length / setupLength
  130. else
  131. spaces[i + 1] = spacing * length / setupLength
  132. end
  133. end
  134. end
  135. else
  136. local i = 1
  137. while i < spacesCount do
  138. spaces[i + 1] = spacing
  139. i = i + 1
  140. end
  141. end
  142. local positions = self:computeWorldPositions(attachment, spacesCount, tangents, data.positionMode == PathConstraintData.PositionMode.percent, percentSpacing)
  143. local boneX = positions[1]
  144. local boneY = positions[2]
  145. local offsetRotation = data.offsetRotation
  146. local tip = false;
  147. if offsetRotation == 0 then
  148. tip = rotateMode == PathConstraintData.RotateMode.chain
  149. else
  150. tip = false;
  151. local p = self.target.bone;
  152. if p.a * p.d - p.b * p.c > 0 then
  153. offsetRotation = offsetRotation * utils.degRad
  154. else
  155. offsetRotation = offsetRotation * -utils.degRad
  156. end
  157. end
  158. local i = 0
  159. local p = 3
  160. while i < boneCount do
  161. local bone = bones[i + 1]
  162. bone.worldX = bone.worldX + (boneX - bone.worldX) * translateMix
  163. bone.worldY = bone.worldY + (boneY - bone.worldY) * translateMix
  164. local x = positions[p + 1]
  165. local y = positions[p + 2]
  166. local dx = x - boneX
  167. local dy = y - boneY
  168. if scale then
  169. local length = lengths[i + 1]
  170. if length ~= 0 then
  171. local s = (math_sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1
  172. bone.a = bone.a * s
  173. bone.c = bone.c * s
  174. end
  175. end
  176. boneX = x
  177. boneY = y
  178. if rotate then
  179. local a = bone.a
  180. local b = bone.b
  181. local c = bone.c
  182. local d = bone.d
  183. local r = 0
  184. local cos = 0
  185. local sin = 0
  186. if tangents then
  187. r = positions[p - 1 + 1]
  188. elseif spaces[i + 1 + 1] == 0 then
  189. r = positions[p + 2 + 1]
  190. else
  191. r = math_atan2(dy, dx)
  192. end
  193. r = r - math_atan2(c, a)
  194. if tip then
  195. cos = math_cos(r)
  196. sin = math_sin(r)
  197. local length = bone.data.length
  198. boneX = boneX + (length * (cos * a - sin * c) - dx) * rotateMix;
  199. boneY = boneY + (length * (sin * a + cos * c) - dy) * rotateMix;
  200. else
  201. r = r + offsetRotation
  202. end
  203. if r > math_pi then
  204. r = r - math_pi2
  205. elseif r < -math_pi then
  206. r = r + math_pi2
  207. end
  208. r = r * rotateMix
  209. cos = math_cos(r)
  210. sin = math.sin(r)
  211. bone.a = cos * a - sin * c
  212. bone.b = cos * b - sin * d
  213. bone.c = sin * a + cos * c
  214. bone.d = sin * b + cos * d
  215. end
  216. bone.appliedValid = false
  217. i = i + 1
  218. p = p + 3
  219. end
  220. end
  221. function PathConstraint:computeWorldPositions (path, spacesCount, tangents, percentPosition, percentSpacing)
  222. local target = self.target
  223. local position = self.position
  224. local spaces = self.spaces
  225. local out = utils.setArraySize(self.positions, spacesCount * 3 + 2)
  226. local world = nil
  227. local closed = path.closed
  228. local verticesLength = path.worldVerticesLength
  229. local curveCount = verticesLength / 6
  230. local prevCurve = PathConstraint.NONE
  231. local i = 0
  232. if not path.constantSpeed then
  233. local lengths = path.lengths
  234. if closed then curveCount = curveCount - 1 else curveCount = curveCount - 2 end
  235. local pathLength = lengths[curveCount + 1];
  236. if percentPosition then position = position * pathLength end
  237. if percentSpacing then
  238. i = 1
  239. while i < spacesCount do
  240. spaces[i + 1] = spaces[i + 1] * pathLength
  241. i = i + 1
  242. end
  243. end
  244. world = utils.setArraySize(self.world, 8);
  245. i = 0
  246. local o = 0
  247. local curve = 0
  248. while i < spacesCount do
  249. local space = spaces[i + 1];
  250. position = position + space
  251. local p = position
  252. local skip = false
  253. if closed then
  254. p = p % pathLength
  255. if p < 0 then p = p + pathLength end
  256. curve = 0
  257. elseif p < 0 then
  258. if prevCurve ~= PathConstraint.BEFORE then
  259. prevCurve = PathConstraint.BEFORE
  260. path:computeWorldVertices(target, 2, 4, world, 0, 2)
  261. end
  262. self:addBeforePosition(p, world, 0, out, o)
  263. skip = true
  264. elseif p > pathLength then
  265. if prevCurve ~= PathConstraint.AFTER then
  266. prevCurve = PathConstraint.AFTER
  267. path:computeWorldVertices(target, verticesLength - 6, 4, world, 0, 2)
  268. end
  269. self:addAfterPosition(p - pathLength, world, 0, out, o)
  270. skip = true
  271. end
  272. if not skip then
  273. -- Determine curve containing position.
  274. while true do
  275. local length = lengths[curve + 1]
  276. if p <= length then
  277. if curve == 0 then
  278. p = p / length
  279. else
  280. local prev = lengths[curve - 1 + 1]
  281. p = (p - prev) / (length - prev)
  282. end
  283. break
  284. end
  285. curve = curve + 1
  286. end
  287. if curve ~= prevCurve then
  288. prevCurve = curve
  289. if closed and curve == curveCount then
  290. path:computeWorldVertices(target, verticesLength - 4, 4, world, 0, 2)
  291. path:computeWorldVertices(target, 0, 4, world, 4, 2)
  292. else
  293. path:computeWorldVertices(target, curve * 6 + 2, 8, world, 0, 2)
  294. end
  295. end
  296. self:addCurvePosition(p, world[1], world[2], world[3], world[4], world[5], world[6], world[7], world[8], out, o, tangents or (i > 0 and space == 0))
  297. end
  298. i = i + 1
  299. o = o + 3
  300. end
  301. return out
  302. end
  303. -- World vertices.
  304. if closed then
  305. verticesLength = verticesLength + 2
  306. world = utils.setArraySize(self.world, verticesLength)
  307. path:computeWorldVertices(target, 2, verticesLength - 4, world, 0, 2)
  308. path:computeWorldVertices(target, 0, 2, world, verticesLength - 4, 2)
  309. world[verticesLength - 2 + 1] = world[0 + 1]
  310. world[verticesLength - 1 + 1] = world[1 + 1]
  311. else
  312. curveCount = curveCount - 1
  313. verticesLength = verticesLength - 4;
  314. world = utils.setArraySize(self.world, verticesLength)
  315. path:computeWorldVertices(target, 2, verticesLength, world, 0, 2)
  316. end
  317. -- Curve lengths.
  318. local curves = utils.setArraySize(self.curves, curveCount)
  319. local pathLength = 0;
  320. local x1 = world[0 + 1]
  321. local y1 = world[1 + 1]
  322. local cx1 = 0
  323. local cy1 = 0
  324. local cx2 = 0
  325. local cy2 = 0
  326. local x2 = 0
  327. local y2 = 0
  328. local tmpx = 0
  329. local tmpy = 0
  330. local dddfx = 0
  331. local dddfy = 0
  332. local ddfx = 0
  333. local ddfy = 0
  334. local dfx = 0
  335. local dfy = 0
  336. local w = 2
  337. while i < curveCount do
  338. cx1 = world[w + 1]
  339. cy1 = world[w + 2]
  340. cx2 = world[w + 3]
  341. cy2 = world[w + 4]
  342. x2 = world[w + 5]
  343. y2 = world[w + 6]
  344. tmpx = (x1 - cx1 * 2 + cx2) * 0.1875
  345. tmpy = (y1 - cy1 * 2 + cy2) * 0.1875
  346. dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375
  347. dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375
  348. ddfx = tmpx * 2 + dddfx
  349. ddfy = tmpy * 2 + dddfy
  350. dfx = (cx1 - x1) * 0.75 + tmpx + dddfx * 0.16666667
  351. dfy = (cy1 - y1) * 0.75 + tmpy + dddfy * 0.16666667
  352. pathLength = pathLength + math_sqrt(dfx * dfx + dfy * dfy)
  353. dfx = dfx + ddfx
  354. dfy = dfy + ddfy
  355. ddfx = ddfx + dddfx
  356. ddfy = ddfy + dddfy
  357. pathLength = pathLength + math_sqrt(dfx * dfx + dfy * dfy)
  358. dfx = dfx + ddfx
  359. dfy = dfy + ddfy
  360. pathLength = pathLength + math_sqrt(dfx * dfx + dfy * dfy)
  361. dfx = dfx + ddfx + dddfx
  362. dfy = dfy + ddfy + dddfy
  363. pathLength = pathLength + math_sqrt(dfx * dfx + dfy * dfy)
  364. curves[i + 1] = pathLength
  365. x1 = x2
  366. y1 = y2
  367. i = i + 1
  368. w = w + 6
  369. end
  370. if percentPosition then
  371. position = position * pathLength
  372. else
  373. position = position * pathLength / path.lengths[curveCount];
  374. end
  375. if percentSpacing then
  376. i = 1
  377. while i < spacesCount do
  378. spaces[i + 1] = spaces[i + 1] * pathLength
  379. i = i + 1
  380. end
  381. end
  382. local segments = self.segments
  383. local curveLength = 0
  384. i = 0
  385. local o = 0
  386. local curve = 0
  387. local segment = 0
  388. while i < spacesCount do
  389. local space = spaces[i + 1]
  390. position = position + space
  391. local p = position
  392. local skip = false
  393. if closed then
  394. p = p % pathLength
  395. if p < 0 then p = p + pathLength end
  396. curve = 0
  397. elseif p < 0 then
  398. self:addBeforePosition(p, world, 0, out, o)
  399. skip = true
  400. elseif p > pathLength then
  401. self:addAfterPosition(p - pathLength, world, verticesLength - 4, out, o)
  402. skip = true
  403. end
  404. if not skip then
  405. -- Determine curve containing position.
  406. while true do
  407. local length = curves[curve + 1]
  408. if p <= length then
  409. if curve == 0 then
  410. p = p / length
  411. else
  412. local prev = curves[curve - 1 + 1]
  413. p = (p - prev) / (length - prev)
  414. end
  415. break
  416. end
  417. curve = curve + 1
  418. end
  419. -- Curve segment lengths.
  420. if curve ~= prevCurve then
  421. prevCurve = curve
  422. local ii = curve * 6
  423. x1 = world[ii + 1]
  424. y1 = world[ii + 2]
  425. cx1 = world[ii + 3]
  426. cy1 = world[ii + 4]
  427. cx2 = world[ii + 5]
  428. cy2 = world[ii + 6]
  429. x2 = world[ii + 7]
  430. y2 = world[ii + 8]
  431. tmpx = (x1 - cx1 * 2 + cx2) * 0.03
  432. tmpy = (y1 - cy1 * 2 + cy2) * 0.03
  433. dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006
  434. dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.006
  435. ddfx = tmpx * 2 + dddfx
  436. ddfy = tmpy * 2 + dddfy
  437. dfx = (cx1 - x1) * 0.3 + tmpx + dddfx * 0.16666667
  438. dfy = (cy1 - y1) * 0.3 + tmpy + dddfy * 0.16666667
  439. curveLength = math_sqrt(dfx * dfx + dfy * dfy)
  440. segments[1] = curveLength
  441. ii = 1
  442. while ii < 8 do
  443. dfx = dfx + ddfx
  444. dfy = dfy + ddfy
  445. ddfx = ddfx + dddfx
  446. ddfy = ddfy + dddfy
  447. curveLength = curveLength + math_sqrt(dfx * dfx + dfy * dfy)
  448. segments[ii + 1] = curveLength
  449. ii = ii + 1
  450. end
  451. dfx = dfx + ddfx
  452. dfy = dfy + ddfy
  453. curveLength = curveLength + math_sqrt(dfx * dfx + dfy * dfy)
  454. segments[9] = curveLength
  455. dfx = dfx + ddfx + dddfx
  456. dfy = dfy + ddfy + dddfy
  457. curveLength = curveLength + math_sqrt(dfx * dfx + dfy * dfy)
  458. segments[10] = curveLength
  459. segment = 0
  460. end
  461. -- Weight by segment length.
  462. p = p * curveLength
  463. while true do
  464. local length = segments[segment + 1]
  465. if p <= length then
  466. if segment == 0 then
  467. p = p / length
  468. else
  469. local prev = segments[segment - 1 + 1]
  470. p = segment + (p - prev) / (length - prev)
  471. end
  472. break;
  473. end
  474. segment = segment + 1
  475. end
  476. self:addCurvePosition(p * 0.1, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents or (i > 0 and space == 0))
  477. end
  478. i = i + 1
  479. o = o + 3
  480. end
  481. return out
  482. end
  483. function PathConstraint:addBeforePosition (p, temp, i, out, o)
  484. local x1 = temp[i + 1]
  485. local y1 = temp[i + 2]
  486. local dx = temp[i + 3] - x1
  487. local dy = temp[i + 4] - y1
  488. local r = math_atan2(dy, dx)
  489. out[o + 1] = x1 + p * math_cos(r)
  490. out[o + 2] = y1 + p * math_sin(r)
  491. out[o + 3] = r
  492. end
  493. function PathConstraint:addAfterPosition(p, temp, i, out, o)
  494. local x1 = temp[i + 3]
  495. local y1 = temp[i + 4]
  496. local dx = x1 - temp[i + 1]
  497. local dy = y1 - temp[i + 2]
  498. local r = math_atan2(dy, dx)
  499. out[o + 1] = x1 + p * math_cos(r)
  500. out[o + 2] = y1 + p * math_sin(r)
  501. out[o + 3] = r
  502. end
  503. function PathConstraint:addCurvePosition(p, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents)
  504. if p == 0 or (p ~= p) then
  505. out[o + 1] = x1
  506. out[o + 2] = y1
  507. out[o + 3] = math_atan2(cy1 - y1, cx1 - x1)
  508. return;
  509. end
  510. local tt = p * p
  511. local ttt = tt * p
  512. local u = 1 - p
  513. local uu = u * u
  514. local uuu = uu * u
  515. local ut = u * p
  516. local ut3 = ut * 3
  517. local uut3 = u * ut3
  518. local utt3 = ut3 * p
  519. local x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt
  520. local y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt
  521. out[o + 1] = x
  522. out[o + 2] = y
  523. if tangents then
  524. if p < 0.001 then
  525. out[o + 3] = math_atan2(cy1 - y1, cx1 - x1)
  526. else
  527. out[o + 3] = math_atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt))
  528. end
  529. end
  530. end
  531. return PathConstraint