PathConstraint.lua 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  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. 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 i,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.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 spacingMode = data.spacingMode
  91. local lengthSpacing = spacingMode == PathConstraintData.SpacingMode.length
  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 lengthSpacing then
  103. if scale then lengths = utils.setArraySize(self.lengths, boneCount) end
  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. local x = setupLength * bone.a
  110. local y = setupLength * bone.c
  111. local length = math_sqrt(x * x + y * y)
  112. if scale then lengths[i + 1] = length end
  113. i = i + 1
  114. if lengthSpacing then spaces[i + 1] = (setupLength + spacing) * length / setupLength else spaces[i + 1] = spacing * length / setupLength end
  115. end
  116. else
  117. local i = 1
  118. while i < spacesCount do
  119. spaces[i + 1] = spacing
  120. i = i + 1
  121. end
  122. end
  123. local positions = self:computeWorldPositions(attachment, spacesCount, tangents, data.positionMode == PathConstraintData.PositionMode.percent, spacingMode == PathConstraintData.SpacingMode.percent)
  124. local boneX = positions[1]
  125. local boneY = positions[2]
  126. local offsetRotation = data.offsetRotation
  127. local tip = false;
  128. if offsetRotation == 0 then
  129. tip = rotateMode == PathConstraintData.RotateMode.chain
  130. else
  131. tip = false;
  132. local p = self.target.bone;
  133. if p.a * p.d - p.b * p.c > 0 then
  134. offsetRotation = offsetRotation * utils.degRad
  135. else
  136. offsetRotation = offsetRotation * -utils.degRad
  137. end
  138. end
  139. local i = 0
  140. local p = 3
  141. while i < boneCount do
  142. local bone = bones[i + 1]
  143. bone.worldX = bone.worldX + (boneX - bone.worldX) * translateMix
  144. bone.worldY = bone.worldY + (boneY - bone.worldY) * translateMix
  145. local x = positions[p + 1]
  146. local y = positions[p + 2]
  147. local dx = x - boneX
  148. local dy = y - boneY
  149. if scale then
  150. local length = lengths[i + 1]
  151. if length ~= 0 then
  152. local s = (math_sqrt(dx * dx + dy * dy) / length - 1) * rotateMix + 1
  153. bone.a = bone.a * s
  154. bone.c = bone.c * s
  155. end
  156. end
  157. boneX = x
  158. boneY = y
  159. if rotate then
  160. local a = bone.a
  161. local b = bone.b
  162. local c = bone.c
  163. local d = bone.d
  164. local r = 0
  165. local cos = 0
  166. local sin = 0
  167. if tangents then
  168. r = positions[p - 1 + 1]
  169. elseif spaces[i + 1 + 1] == 0 then
  170. r = positions[p + 2 + 1]
  171. else
  172. r = math_atan2(dy, dx)
  173. end
  174. r = r - math_atan2(c, a)
  175. if tip then
  176. cos = math_cos(r)
  177. sin = math_sin(r)
  178. local length = bone.data.length
  179. boneX = boneX + (length * (cos * a - sin * c) - dx) * rotateMix;
  180. boneY = boneY + (length * (sin * a + cos * c) - dy) * rotateMix;
  181. else
  182. r = r + offsetRotation
  183. end
  184. if r > math_pi then
  185. r = r - math_pi2
  186. elseif r < -math_pi then
  187. r = r + math_pi2
  188. end
  189. r = r * rotateMix
  190. cos = math_cos(r)
  191. sin = math.sin(r)
  192. bone.a = cos * a - sin * c
  193. bone.b = cos * b - sin * d
  194. bone.c = sin * a + cos * c
  195. bone.d = sin * b + cos * d
  196. end
  197. bone.appliedValid = false
  198. i = i + 1
  199. p = p + 3
  200. end
  201. end
  202. function PathConstraint:computeWorldPositions (path, spacesCount, tangents, percentPosition, percentSpacing)
  203. local target = self.target
  204. local position = self.position
  205. local spaces = self.spaces
  206. local out = utils.setArraySize(self.positions, spacesCount * 3 + 2)
  207. local world = nil
  208. local closed = path.closed
  209. local verticesLength = path.worldVerticesLength
  210. local curveCount = verticesLength / 6
  211. local prevCurve = PathConstraint.NONE
  212. if not path.constantSpeed then
  213. local lengths = path.lengths
  214. if closed then curveCount = curveCount - 1 else curveCount = curveCount - 2 end
  215. local pathLength = lengths[curveCount + 1];
  216. if percentPosition then position = position * pathLength end
  217. if percentSpacing then
  218. local i = 0
  219. while i < spacesCount do
  220. spaces[i + 1] = spaces[i + 1] * pathLength
  221. i = i + 1
  222. end
  223. end
  224. world = utils.setArraySize(self.world, 8);
  225. local i = 0
  226. local o = 0
  227. local curve = 0
  228. while i < spacesCount do
  229. local space = spaces[i + 1];
  230. position = position + space
  231. local p = position
  232. local skip = false
  233. if closed then
  234. p = p % pathLength
  235. if p < 0 then p = p + pathLength end
  236. curve = 0
  237. elseif p < 0 then
  238. if prevCurve ~= PathConstraint.BEFORE then
  239. prevCurve = PathConstraint.BEFORE
  240. path:computeWorldVertices(target, 2, 4, world, 0, 2)
  241. end
  242. self:addBeforePosition(p, world, 0, out, o)
  243. skip = true
  244. elseif p > pathLength then
  245. if prevCurve ~= PathConstraint.AFTER then
  246. prevCurve = PathConstraint.AFTER
  247. path:computeWorldVertices(target, verticesLength - 6, 4, world, 0, 2)
  248. end
  249. self:addAfterPosition(p - pathLength, world, 0, out, o)
  250. skip = true
  251. end
  252. if not skip then
  253. -- Determine curve containing position.
  254. while true do
  255. local length = lengths[curve + 1]
  256. if p <= length then
  257. if curve == 0 then
  258. p = p / length
  259. else
  260. local prev = lengths[curve - 1 + 1]
  261. p = (p - prev) / (length - prev)
  262. end
  263. break
  264. end
  265. curve = curve + 1
  266. end
  267. if curve ~= prevCurve then
  268. prevCurve = curve
  269. if closed and curve == curveCount then
  270. path:computeWorldVertices(target, verticesLength - 4, 4, world, 0, 2)
  271. path:computeWorldVertices(target, 0, 4, world, 4, 2)
  272. else
  273. path:computeWorldVertices(target, curve * 6 + 2, 8, world, 0, 2)
  274. end
  275. end
  276. 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))
  277. end
  278. i = i + 1
  279. o = o + 3
  280. end
  281. return out
  282. end
  283. -- World vertices.
  284. if closed then
  285. verticesLength = verticesLength + 2
  286. world = utils.setArraySize(self.world, verticesLength)
  287. path:computeWorldVertices(target, 2, verticesLength - 4, world, 0, 2)
  288. path:computeWorldVertices(target, 0, 2, world, verticesLength - 4, 2)
  289. world[verticesLength - 2 + 1] = world[0 + 1]
  290. world[verticesLength - 1 + 1] = world[1 + 1]
  291. else
  292. curveCount = curveCount - 1
  293. verticesLength = verticesLength - 4;
  294. world = utils.setArraySize(self.world, verticesLength)
  295. path:computeWorldVertices(target, 2, verticesLength, world, 0, 2)
  296. end
  297. -- Curve lengths.
  298. local curves = utils.setArraySize(self.curves, curveCount)
  299. local pathLength = 0;
  300. local x1 = world[0 + 1]
  301. local y1 = world[1 + 1]
  302. local cx1 = 0
  303. local cy1 = 0
  304. local cx2 = 0
  305. local cy2 = 0
  306. local x2 = 0
  307. local y2 = 0
  308. local tmpx = 0
  309. local tmpy = 0
  310. local dddfx = 0
  311. local dddfy = 0
  312. local ddfx = 0
  313. local ddfy = 0
  314. local dfx = 0
  315. local dfy = 0
  316. i = 0
  317. local w = 2
  318. while i < curveCount do
  319. cx1 = world[w + 1]
  320. cy1 = world[w + 2]
  321. cx2 = world[w + 3]
  322. cy2 = world[w + 4]
  323. x2 = world[w + 5]
  324. y2 = world[w + 6]
  325. tmpx = (x1 - cx1 * 2 + cx2) * 0.1875
  326. tmpy = (y1 - cy1 * 2 + cy2) * 0.1875
  327. dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.09375
  328. dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.09375
  329. ddfx = tmpx * 2 + dddfx
  330. ddfy = tmpy * 2 + dddfy
  331. dfx = (cx1 - x1) * 0.75 + tmpx + dddfx * 0.16666667
  332. dfy = (cy1 - y1) * 0.75 + tmpy + dddfy * 0.16666667
  333. pathLength = pathLength + math_sqrt(dfx * dfx + dfy * dfy)
  334. dfx = dfx + ddfx
  335. dfy = dfy + ddfy
  336. ddfx = ddfx + dddfx
  337. ddfy = ddfy + dddfy
  338. pathLength = pathLength + math_sqrt(dfx * dfx + dfy * dfy)
  339. dfx = dfx + ddfx
  340. dfy = dfy + ddfy
  341. pathLength = pathLength + math_sqrt(dfx * dfx + dfy * dfy)
  342. dfx = dfx + ddfx + dddfx
  343. dfy = dfy + ddfy + dddfy
  344. pathLength = pathLength + math_sqrt(dfx * dfx + dfy * dfy)
  345. curves[i + 1] = pathLength
  346. x1 = x2
  347. y1 = y2
  348. i = i + 1
  349. w = w + 6
  350. end
  351. if percentPosition then position = position * pathLength end
  352. if percentSpacing then
  353. local i = 0
  354. while i < spacesCount do
  355. spaces[i + 1] = spaces[i + 1] * pathLength
  356. i = i + 1
  357. end
  358. end
  359. local segments = self.segments
  360. local curveLength = 0
  361. local i = 0
  362. local o = 0
  363. local curve = 0
  364. local segment = 0
  365. while i < spacesCount do
  366. local space = spaces[i + 1]
  367. position = position + space
  368. local p = position
  369. local skip = false
  370. if closed then
  371. p = p % pathLength
  372. if p < 0 then p = p + pathLength end
  373. curve = 0
  374. elseif p < 0 then
  375. self:addBeforePosition(p, world, 0, out, o)
  376. skip = true
  377. elseif p > pathLength then
  378. self:addAfterPosition(p - pathLength, world, verticesLength - 4, out, o)
  379. skip = true
  380. end
  381. if not skip then
  382. -- Determine curve containing position.
  383. while true do
  384. local length = curves[curve + 1]
  385. if p <= length then
  386. if curve == 0 then
  387. p = p / length
  388. else
  389. local prev = curves[curve - 1 + 1]
  390. p = (p - prev) / (length - prev)
  391. end
  392. break
  393. end
  394. curve = curve + 1
  395. end
  396. -- Curve segment lengths.
  397. if curve ~= prevCurve then
  398. prevCurve = curve
  399. local ii = curve * 6
  400. x1 = world[ii + 1]
  401. y1 = world[ii + 2]
  402. cx1 = world[ii + 3]
  403. cy1 = world[ii + 4]
  404. cx2 = world[ii + 5]
  405. cy2 = world[ii + 6]
  406. x2 = world[ii + 7]
  407. y2 = world[ii + 8]
  408. tmpx = (x1 - cx1 * 2 + cx2) * 0.03
  409. tmpy = (y1 - cy1 * 2 + cy2) * 0.03
  410. dddfx = ((cx1 - cx2) * 3 - x1 + x2) * 0.006
  411. dddfy = ((cy1 - cy2) * 3 - y1 + y2) * 0.006
  412. ddfx = tmpx * 2 + dddfx
  413. ddfy = tmpy * 2 + dddfy
  414. dfx = (cx1 - x1) * 0.3 + tmpx + dddfx * 0.16666667
  415. dfy = (cy1 - y1) * 0.3 + tmpy + dddfy * 0.16666667
  416. curveLength = math_sqrt(dfx * dfx + dfy * dfy)
  417. segments[1] = curveLength
  418. ii = 1
  419. while ii < 8 do
  420. dfx = dfx + ddfx
  421. dfy = dfy + ddfy
  422. ddfx = ddfx + dddfx
  423. ddfy = ddfy + dddfy
  424. curveLength = curveLength + math_sqrt(dfx * dfx + dfy * dfy)
  425. segments[ii + 1] = curveLength
  426. ii = ii + 1
  427. end
  428. dfx = dfx + ddfx
  429. dfy = dfy + ddfy
  430. curveLength = curveLength + math_sqrt(dfx * dfx + dfy * dfy)
  431. segments[9] = curveLength
  432. dfx = dfx + ddfx + dddfx
  433. dfy = dfy + ddfy + dddfy
  434. curveLength = curveLength + math_sqrt(dfx * dfx + dfy * dfy)
  435. segments[10] = curveLength
  436. segment = 0
  437. end
  438. -- Weight by segment length.
  439. p = p * curveLength
  440. while true do
  441. local length = segments[segment + 1]
  442. if p <= length then
  443. if segment == 0 then
  444. p = p / length
  445. else
  446. local prev = segments[segment - 1 + 1]
  447. p = segment + (p - prev) / (length - prev)
  448. end
  449. break;
  450. end
  451. segment = segment + 1
  452. end
  453. self:addCurvePosition(p * 0.1, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents or (i > 0 and space == 0))
  454. end
  455. i = i + 1
  456. o = o + 3
  457. end
  458. return out
  459. end
  460. function PathConstraint:addBeforePosition (p, temp, i, out, o)
  461. local x1 = temp[i + 1]
  462. local y1 = temp[i + 2]
  463. local dx = temp[i + 3] - x1
  464. local dy = temp[i + 4] - y1
  465. local r = math_atan2(dy, dx)
  466. out[o + 1] = x1 + p * math_cos(r)
  467. out[o + 2] = y1 + p * math_sin(r)
  468. out[o + 3] = r
  469. end
  470. function PathConstraint:addAfterPosition(p, temp, i, out, o)
  471. local x1 = temp[i + 3]
  472. local y1 = temp[i + 4]
  473. local dx = x1 - temp[i + 1]
  474. local dy = y1 - temp[i + 2]
  475. local r = math_atan2(dy, dx)
  476. out[o + 1] = x1 + p * math_cos(r)
  477. out[o + 2] = y1 + p * math_sin(r)
  478. out[o + 3] = r
  479. end
  480. function PathConstraint:addCurvePosition(p, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, tangents)
  481. if p == 0 or (p ~= p) then p = 0.0001 end
  482. local tt = p * p
  483. local ttt = tt * p
  484. local u = 1 - p
  485. local uu = u * u
  486. local uuu = uu * u
  487. local ut = u * p
  488. local ut3 = ut * 3
  489. local uut3 = u * ut3
  490. local utt3 = ut3 * p
  491. local x = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt
  492. local y = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt
  493. out[o + 1] = x
  494. out[o + 2] = y
  495. if tangents then out[o + 3] = math_atan2(y - (y1 * uu + cy1 * ut * 2 + cy2 * tt), x - (x1 * uu + cx1 * ut * 2 + cx2 * tt)) end
  496. end
  497. return PathConstraint