PathConstraint.lua 17 KB

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