Эх сурвалжийг харах

IK for spine-lua, spine-corona, spine-love.

NathanSweet 10 жил өмнө
parent
commit
7c065a76e2
95 өөрчлөгдсөн 560 нэмэгдсэн , 2610 устгасан
  1. 0 0
      spine-corona/examples/dragon/dragon.lua
  2. 0 0
      spine-corona/examples/goblins/goblins.lua
  3. 19 3
      spine-corona/examples/hero/hero.lua
  4. BIN
      spine-corona/examples/spineboy/images/eye_indifferent.png
  5. BIN
      spine-corona/examples/spineboy/images/eye_surprised.png
  6. BIN
      spine-corona/examples/spineboy/images/front_bracer.png
  7. BIN
      spine-corona/examples/spineboy/images/front_fist_closed.png
  8. BIN
      spine-corona/examples/spineboy/images/front_fist_open.png
  9. BIN
      spine-corona/examples/spineboy/images/front_foot.png
  10. BIN
      spine-corona/examples/spineboy/images/front_foot_bend1.png
  11. BIN
      spine-corona/examples/spineboy/images/front_foot_bend2.png
  12. BIN
      spine-corona/examples/spineboy/images/front_shin.png
  13. BIN
      spine-corona/examples/spineboy/images/front_thigh.png
  14. BIN
      spine-corona/examples/spineboy/images/front_upper_arm.png
  15. BIN
      spine-corona/examples/spineboy/images/goggles.png
  16. BIN
      spine-corona/examples/spineboy/images/gun.png
  17. BIN
      spine-corona/examples/spineboy/images/head.png
  18. BIN
      spine-corona/examples/spineboy/images/mouth_grind.png
  19. BIN
      spine-corona/examples/spineboy/images/mouth_oooo.png
  20. BIN
      spine-corona/examples/spineboy/images/mouth_smile.png
  21. BIN
      spine-corona/examples/spineboy/images/muzzle.png
  22. BIN
      spine-corona/examples/spineboy/images/neck.png
  23. BIN
      spine-corona/examples/spineboy/images/rear_bracer.png
  24. BIN
      spine-corona/examples/spineboy/images/rear_foot.png
  25. BIN
      spine-corona/examples/spineboy/images/rear_foot_bend1.png
  26. BIN
      spine-corona/examples/spineboy/images/rear_foot_bend2.png
  27. BIN
      spine-corona/examples/spineboy/images/rear_shin.png
  28. BIN
      spine-corona/examples/spineboy/images/rear_thigh.png
  29. BIN
      spine-corona/examples/spineboy/images/rear_upper_arm.png
  30. BIN
      spine-corona/examples/spineboy/images/torso.png
  31. 0 1002
      spine-corona/examples/spineboy/spineboy.json
  32. 6 5
      spine-corona/examples/spineboy/spineboy.lua
  33. 4 4
      spine-corona/main.lua
  34. 2 0
      spine-corona/spine-corona/spine.lua
  35. 0 499
      spine-love/data/goblins.json
  36. BIN
      spine-love/data/head.png
  37. BIN
      spine-love/data/images/eye_indifferent.png
  38. BIN
      spine-love/data/images/eye_surprised.png
  39. 0 0
      spine-love/data/images/eyes-closed.png
  40. 0 0
      spine-love/data/images/eyes.png
  41. BIN
      spine-love/data/images/front_bracer.png
  42. BIN
      spine-love/data/images/front_fist_closed.png
  43. BIN
      spine-love/data/images/front_fist_open.png
  44. BIN
      spine-love/data/images/front_foot.png
  45. BIN
      spine-love/data/images/front_foot_bend1.png
  46. BIN
      spine-love/data/images/front_foot_bend2.png
  47. BIN
      spine-love/data/images/front_shin.png
  48. BIN
      spine-love/data/images/front_thigh.png
  49. BIN
      spine-love/data/images/front_upper_arm.png
  50. BIN
      spine-love/data/images/goggles.png
  51. BIN
      spine-love/data/images/gun.png
  52. BIN
      spine-love/data/images/head.png
  53. 0 0
      spine-love/data/images/left-ankle.png
  54. 0 0
      spine-love/data/images/left-arm.png
  55. 0 0
      spine-love/data/images/left-foot.png
  56. 0 0
      spine-love/data/images/left-hand.png
  57. 0 0
      spine-love/data/images/left-lower-leg.png
  58. 0 0
      spine-love/data/images/left-pant-bottom.png
  59. 0 0
      spine-love/data/images/left-shoulder.png
  60. 0 0
      spine-love/data/images/left-upper-leg.png
  61. BIN
      spine-love/data/images/mouth_grind.png
  62. BIN
      spine-love/data/images/mouth_oooo.png
  63. BIN
      spine-love/data/images/mouth_smile.png
  64. BIN
      spine-love/data/images/muzzle.png
  65. BIN
      spine-love/data/images/neck.png
  66. 0 0
      spine-love/data/images/pelvis.png
  67. BIN
      spine-love/data/images/rear_bracer.png
  68. BIN
      spine-love/data/images/rear_foot.png
  69. BIN
      spine-love/data/images/rear_foot_bend1.png
  70. BIN
      spine-love/data/images/rear_foot_bend2.png
  71. BIN
      spine-love/data/images/rear_shin.png
  72. BIN
      spine-love/data/images/rear_thigh.png
  73. BIN
      spine-love/data/images/rear_upper_arm.png
  74. 0 0
      spine-love/data/images/right-ankle.png
  75. 0 0
      spine-love/data/images/right-arm.png
  76. 0 0
      spine-love/data/images/right-foot-idle.png
  77. 0 0
      spine-love/data/images/right-foot.png
  78. 0 0
      spine-love/data/images/right-hand.png
  79. 0 0
      spine-love/data/images/right-lower-leg.png
  80. 0 0
      spine-love/data/images/right-pant-bottom.png
  81. 0 0
      spine-love/data/images/right-shoulder.png
  82. 0 0
      spine-love/data/images/right-upper-leg.png
  83. BIN
      spine-love/data/images/torso.png
  84. BIN
      spine-love/data/neck.png
  85. 0 1002
      spine-love/data/spineboy.json
  86. BIN
      spine-love/data/torso.png
  87. 8 7
      spine-love/main.lua
  88. 4 0
      spine-love/spine-love/spine.lua
  89. 151 78
      spine-lua/Animation.lua
  90. 28 4
      spine-lua/Bone.lua
  91. 150 0
      spine-lua/IkConstraint.lua
  92. 45 0
      spine-lua/IkConstraintData.lua
  93. 70 3
      spine-lua/Skeleton.lua
  94. 11 0
      spine-lua/SkeletonData.lua
  95. 62 3
      spine-lua/SkeletonJson.lua

+ 0 - 0
spine-corona/examples/dragon.lua → spine-corona/examples/dragon/dragon.lua


+ 0 - 0
spine-corona/examples/goblins.lua → spine-corona/examples/goblins/goblins.lua


+ 19 - 3
spine-corona/examples/hero.lua → spine-corona/examples/hero/hero.lua

@@ -10,7 +10,7 @@ local skeleton = spine.Skeleton.new(skeletonData)
 function skeleton:createImage (attachment)
 	return display.newImage("examples/hero/images/" .. attachment.name .. ".png")
 end
-skeleton.group.x = 195
+skeleton.group.x = 95
 skeleton.group.y = 385
 skeleton.flipX = false
 skeleton.flipY = false
@@ -19,10 +19,13 @@ skeleton:setToSetupPose()
 
 -- AnimationStateData defines crossfade durations between animations.
 local stateData = spine.AnimationStateData.new(skeletonData)
+stateData.defaultMix = 0.2;
+stateData:setMix("Walk", "Attack", 0)
+stateData:setMix("Attack", "Run", 0)
+stateData:setMix("Run", "Attack", 0)
 -- AnimationState has a queue of animations and can apply them with crossfading.
 local state = spine.AnimationState.new(stateData)
---state:setAnimationByName(0, "Idle", true, 0)
-state:setAnimationByName(0, "Walk", true, 0)
+state:setAnimationByName(0, "Idle", true)
 
 local lastTime = 0
 local animationTime = 0
@@ -37,3 +40,16 @@ Runtime:addEventListener("enterFrame", function (event)
 	state:apply(skeleton)
 	skeleton:updateWorldTransform()
 end)
+
+Runtime:addEventListener("touch", function (event)
+	if event.phase ~= "ended" and event.phase ~= "cancelled" then return end
+	local name = state:getCurrent(0).animation.name
+	if name == "Idle" then
+		state:setAnimationByName(0, "Crouch", true)
+	elseif name == "Crouch" then
+		state:setAnimationByName(0, "Walk", true)
+	else
+		state:setAnimationByName(0, "Attack", false)
+		state:addAnimationByName(0, "Run", true, 0)
+	end
+end)

BIN
spine-corona/examples/spineboy/images/eye_indifferent.png


BIN
spine-corona/examples/spineboy/images/eye_surprised.png


BIN
spine-corona/examples/spineboy/images/front_bracer.png


BIN
spine-corona/examples/spineboy/images/front_fist_closed.png


BIN
spine-corona/examples/spineboy/images/front_fist_open.png


BIN
spine-corona/examples/spineboy/images/front_foot.png


BIN
spine-corona/examples/spineboy/images/front_foot_bend1.png


BIN
spine-corona/examples/spineboy/images/front_foot_bend2.png


BIN
spine-corona/examples/spineboy/images/front_shin.png


BIN
spine-corona/examples/spineboy/images/front_thigh.png


BIN
spine-corona/examples/spineboy/images/front_upper_arm.png


BIN
spine-corona/examples/spineboy/images/goggles.png


BIN
spine-corona/examples/spineboy/images/gun.png


BIN
spine-corona/examples/spineboy/images/head.png


BIN
spine-corona/examples/spineboy/images/mouth_grind.png


BIN
spine-corona/examples/spineboy/images/mouth_oooo.png


BIN
spine-corona/examples/spineboy/images/mouth_smile.png


BIN
spine-corona/examples/spineboy/images/muzzle.png


BIN
spine-corona/examples/spineboy/images/neck.png


BIN
spine-corona/examples/spineboy/images/rear_bracer.png


BIN
spine-corona/examples/spineboy/images/rear_foot.png


BIN
spine-corona/examples/spineboy/images/rear_foot_bend1.png


BIN
spine-corona/examples/spineboy/images/rear_foot_bend2.png


BIN
spine-corona/examples/spineboy/images/rear_shin.png


BIN
spine-corona/examples/spineboy/images/rear_thigh.png


BIN
spine-corona/examples/spineboy/images/rear_upper_arm.png


BIN
spine-corona/examples/spineboy/images/torso.png


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 1002
spine-corona/examples/spineboy/spineboy.json


+ 6 - 5
spine-corona/examples/spineboy.lua → spine-corona/examples/spineboy/spineboy.lua

@@ -4,7 +4,7 @@
 local spine = require "spine-corona.spine"
 
 local json = spine.SkeletonJson.new()
-json.scale = 1
+json.scale = 0.6
 local skeletonData = json:readSkeletonDataFile("examples/spineboy/spineboy.json")
 
 local skeleton = spine.Skeleton.new(skeletonData)
@@ -25,13 +25,14 @@ local bounds = spine.SkeletonBounds.new()
 -- AnimationStateData defines crossfade durations between animations.
 local stateData = spine.AnimationStateData.new(skeletonData)
 stateData:setMix("walk", "jump", 0.2)
-stateData:setMix("jump", "walk", 0.4)
+stateData:setMix("jump", "run", 0.2)
 
 -- AnimationState has a queue of animations and can apply them with crossfading.
 local state = spine.AnimationState.new(stateData)
-state:setAnimationByName(0, "drawOrder")
-state:addAnimationByName(0, "jump", false, 0)
-state:addAnimationByName(0, "walk", true, 0)
+-- state:setAnimationByName(0, "test")
+state:setAnimationByName(0, "walk", true)
+state:addAnimationByName(0, "jump", false, 3)
+state:addAnimationByName(0, "run", true, 0)
 
 state.onStart = function (trackIndex)
 	print(trackIndex.." start: "..state:getCurrent(trackIndex).animation.name)

+ 4 - 4
spine-corona/main.lua

@@ -1,5 +1,5 @@
 
--- require "examples.spineboy"
--- require "examples.goblins"
--- require "examples.dragon"
-require "examples.hero"
+require "examples.spineboy.spineboy"
+-- require "examples.goblins.goblins"
+-- require "examples.dragon.dragon"
+-- require "examples.hero.hero"

+ 2 - 0
spine-corona/spine-corona/spine.lua

@@ -35,6 +35,7 @@ spine.SkeletonJson = require "spine-lua.SkeletonJson"
 spine.SkeletonData = require "spine-lua.SkeletonData"
 spine.BoneData = require "spine-lua.BoneData"
 spine.SlotData = require "spine-lua.SlotData"
+spine.IkConstraintData = require "spine-lua.IkConstraintData"
 spine.Skin = require "spine-lua.Skin"
 spine.RegionAttachment = require "spine-lua.RegionAttachment"
 spine.MeshAttachment = require "spine-lua.MeshAttachment"
@@ -42,6 +43,7 @@ spine.SkinnedMeshAttachment = require "spine-lua.SkinnedMeshAttachment"
 spine.Skeleton = require "spine-lua.Skeleton"
 spine.Bone = require "spine-lua.Bone"
 spine.Slot = require "spine-lua.Slot"
+spine.IkConstraint = require "spine-lua.IkConstraint"
 spine.AttachmentType = require "spine-lua.AttachmentType"
 spine.AttachmentLoader = require "spine-lua.AttachmentLoader"
 spine.Animation = require "spine-lua.Animation"

+ 0 - 499
spine-love/data/goblins.json

@@ -1,499 +0,0 @@
-{
-"bones": [
-	{ "name": "root" },
-	{ "name": "hip", "parent": "root", "x": 0.64, "y": 114.41 },
-	{ "name": "left upper leg", "parent": "hip", "length": 50.39, "x": 14.45, "y": 2.81, "rotation": -89.09 },
-	{ "name": "left lower leg", "parent": "left upper leg", "length": 49.89, "x": 56.34, "y": 0.98, "rotation": -16.65 },
-	{ "name": "left foot", "parent": "left lower leg", "length": 46.5, "x": 58.94, "y": -7.61, "rotation": 102.43 },
-	{ "name": "right upper leg", "parent": "hip", "length": 42.45, "x": -20.07, "y": -6.83, "rotation": -97.49 },
-	{ "name": "right lower leg", "parent": "right upper leg", "length": 58.52, "x": 42.99, "y": -0.61, "rotation": -14.34 },
-	{ "name": "right foot", "parent": "right lower leg", "length": 45.45, "x": 64.88, "y": 0.04, "rotation": 110.3 },
-	{ "name": "torso", "parent": "hip", "length": 85.82, "x": -6.42, "y": 1.97, "rotation": 93.92 },
-	{ "name": "neck", "parent": "torso", "length": 18.38, "x": 81.67, "y": -6.34, "rotation": -1.51 },
-	{ "name": "head", "parent": "neck", "length": 68.28, "x": 20.93, "y": 11.59, "rotation": -13.92 },
-	{ "name": "right shoulder", "parent": "torso", "length": 37.24, "x": 76.02, "y": 18.14, "rotation": 133.88 },
-	{ "name": "right arm", "parent": "right shoulder", "length": 36.74, "x": 37.6, "y": 0.31, "rotation": 36.32 },
-	{ "name": "right hand", "parent": "right arm", "length": 15.32, "x": 36.9, "y": 0.34, "rotation": 2.35 },
-	{ "name": "left shoulder", "parent": "torso", "length": 35.43, "x": 74.04, "y": -20.38, "rotation": -156.96 },
-	{ "name": "left arm", "parent": "left shoulder", "length": 35.62, "x": 37.85, "y": -2.34, "rotation": 28.16 },
-	{ "name": "left hand", "parent": "left arm", "length": 11.52, "x": 35.62, "y": 0.07, "rotation": 2.7 },
-	{ "name": "pelvis", "parent": "hip", "x": 1.41, "y": -6.57 }
-],
-"slots": [
-	{ "name": "left shoulder", "bone": "left shoulder", "attachment": "left shoulder" },
-	{ "name": "left arm", "bone": "left arm", "attachment": "left arm" },
-	{ "name": "left hand item", "bone": "left hand", "attachment": "spear" },
-	{ "name": "left hand", "bone": "left hand", "attachment": "left hand" },
-	{ "name": "left foot", "bone": "left foot", "attachment": "left foot" },
-	{ "name": "left lower leg", "bone": "left lower leg", "attachment": "left lower leg" },
-	{ "name": "left upper leg", "bone": "left upper leg", "attachment": "left upper leg" },
-	{ "name": "neck", "bone": "neck", "attachment": "neck" },
-	{ "name": "torso", "bone": "torso", "attachment": "torso" },
-	{ "name": "pelvis", "bone": "pelvis", "attachment": "pelvis" },
-	{ "name": "right foot", "bone": "right foot", "attachment": "right foot" },
-	{ "name": "right lower leg", "bone": "right lower leg", "attachment": "right lower leg" },
-	{ "name": "undie straps", "bone": "pelvis", "attachment": "undie straps" },
-	{ "name": "undies", "bone": "pelvis", "attachment": "undies" },
-	{ "name": "right upper leg", "bone": "right upper leg", "attachment": "right upper leg" },
-	{ "name": "head", "bone": "head", "attachment": "head" },
-	{ "name": "eyes", "bone": "head" },
-	{ "name": "right shoulder", "bone": "right shoulder", "attachment": "right shoulder" },
-	{ "name": "right arm", "bone": "right arm", "attachment": "right arm" },
-	{ "name": "right hand item", "bone": "right hand", "attachment": "dagger" },
-	{ "name": "right hand", "bone": "right hand", "attachment": "right hand" }
-],
-"skins": {
-	"default": {
-		"left hand item": {
-			"dagger": { "x": 7.88, "y": -23.45, "rotation": 10.47, "width": 26, "height": 108 },
-			"spear": { "x": -4.55, "y": 39.2, "rotation": 13.04, "width": 22, "height": 368 }
-		},
-		"right hand item": {
-			"dagger": { "x": 6.51, "y": -24.15, "rotation": -8.06, "width": 26, "height": 108 }
-		}
-	},
-	"goblin": {
-		"neck": {
-			"neck": { "name": "goblin/neck", "x": 10.1, "y": 0.42, "rotation": -93.69, "width": 36, "height": 41 }
-		},
-		"undies": {
-			"undies": { "name": "goblin/undies", "x": 6.3, "y": 0.12, "rotation": 0.91, "width": 36, "height": 29 }
-		},
-		"right hand": {
-			"right hand": { "name": "goblin/right-hand", "x": 7.88, "y": 2.78, "rotation": 91.96, "width": 36, "height": 37 }
-		},
-		"right arm": {
-			"right arm": { "name": "goblin/right-arm", "x": 16.44, "y": -1.04, "rotation": 94.32, "width": 23, "height": 50 }
-		},
-		"head": {
-			"head": { "name": "goblin/head", "x": 25.73, "y": 2.33, "rotation": -92.29, "width": 103, "height": 66 }
-		},
-		"left shoulder": {
-			"left shoulder": { "name": "goblin/left-shoulder", "x": 15.56, "y": -2.26, "rotation": 62.01, "width": 29, "height": 44 }
-		},
-		"left arm": {
-			"left arm": {
-				"name": "goblin/left-arm",
-				"x": 16.7,
-				"y": -1.69,
-				"scaleX": 1.057,
-				"scaleY": 1.057,
-				"rotation": 33.84,
-				"width": 37,
-				"height": 35
-			}
-		},
-		"left hand": {
-			"left hand": {
-				"name": "goblin/left-hand",
-				"x": 3.47,
-				"y": 3.41,
-				"scaleX": 0.892,
-				"scaleY": 0.892,
-				"rotation": 31.14,
-				"width": 36,
-				"height": 41
-			}
-		},
-		"right lower leg": {
-			"right lower leg": { "name": "goblin/right-lower-leg", "x": 25.68, "y": -3.15, "rotation": 111.83, "width": 36, "height": 76 }
-		},
-		"right upper leg": {
-			"right upper leg": { "name": "goblin/right-upper-leg", "x": 20.35, "y": 1.47, "rotation": 97.49, "width": 34, "height": 63 }
-		},
-		"pelvis": {
-			"pelvis": { "name": "goblin/pelvis", "x": -5.61, "y": 0.76, "width": 62, "height": 43 }
-		},
-		"left lower leg": {
-			"left lower leg": { "name": "goblin/left-lower-leg", "x": 23.58, "y": -2.06, "rotation": 105.75, "width": 33, "height": 70 }
-		},
-		"left upper leg": {
-			"left upper leg": { "name": "goblin/left-upper-leg", "x": 29.68, "y": -3.87, "rotation": 89.09, "width": 33, "height": 73 }
-		},
-		"torso": {
-			"torso": { "name": "goblin/torso", "x": 38.09, "y": -3.87, "rotation": -94.95, "width": 68, "height": 96 }
-		},
-		"right shoulder": {
-			"right shoulder": { "name": "goblin/right-shoulder", "x": 15.68, "y": -1.03, "rotation": 130.65, "width": 39, "height": 45 }
-		},
-		"right foot": {
-			"right foot": { "name": "goblin/right-foot", "x": 23.56, "y": 9.8, "rotation": 1.52, "width": 63, "height": 33 }
-		},
-		"left foot": {
-			"left foot": { "name": "goblin/left-foot", "x": 24.85, "y": 8.74, "rotation": 3.32, "width": 65, "height": 31 }
-		},
-		"undie straps": {
-			"undie straps": { "name": "goblin/undie-straps", "x": -3.87, "y": 13.1, "scaleX": 1.089, "width": 55, "height": 19 }
-		},
-		"eyes": {
-			"eyes closed": { "name": "goblin/eyes-closed", "x": 32.21, "y": -21.27, "rotation": -88.92, "width": 34, "height": 12 }
-		}
-	},
-	"goblingirl": {
-		"left upper leg": {
-			"left upper leg": { "name": "goblingirl/left-upper-leg", "x": 30.21, "y": -2.95, "rotation": 89.09, "width": 33, "height": 70 }
-		},
-		"left lower leg": {
-			"left lower leg": { "name": "goblingirl/left-lower-leg", "x": 25.02, "y": -0.6, "rotation": 105.75, "width": 33, "height": 70 }
-		},
-		"left foot": {
-			"left foot": { "name": "goblingirl/left-foot", "x": 25.17, "y": 7.92, "rotation": 3.32, "width": 65, "height": 31 }
-		},
-		"right upper leg": {
-			"right upper leg": { "name": "goblingirl/right-upper-leg", "x": 19.69, "y": 2.13, "rotation": 97.49, "width": 34, "height": 63 }
-		},
-		"right lower leg": {
-			"right lower leg": { "name": "goblingirl/right-lower-leg", "x": 26.15, "y": -3.27, "rotation": 111.83, "width": 36, "height": 76 }
-		},
-		"right foot": {
-			"right foot": { "name": "goblingirl/right-foot", "x": 23.46, "y": 9.66, "rotation": 1.52, "width": 63, "height": 33 }
-		},
-		"torso": {
-			"torso": { "name": "goblingirl/torso", "x": 36.28, "y": -5.14, "rotation": -95.74, "width": 68, "height": 96 }
-		},
-		"left shoulder": {
-			"left shoulder": { "name": "goblingirl/left-shoulder", "x": 19.8, "y": -0.42, "rotation": 61.21, "width": 28, "height": 46 }
-		},
-		"left arm": {
-			"left arm": { "name": "goblingirl/left-arm", "x": 19.64, "y": -2.42, "rotation": 33.05, "width": 37, "height": 35 }
-		},
-		"left hand": {
-			"left hand": {
-				"name": "goblingirl/left-hand",
-				"x": 4.34,
-				"y": 2.39,
-				"scaleX": 0.896,
-				"scaleY": 0.896,
-				"rotation": 30.34,
-				"width": 35,
-				"height": 40
-			}
-		},
-		"neck": {
-			"neck": { "name": "goblingirl/neck", "x": 6.16, "y": -3.14, "rotation": -98.86, "width": 35, "height": 41 }
-		},
-		"head": {
-			"head": { "name": "goblingirl/head", "x": 27.71, "y": -4.32, "rotation": -85.58, "width": 103, "height": 81 }
-		},
-		"right shoulder": {
-			"right shoulder": { "name": "goblingirl/right-shoulder", "x": 14.46, "y": 0.45, "rotation": 129.85, "width": 39, "height": 45 }
-		},
-		"right arm": {
-			"right arm": { "name": "goblingirl/right-arm", "x": 16.85, "y": -0.66, "rotation": 93.52, "width": 28, "height": 50 }
-		},
-		"right hand": {
-			"right hand": { "name": "goblingirl/right-hand", "x": 7.21, "y": 3.43, "rotation": 91.16, "width": 36, "height": 37 }
-		},
-		"pelvis": {
-			"pelvis": { "name": "goblingirl/pelvis", "x": -3.87, "y": 3.18, "width": 62, "height": 43 }
-		},
-		"undie straps": {
-			"undie straps": { "name": "goblingirl/undie-straps", "x": -1.51, "y": 14.18, "width": 55, "height": 19 }
-		},
-		"undies": {
-			"undies": { "name": "goblingirl/undies", "x": 5.4, "y": 1.7, "width": 36, "height": 29 }
-		},
-		"eyes": {
-			"eyes closed": { "name": "goblingirl/eyes-closed", "x": 28, "y": -25.54, "rotation": -87.04, "width": 37, "height": 21 }
-		}
-	}
-},
-"animations": {
-	"walk": {
-		"bones": {
-			"left upper leg": {
-				"rotate": [
-					{ "time": 0, "angle": -26.55 },
-					{ "time": 0.1333, "angle": -8.78 },
-					{ "time": 0.2333, "angle": 9.51 },
-					{ "time": 0.3666, "angle": 30.74 },
-					{ "time": 0.5, "angle": 25.33 },
-					{ "time": 0.6333, "angle": 26.11 },
-					{ "time": 0.7333, "angle": -7.7 },
-					{ "time": 0.8666, "angle": -21.19 },
-					{ "time": 1, "angle": -26.55 }
-				],
-				"translate": [
-					{ "time": 0, "x": -1.32, "y": 1.7 },
-					{ "time": 0.3666, "x": -0.06, "y": 2.42 },
-					{ "time": 1, "x": -1.32, "y": 1.7 }
-				]
-			},
-			"right upper leg": {
-				"rotate": [
-					{ "time": 0, "angle": 42.45 },
-					{ "time": 0.1333, "angle": 52.1 },
-					{ "time": 0.2333, "angle": 8.53 },
-					{ "time": 0.5, "angle": -16.93 },
-					{ "time": 0.6333, "angle": 1.89 },
-					{
-						"time": 0.7333,
-						"angle": 28.06,
-						"curve": [ 0.462, 0.11, 1, 1 ]
-					},
-					{
-						"time": 0.8666,
-						"angle": 58.68,
-						"curve": [ 0.5, 0.02, 1, 1 ]
-					},
-					{ "time": 1, "angle": 42.45 }
-				],
-				"translate": [
-					{ "time": 0, "x": 6.23, "y": 0 },
-					{ "time": 0.2333, "x": 2.14, "y": 2.4 },
-					{ "time": 0.5, "x": 2.44, "y": 4.8 },
-					{ "time": 1, "x": 6.23, "y": 0 }
-				]
-			},
-			"left lower leg": {
-				"rotate": [
-					{ "time": 0, "angle": -22.98 },
-					{ "time": 0.1333, "angle": -63.5 },
-					{ "time": 0.2333, "angle": -73.76 },
-					{ "time": 0.5, "angle": 5.11 },
-					{ "time": 0.6333, "angle": -28.29 },
-					{ "time": 0.7333, "angle": 4.08 },
-					{ "time": 0.8666, "angle": 3.53 },
-					{ "time": 1, "angle": -22.98 }
-				],
-				"translate": [
-					{ "time": 0, "x": 0, "y": 0 },
-					{ "time": 0.2333, "x": 2.55, "y": -0.47 },
-					{ "time": 0.5, "x": 0, "y": 0, "curve": "stepped" },
-					{ "time": 1, "x": 0, "y": 0 }
-				]
-			},
-			"left foot": {
-				"rotate": [
-					{ "time": 0, "angle": -3.69 },
-					{ "time": 0.1333, "angle": -10.42 },
-					{ "time": 0.2333, "angle": -5.01 },
-					{ "time": 0.3666, "angle": 3.87 },
-					{ "time": 0.5, "angle": -3.87 },
-					{ "time": 0.6333, "angle": 2.78 },
-					{ "time": 0.7333, "angle": 1.68 },
-					{ "time": 0.8666, "angle": -8.54 },
-					{ "time": 1, "angle": -3.69 }
-				]
-			},
-			"right shoulder": {
-				"rotate": [
-					{
-						"time": 0,
-						"angle": 5.29,
-						"curve": [ 0.264, 0, 0.75, 1 ]
-					},
-					{ "time": 0.6333, "angle": 6.65 },
-					{ "time": 1, "angle": 5.29 }
-				]
-			},
-			"right arm": {
-				"rotate": [
-					{
-						"time": 0,
-						"angle": -4.02,
-						"curve": [ 0.267, 0, 0.804, 0.99 ]
-					},
-					{
-						"time": 0.6333,
-						"angle": 19.78,
-						"curve": [ 0.307, 0, 0.787, 0.99 ]
-					},
-					{ "time": 1, "angle": -4.02 }
-				]
-			},
-			"right hand": {
-				"rotate": [
-					{ "time": 0, "angle": 8.98 },
-					{ "time": 0.6333, "angle": 0.51 },
-					{ "time": 1, "angle": 8.98 }
-				]
-			},
-			"left shoulder": {
-				"rotate": [
-					{
-						"time": 0,
-						"angle": 6.25,
-						"curve": [ 0.339, 0, 0.683, 1 ]
-					},
-					{
-						"time": 0.5,
-						"angle": -11.78,
-						"curve": [ 0.281, 0, 0.686, 0.99 ]
-					},
-					{ "time": 1, "angle": 6.25 }
-				],
-				"translate": [
-					{ "time": 0, "x": 1.15, "y": 0.23 }
-				]
-			},
-			"left hand": {
-				"rotate": [
-					{
-						"time": 0,
-						"angle": -21.23,
-						"curve": [ 0.295, 0, 0.755, 0.98 ]
-					},
-					{
-						"time": 0.5,
-						"angle": -27.28,
-						"curve": [ 0.241, 0, 0.75, 0.97 ]
-					},
-					{ "time": 1, "angle": -21.23 }
-				]
-			},
-			"left arm": {
-				"rotate": [
-					{
-						"time": 0,
-						"angle": 28.37,
-						"curve": [ 0.339, 0, 0.683, 1 ]
-					},
-					{
-						"time": 0.5,
-						"angle": 60.09,
-						"curve": [ 0.281, 0, 0.686, 0.99 ]
-					},
-					{ "time": 1, "angle": 28.37 }
-				]
-			},
-			"torso": {
-				"rotate": [
-					{ "time": 0, "angle": -10.28 },
-					{
-						"time": 0.1333,
-						"angle": -15.38,
-						"curve": [ 0.545, 0, 0.818, 1 ]
-					},
-					{
-						"time": 0.3666,
-						"angle": -9.78,
-						"curve": [ 0.58, 0.17, 0.669, 0.99 ]
-					},
-					{
-						"time": 0.6333,
-						"angle": -15.75,
-						"curve": [ 0.235, 0.01, 0.795, 1 ]
-					},
-					{
-						"time": 0.8666,
-						"angle": -7.06,
-						"curve": [ 0.209, 0, 0.816, 0.98 ]
-					},
-					{ "time": 1, "angle": -10.28 }
-				],
-				"translate": [
-					{ "time": 0, "x": -1.29, "y": 1.68 }
-				]
-			},
-			"right foot": {
-				"rotate": [
-					{ "time": 0, "angle": -5.25 },
-					{ "time": 0.2333, "angle": -1.91 },
-					{ "time": 0.3666, "angle": -6.45 },
-					{ "time": 0.5, "angle": -5.39 },
-					{ "time": 0.7333, "angle": -11.68 },
-					{ "time": 0.8666, "angle": 0.46 },
-					{ "time": 1, "angle": -5.25 }
-				]
-			},
-			"right lower leg": {
-				"rotate": [
-					{
-						"time": 0,
-						"angle": -3.39,
-						"curve": [ 0.316, 0.01, 0.741, 0.98 ]
-					},
-					{
-						"time": 0.1333,
-						"angle": -45.53,
-						"curve": [ 0.229, 0, 0.738, 0.97 ]
-					},
-					{ "time": 0.2333, "angle": -4.83 },
-					{ "time": 0.5, "angle": -19.53 },
-					{ "time": 0.6333, "angle": -64.8 },
-					{
-						"time": 0.7333,
-						"angle": -82.56,
-						"curve": [ 0.557, 0.18, 1, 1 ]
-					},
-					{ "time": 1, "angle": -3.39 }
-				],
-				"translate": [
-					{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
-					{ "time": 0.5, "x": 0, "y": 0 },
-					{ "time": 0.6333, "x": 2.18, "y": 0.21 },
-					{ "time": 1, "x": 0, "y": 0 }
-				]
-			},
-			"hip": {
-				"rotate": [
-					{ "time": 0, "angle": 0, "curve": "stepped" },
-					{ "time": 1, "angle": 0 }
-				],
-				"translate": [
-					{ "time": 0, "x": 0, "y": -4.16 },
-					{
-						"time": 0.1333,
-						"x": 0,
-						"y": -7.05,
-						"curve": [ 0.359, 0.47, 0.646, 0.74 ]
-					},
-					{ "time": 0.3666, "x": 0, "y": 6.78 },
-					{ "time": 0.5, "x": 0, "y": -6.13 },
-					{
-						"time": 0.6333,
-						"x": 0,
-						"y": -7.05,
-						"curve": [ 0.359, 0.47, 0.646, 0.74 ]
-					},
-					{ "time": 0.8666, "x": 0, "y": 6.78 },
-					{ "time": 1, "x": 0, "y": -4.16 }
-				]
-			},
-			"neck": {
-				"rotate": [
-					{ "time": 0, "angle": 3.6 },
-					{ "time": 0.1333, "angle": 17.49 },
-					{ "time": 0.2333, "angle": 6.1 },
-					{ "time": 0.3666, "angle": 3.45 },
-					{ "time": 0.5, "angle": 5.17 },
-					{ "time": 0.6333, "angle": 18.36 },
-					{ "time": 0.7333, "angle": 6.09 },
-					{ "time": 0.8666, "angle": 2.28 },
-					{ "time": 1, "angle": 3.6 }
-				]
-			},
-			"head": {
-				"rotate": [
-					{
-						"time": 0,
-						"angle": 3.6,
-						"curve": [ 0, 0, 0.704, 1.17 ]
-					},
-					{ "time": 0.1333, "angle": -0.2 },
-					{ "time": 0.2333, "angle": 6.1 },
-					{ "time": 0.3666, "angle": 3.45 },
-					{
-						"time": 0.5,
-						"angle": 5.17,
-						"curve": [ 0, 0, 0.704, 1.61 ]
-					},
-					{ "time": 0.6666, "angle": 1.1 },
-					{ "time": 0.7333, "angle": 6.09 },
-					{ "time": 0.8666, "angle": 2.28 },
-					{ "time": 1, "angle": 3.6 }
-				]
-			}
-		},
-		"slots": {
-			"eyes": {
-				"attachment": [
-					{ "time": 0.7, "name": "eyes closed" },
-					{ "time": 0.8, "name": null }
-				]
-			}
-		}
-	}
-}
-}

BIN
spine-love/data/head.png


BIN
spine-love/data/images/eye_indifferent.png


BIN
spine-love/data/images/eye_surprised.png


+ 0 - 0
spine-love/data/eyes-closed.png → spine-love/data/images/eyes-closed.png


+ 0 - 0
spine-love/data/eyes.png → spine-love/data/images/eyes.png


BIN
spine-love/data/images/front_bracer.png


BIN
spine-love/data/images/front_fist_closed.png


BIN
spine-love/data/images/front_fist_open.png


BIN
spine-love/data/images/front_foot.png


BIN
spine-love/data/images/front_foot_bend1.png


BIN
spine-love/data/images/front_foot_bend2.png


BIN
spine-love/data/images/front_shin.png


BIN
spine-love/data/images/front_thigh.png


BIN
spine-love/data/images/front_upper_arm.png


BIN
spine-love/data/images/goggles.png


BIN
spine-love/data/images/gun.png


BIN
spine-love/data/images/head.png


+ 0 - 0
spine-love/data/left-ankle.png → spine-love/data/images/left-ankle.png


+ 0 - 0
spine-love/data/left-arm.png → spine-love/data/images/left-arm.png


+ 0 - 0
spine-love/data/left-foot.png → spine-love/data/images/left-foot.png


+ 0 - 0
spine-love/data/left-hand.png → spine-love/data/images/left-hand.png


+ 0 - 0
spine-love/data/left-lower-leg.png → spine-love/data/images/left-lower-leg.png


+ 0 - 0
spine-love/data/left-pant-bottom.png → spine-love/data/images/left-pant-bottom.png


+ 0 - 0
spine-love/data/left-shoulder.png → spine-love/data/images/left-shoulder.png


+ 0 - 0
spine-love/data/left-upper-leg.png → spine-love/data/images/left-upper-leg.png


BIN
spine-love/data/images/mouth_grind.png


BIN
spine-love/data/images/mouth_oooo.png


BIN
spine-love/data/images/mouth_smile.png


BIN
spine-love/data/images/muzzle.png


BIN
spine-love/data/images/neck.png


+ 0 - 0
spine-love/data/pelvis.png → spine-love/data/images/pelvis.png


BIN
spine-love/data/images/rear_bracer.png


BIN
spine-love/data/images/rear_foot.png


BIN
spine-love/data/images/rear_foot_bend1.png


BIN
spine-love/data/images/rear_foot_bend2.png


BIN
spine-love/data/images/rear_shin.png


BIN
spine-love/data/images/rear_thigh.png


BIN
spine-love/data/images/rear_upper_arm.png


+ 0 - 0
spine-love/data/right-ankle.png → spine-love/data/images/right-ankle.png


+ 0 - 0
spine-love/data/right-arm.png → spine-love/data/images/right-arm.png


+ 0 - 0
spine-love/data/right-foot-idle.png → spine-love/data/images/right-foot-idle.png


+ 0 - 0
spine-love/data/right-foot.png → spine-love/data/images/right-foot.png


+ 0 - 0
spine-love/data/right-hand.png → spine-love/data/images/right-hand.png


+ 0 - 0
spine-love/data/right-lower-leg.png → spine-love/data/images/right-lower-leg.png


+ 0 - 0
spine-love/data/right-pant-bottom.png → spine-love/data/images/right-pant-bottom.png


+ 0 - 0
spine-love/data/right-shoulder.png → spine-love/data/images/right-shoulder.png


+ 0 - 0
spine-love/data/right-upper-leg.png → spine-love/data/images/right-upper-leg.png


BIN
spine-love/data/images/torso.png


BIN
spine-love/data/neck.png


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 1002
spine-love/data/spineboy.json


BIN
spine-love/data/torso.png


+ 8 - 7
spine-love/main.lua

@@ -31,16 +31,16 @@
 local spine = require "spine-love.spine"
 
 local json = spine.SkeletonJson.new()
-json.scale = 1
+json.scale = 0.6
 local skeletonData = json:readSkeletonDataFile("data/spineboy.json")
 
 local skeleton = spine.Skeleton.new(skeletonData)
 function skeleton:createImage (attachment)
 	-- Customize where images are loaded.
-	return love.graphics.newImage("data/" .. attachment.name .. ".png")
+	return love.graphics.newImage("data/images/" .. attachment.name .. ".png")
 end
 skeleton.x = love.graphics.getWidth() / 2
-skeleton.y = love.graphics.getHeight() / 2 + 150
+skeleton.y = love.graphics.getHeight() / 2 + 250
 skeleton.flipX = false
 skeleton.flipY = false
 skeleton.debugBones = true -- Omit or set to false to not draw debug lines on top of the images.
@@ -50,13 +50,14 @@ skeleton:setToSetupPose()
 -- AnimationStateData defines crossfade durations between animations.
 local stateData = spine.AnimationStateData.new(skeletonData)
 stateData:setMix("walk", "jump", 0.2)
-stateData:setMix("jump", "walk", 0.4)
+stateData:setMix("jump", "run", 0.2)
 
 -- AnimationState has a queue of animations and can apply them with crossfading.
 local state = spine.AnimationState.new(stateData)
-state:setAnimationByName(0, "drawOrder")
-state:addAnimationByName(0, "jump", false, 0)
-state:addAnimationByName(0, "walk", true, 0)
+-- state:setAnimationByName(0, "test")
+state:setAnimationByName(0, "walk", true)
+state:addAnimationByName(0, "jump", true, 3)
+state:addAnimationByName(0, "run", true, 0)
 
 state.onStart = function (trackIndex)
 	print(trackIndex.." start: "..state:getCurrent(trackIndex).animation.name)

+ 4 - 0
spine-love/spine-love/spine.lua

@@ -35,11 +35,15 @@ spine.SkeletonJson = require "spine-lua.SkeletonJson"
 spine.SkeletonData = require "spine-lua.SkeletonData"
 spine.BoneData = require "spine-lua.BoneData"
 spine.SlotData = require "spine-lua.SlotData"
+spine.IkConstraintData = require "spine-lua.IkConstraintData"
 spine.Skin = require "spine-lua.Skin"
 spine.RegionAttachment = require "spine-lua.RegionAttachment"
+spine.MeshAttachment = require "spine-lua.MeshAttachment"
+spine.SkinnedMeshAttachment = require "spine-lua.SkinnedMeshAttachment"
 spine.Skeleton = require "spine-lua.Skeleton"
 spine.Bone = require "spine-lua.Bone"
 spine.Slot = require "spine-lua.Slot"
+spine.IkConstraint = require "spine-lua.IkConstraint"
 spine.AttachmentType = require "spine-lua.AttachmentType"
 spine.AttachmentLoader = require "spine-lua.AttachmentLoader"
 spine.Animation = require "spine-lua.Animation"

+ 151 - 78
spine-lua/Animation.lua

@@ -109,72 +109,89 @@ end
 Animation.CurveTimeline = {}
 function Animation.CurveTimeline.new ()
 	local LINEAR = 0
-	local STEPPED = -1
+	local STEPPED = 1
+	local BEZIER = 2;
 	local BEZIER_SEGMENTS = 10
+	local BEZIER_SIZE = BEZIER_SEGMENTS * 2 - 1
 
 	local self = {
-		curves = {}
+		curves = {} -- type, x, y, ...
 	}
 
 	function self:setLinear (frameIndex)
-		self.curves[frameIndex * 6] = LINEAR
+		self.curves[frameIndex * BEZIER_SIZE] = LINEAR
 	end
 
 	function self:setStepped (frameIndex)
-		self.curves[frameIndex * 6] = STEPPED
+		self.curves[frameIndex * BEZIER_SIZE] = STEPPED
 	end
 
 	function self:setCurve (frameIndex, cx1, cy1, cx2, cy2)
-		local subdiv_step = 1 / BEZIER_SEGMENTS
-		local subdiv_step2 = subdiv_step * subdiv_step
-		local subdiv_step3 = subdiv_step2 * subdiv_step
-		local pre1 = 3 * subdiv_step
-		local pre2 = 3 * subdiv_step2
-		local pre4 = 6 * subdiv_step2
-		local pre5 = 6 * subdiv_step3
+		local subdiv1 = 1 / BEZIER_SEGMENTS
+		local subdiv2 = subdiv1 * subdiv1
+		local subdiv3 = subdiv2 * subdiv1;
+		local pre1 = 3 * subdiv1
+		local pre2 = 3 * subdiv2
+		local pre4 = 6 * subdiv2
+		local pre5 = 6 * subdiv3
 		local tmp1x = -cx1 * 2 + cx2
 		local tmp1y = -cy1 * 2 + cy2
 		local tmp2x = (cx1 - cx2) * 3 + 1
 		local tmp2y = (cy1 - cy2) * 3 + 1
-		local i = frameIndex * 6
+		local dfx = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv3
+		local dfy = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv3
+		local ddfx = tmp1x * pre4 + tmp2x * pre5
+		local ddfy = tmp1y * pre4 + tmp2y * pre5;
+		local dddfx = tmp2x * pre5
+		local dddfy = tmp2y * pre5
+
+		local i = frameIndex * BEZIER_SIZE
 		local curves = self.curves
-		curves[i] = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv_step3
-		curves[i + 1] = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv_step3
-		curves[i + 2] = tmp1x * pre4 + tmp2x * pre5
-		curves[i + 3] = tmp1y * pre4 + tmp2y * pre5
-		curves[i + 4] = tmp2x * pre5
-		curves[i + 5] = tmp2y * pre5
-	end
+		curves[i] = BEZIER
+		i = i + 1
 
-	function self:getCurvePercent (frameIndex, percent)
-		local curveIndex = frameIndex * 6
-		local curves = self.curves
-		local dfx = curves[curveIndex]
-		if not dfx then return percent end -- linear
-		if dfx == STEPPED then return 0 end
-		local dfy = curves[curveIndex + 1]
-		local ddfx = curves[curveIndex + 2]
-		local ddfy = curves[curveIndex + 3]
-		local dddfx = curves[curveIndex + 4]
-		local dddfy = curves[curveIndex + 5]
 		local x = dfx
 		local y = dfy
-		local i = BEZIER_SEGMENTS - 2
-		while true do
-			if x >= percent then
-				local lastX = x - dfx
-				local lastY = y - dfy
-				return lastY + (y - lastY) * (percent - lastX) / (x - lastX)
-			end
-			if i == 0 then break end
-			i = i - 1
+		local n = i + BEZIER_SIZE - 1
+		while i < n do
+			curves[i] = x
+			curves[i + 1] = y
 			dfx = dfx + ddfx
 			dfy = dfy + ddfy
 			ddfx = ddfx + dddfx
 			ddfy = ddfy + dddfy
 			x = x + dfx
 			y = y + dfy
+			i = i + 2
+		end
+	end
+
+	function self:getCurvePercent (frameIndex, percent)
+		local curves = self.curves
+		local i = frameIndex * BEZIER_SIZE
+		local type = curves[i]
+		if type == LINEAR then return percent end
+		if type == STEPPED then return 0 end
+		i = i + 1
+		local x
+		local n = i + BEZIER_SIZE - 1
+		local start = i
+		while i < n do
+			x = curves[i]
+			if x >= percent then
+				local prevX, prevY
+				if i == start then
+					prevX = 0
+					prevY = 0
+				else
+					prevX = curves[i - 2]
+					prevY = curves[i - 1]
+				end
+				return prevY + (curves[i + 1] - prevY) * (percent - prevX) / (x - prevX)
+			end
+			i = i + 2
 		end
+		local y = curves[i - 1]
 		return y + (1 - y) * (percent - x) / (1 - x) -- Last point is 1,1.
 	end
 
@@ -183,7 +200,7 @@ end
 
 Animation.RotateTimeline = {}
 function Animation.RotateTimeline.new ()
-	local LAST_FRAME_TIME = -2
+	local PREV_FRAME_TIME = -2
 	local FRAME_VALUE = 1
 
 	local self = Animation.CurveTimeline.new()
@@ -224,20 +241,20 @@ function Animation.RotateTimeline.new ()
 
 		-- Interpolate between the last frame and the current frame.
 		local frameIndex = binarySearch(frames, time, 2)
-		local lastFrameValue = frames[frameIndex - 1]
+		local prevFrameValue = frames[frameIndex - 1]
 		local frameTime = frames[frameIndex]
-		local percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime)
+		local percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime)
 		if percent < 0 then percent = 0 elseif percent > 1 then percent = 1 end
 		percent = self:getCurvePercent(frameIndex / 2 - 1, percent)
 
-		local amount = frames[frameIndex + FRAME_VALUE] - lastFrameValue
+		local amount = frames[frameIndex + FRAME_VALUE] - prevFrameValue
 		while amount > 180 do
 			amount = amount - 360
 		end
 		while amount < -180 do
 			amount = amount + 360
 		end
-		amount = bone.data.rotation + (lastFrameValue + amount * percent) - bone.rotation
+		amount = bone.data.rotation + (prevFrameValue + amount * percent) - bone.rotation
 		while amount > 180 do
 			amount = amount - 360
 		end
@@ -252,7 +269,7 @@ end
 
 Animation.TranslateTimeline = {}
 function Animation.TranslateTimeline.new ()
-	local LAST_FRAME_TIME = -3
+	local PREV_FRAME_TIME = -3
 	local FRAME_X = 1
 	local FRAME_Y = 2
 
@@ -289,15 +306,15 @@ function Animation.TranslateTimeline.new ()
 
 		-- Interpolate between the last frame and the current frame.
 		local frameIndex = binarySearch(frames, time, 3)
-		local lastFrameX = frames[frameIndex - 2]
-		local lastFrameY = frames[frameIndex - 1]
+		local prevFrameX = frames[frameIndex - 2]
+		local prevFrameY = frames[frameIndex - 1]
 		local frameTime = frames[frameIndex]
-		local percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime)
+		local percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime)
 		if percent < 0 then percent = 0 elseif percent > 1 then percent = 1 end
 		percent = self:getCurvePercent(frameIndex / 3 - 1, percent)
 
-		bone.x = bone.x + (bone.data.x + lastFrameX + (frames[frameIndex + FRAME_X] - lastFrameX) * percent - bone.x) * alpha
-		bone.y = bone.y + (bone.data.y + lastFrameY + (frames[frameIndex + FRAME_Y] - lastFrameY) * percent - bone.y) * alpha
+		bone.x = bone.x + (bone.data.x + prevFrameX + (frames[frameIndex + FRAME_X] - prevFrameX) * percent - bone.x) * alpha
+		bone.y = bone.y + (bone.data.y + prevFrameY + (frames[frameIndex + FRAME_Y] - prevFrameY) * percent - bone.y) * alpha
 	end
 
 	return self
@@ -305,7 +322,7 @@ end
 
 Animation.ScaleTimeline = {}
 function Animation.ScaleTimeline.new ()
-	local LAST_FRAME_TIME = -3
+	local PREV_FRAME_TIME = -3
 	local FRAME_X = 1
 	local FRAME_Y = 2
 
@@ -325,15 +342,15 @@ function Animation.ScaleTimeline.new ()
 
 		-- Interpolate between the last frame and the current frame.
 		local frameIndex = binarySearch(frames, time, 3)
-		local lastFrameX = frames[frameIndex - 2]
-		local lastFrameY = frames[frameIndex - 1]
+		local prevFrameX = frames[frameIndex - 2]
+		local prevFrameY = frames[frameIndex - 1]
 		local frameTime = frames[frameIndex]
-		local percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime)
+		local percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime)
 		if percent < 0 then percent = 0 elseif percent > 1 then percent = 1 end
 		percent = self:getCurvePercent(frameIndex / 3 - 1, percent)
 
-		bone.scaleX = bone.scaleX + (bone.data.scaleX * (lastFrameX + (frames[frameIndex + FRAME_X] - lastFrameX) * percent) - bone.scaleX) * alpha
-		bone.scaleY = bone.scaleY + (bone.data.scaleY * (lastFrameY + (frames[frameIndex + FRAME_Y] - lastFrameY) * percent) - bone.scaleY) * alpha
+		bone.scaleX = bone.scaleX + (bone.data.scaleX * (prevFrameX + (frames[frameIndex + FRAME_X] - prevFrameX) * percent) - bone.scaleX) * alpha
+		bone.scaleY = bone.scaleY + (bone.data.scaleY * (prevFrameY + (frames[frameIndex + FRAME_Y] - prevFrameY) * percent) - bone.scaleY) * alpha
 	end
 
 	return self
@@ -341,7 +358,7 @@ end
 
 Animation.ColorTimeline = {}
 function Animation.ColorTimeline.new ()
-	local LAST_FRAME_TIME = -5
+	local PREV_FRAME_TIME = -5
 	local FRAME_R = 1
 	local FRAME_G = 2
 	local FRAME_B = 3
@@ -381,19 +398,19 @@ function Animation.ColorTimeline.new ()
 		else
 			-- Interpolate between the last frame and the current frame.
 			local frameIndex = binarySearch(frames, time, 5)
-			local lastFrameR = frames[frameIndex - 4]
-			local lastFrameG = frames[frameIndex - 3]
-			local lastFrameB = frames[frameIndex - 2]
-			local lastFrameA = frames[frameIndex - 1]
+			local prevFrameR = frames[frameIndex - 4]
+			local prevFrameG = frames[frameIndex - 3]
+			local prevFrameB = frames[frameIndex - 2]
+			local prevFrameA = frames[frameIndex - 1]
 			local frameTime = frames[frameIndex]
-			local percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime)
+			local percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime)
 			if percent < 0 then percent = 0 elseif percent > 255 then percent = 255 end
 			percent = self:getCurvePercent(frameIndex / 5 - 1, percent)
 
-			r = lastFrameR + (frames[frameIndex + FRAME_R] - lastFrameR) * percent
-			g = lastFrameG + (frames[frameIndex + FRAME_G] - lastFrameG) * percent
-			b = lastFrameB + (frames[frameIndex + FRAME_B] - lastFrameB) * percent
-			a = lastFrameA + (frames[frameIndex + FRAME_A] - lastFrameA) * percent
+			r = prevFrameR + (frames[frameIndex + FRAME_R] - prevFrameR) * percent
+			g = prevFrameG + (frames[frameIndex + FRAME_G] - prevFrameG) * percent
+			b = prevFrameB + (frames[frameIndex + FRAME_B] - prevFrameB) * percent
+			a = prevFrameA + (frames[frameIndex + FRAME_A] - prevFrameA) * percent
 		end
 		local slot = skeleton.slots[self.slotIndex]
 		if alpha < 1 then
@@ -409,7 +426,7 @@ end
 Animation.AttachmentTimeline = {}
 function Animation.AttachmentTimeline.new ()
 	local self = {
-		frames = {},
+		frames = {}, -- time, ...
 		attachmentNames = {},
 		slotName = nil
 	}
@@ -429,14 +446,20 @@ function Animation.AttachmentTimeline.new ()
 
 	function self:apply (skeleton, lastTime, time, firedEvents, alpha)
 		local frames = self.frames
-		if time < frames[0] then return end -- Time is before first frame.
+		if time < frames[0] then
+			if lastTime > time then self:apply(skeleton, lastTime, 999999, nil, 0) end
+			return
+		elseif lastTime > time then
+			lastTime = -1
+		end
 
 		local frameIndex
-		if time >= frames[#frames] then -- Time is after last frame.
+		if time >= frames[#frames] then
 			frameIndex = #frames
 		else
 			frameIndex = binarySearch1(frames, time) - 1
 		end
+		if frames[frameIndex] < lastTime then return end
 
 		local attachmentName = self.attachmentNames[frameIndex]
 		local slot = skeleton.slotsByName[self.slotName]
@@ -561,7 +584,7 @@ end
 Animation.FfdTimeline = {}
 function Animation.FfdTimeline.new ()
 	local self = Animation.CurveTimeline.new()
-	self.frames = {}
+	self.frames = {} -- time, ...
 	self.frameVertices = {}
 	self.slotIndex = -1
 
@@ -583,17 +606,13 @@ function Animation.FfdTimeline.new ()
 		if slot.attachment ~= attachment then return end
 
 		local frames = self.frames
-		if time < frames[0] then -- Time is before first frame.
-			slot.attachmentVerticesCount = 0
-			return
-		end 
-		
+		if time < frames[0] then return end -- Time is before first frame.
+
 		local frameVertices = self.frameVertices
 		local vertexCount = #frameVertices[0]
 		local vertices = slot.attachmentVertices
 		if #vertices < vertexCount then
 			vertices = {}
-			vertices[vertexCount] = 0
 			slot.attachmentVertices = vertices
 		elseif #vertices < vertexCount then
 			alpha = 1 -- Don't mix from uninitialized slot vertices.
@@ -601,7 +620,7 @@ function Animation.FfdTimeline.new ()
 		slot.attachmentVerticesCount = vertexCount
 
 		if time >= frames[#frames] then -- Time is after last frame.
-			local lastVertices = frameVertices[#frames.Length]
+			local lastVertices = frameVertices[#frames]
 			if alpha < 1 then
 				for i = 0, vertexCount do
 					local vertex = vertices[i]
@@ -642,6 +661,60 @@ function Animation.FfdTimeline.new ()
 	return self
 end
 
+Animation.IkConstraintTimeline = {}
+function Animation.IkConstraintTimeline.new ()
+	local PREV_FRAME_TIME = -3
+	local PREV_FRAME_MIX = -2
+	local PREV_FRAME_BEND_DIRECTION = -1
+	local FRAME_MIX = 1
+
+	local self = Animation.CurveTimeline.new()
+	self.frames = {} -- time, mix, bendDirection, ...
+	self.ikConstraintIndex = -1
+
+	function self:getDuration ()
+		return self.frames[#self.frames - 2]
+	end
+
+	function self:getFrameCount ()
+		return (#self.frames + 1) / 3
+	end
+
+	function self:setFrame (frameIndex, time, mix, bendDirection)
+		frameIndex = frameIndex * 3
+		self.frames[frameIndex] = time
+		self.frames[frameIndex + 1] = mix
+		self.frames[frameIndex + 2] = bendDirection
+	end
+
+	function self:apply (skeleton, lastTime, time, firedEvents, alpha)
+		local frames = self.frames
+		if time < frames[0] then return end -- Time is before first frame.
+
+		local ikConstraint = skeleton.ikConstraints[ikConstraintIndex]
+
+		if time >= frames[#frames - 2] then -- Time is after last frame.
+			ikConstraint.mix = ikConstraint.mix + (frames[#frames - 1] - ikConstraint.mix) * alpha
+			ikConstraint.bendDirection = frames[#frames]
+			return
+		end
+
+		-- Interpolate between the previous frame and the current frame.
+		local frameIndex = binarySearch(frames, time, 3);
+		local prevFrameMix = frames[frameIndex + PREV_FRAME_MIX]
+		local frameTime = frames[frameIndex]
+		local percent = 1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime)
+		if percent < 0 then percent = 0 elseif percent > 1 then percent = 1 end
+		percent = self:getCurvePercent(frameIndex / 3 - 1, percent)
+
+		local mix = prevFrameMix + (frames[frameIndex + FRAME_MIX] - prevFrameMix) * percent
+		ikConstraint.mix = ikConstraint.mix + (mix - ikConstraint.mix) * alpha
+		ikConstraint.bendDirection = frames[frameIndex + PREV_FRAME_BEND_DIRECTION]
+	end
+
+	return self
+end
+
 Animation.FlipXTimeline = {}
 function Animation.FlipXTimeline.new ()
 	local self = {
@@ -666,7 +739,7 @@ function Animation.FlipXTimeline.new ()
 	function self:apply (skeleton, lastTime, time, firedEvents, alpha)
 		local frames = self.frames
 		if time < frames[0] then
-			if lastTime > time then self:apply(skeleton, lastTime, 999999, null, 0) end
+			if lastTime > time then self:apply(skeleton, lastTime, 999999, nil, 0) end
 			return
 		elseif lastTime > time then
 			lastTime = -1

+ 28 - 4
spine-lua/Bone.lua

@@ -39,7 +39,7 @@ function Bone.new (data, skeleton, parent)
 		skeleton = skeleton,
 		parent = parent,
 		x = 0, y = 0,
-		rotation = 0,
+		rotation = 0, rotationIK = 0,
 		scaleX = 1, scaleY = 1,
 		flipX = false, flipY = false,
 		m00 = 0, m01 = 0, worldX = 0, -- a b x
@@ -62,9 +62,9 @@ function Bone.new (data, skeleton, parent)
 				 self.worldScaleY = self.scaleY
 			end
 			if (self.data.inheritRotation) then
-				 self.worldRotation = parent.worldRotation + self.rotation
+				 self.worldRotation = parent.worldRotation + self.rotationIK
 			else
-				 self.worldRotation = self.rotation
+				 self.worldRotation = self.rotationIK
 			end
 			self.worldFlipX = parent.worldFlipX ~= self.flipX
 			self.worldFlipY = parent.worldFlipY ~= self.flipY
@@ -82,7 +82,7 @@ function Bone.new (data, skeleton, parent)
 			end
 			self.worldScaleX = self.scaleX
 			self.worldScaleY = self.scaleY
-			self.worldRotation = self.rotation
+			self.worldRotation = self.rotationIK
 			self.worldFlipX = skeletonFlipX ~= self.flipX
 			self.worldFlipY = skeletonFlipY ~= self.flipY
 		end
@@ -110,12 +110,36 @@ function Bone.new (data, skeleton, parent)
 		self.x = data.x
 		self.y = data.y
 		self.rotation = data.rotation
+		self.rotationIK = self.rotation
 		self.scaleX = data.scaleX
 		self.scaleY = data.scaleY
 		self.flipX = data.flipX
 		self.flipY = data.flipY
 	end
 
+	function self:worldToLocal (worldCoords)
+		local dx = worldCoords[1] - self.worldX
+		local dy = worldCoords[2] - self.worldY
+		local m00 = self.m00
+		local m10 = self.m10
+		local m01 = self.m01
+		local m11 = self.m11
+		if self.worldFlipX ~= self.worldFlipY then
+			m00 = -m00
+			m11 = -m11
+		end
+		local invDet = 1 / (m00 * m11 - m01 * m10)
+		worldCoords[1] = dx * m00 * invDet - dy * m01 * invDet
+		worldCoords[2] = dy * m11 * invDet - dx * m10 * invDet
+	end
+
+	function self:localToWorld (localCoords)
+		local localX = localCoords[1]
+		local localY = localCoords[2]
+		localCoords[1] = localX * self.m00 + localY * self.m01 + self.worldX
+		localCoords[2] = localX * self.m10 + localY * self.m11 + self.worldY
+	end
+
 	self:setToSetupPose()
 	return self
 end

+ 150 - 0
spine-lua/IkConstraint.lua

@@ -0,0 +1,150 @@
+-------------------------------------------------------------------------------
+-- Spine Runtimes Software License
+-- Version 2.1
+-- 
+-- Copyright (c) 2013, Esoteric Software
+-- All rights reserved.
+-- 
+-- You are granted a perpetual, non-exclusive, non-sublicensable and
+-- non-transferable license to install, execute and perform the Spine Runtimes
+-- Software (the "Software") solely for internal use. Without the written
+-- permission of Esoteric Software (typically granted by licensing Spine), you
+-- may not (a) modify, translate, adapt or otherwise create derivative works,
+-- improvements of the Software or develop new applications using the Software
+-- or (b) remove, delete, alter or obscure any trademarks or any copyright,
+-- trademark, patent or other intellectual property or proprietary rights
+-- notices on or in the Software, including any copy thereof. Redistributions
+-- in binary or source form must include this license and terms.
+-- 
+-- THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
+-- IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+-- MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+-- EVENT SHALL ESOTERIC SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+-- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+-- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+-- OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+-- WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+-- OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+-- ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local IkConstraint = {}
+function IkConstraint.new (data, skeleton)
+	if not data then error("data cannot be nil", 2) end
+	if not skeleton then error("skeleton cannot be nil", 2) end
+
+	local self = {
+		data = data,
+		skeleton = skeleton,
+		bones = {},
+		target = nil,
+		bendDirection = data.bendDirection,
+		mix = data.mix
+	}
+
+	function self:apply ()
+		local target = self.target
+		local bones = self.bones
+		local boneCount = #bones
+		if boneCount == 1 then
+			IkConstraint.apply1(bones[1], target.worldX, target.worldY, self.mix)
+		elseif boneCount == 2 then
+			IkConstraint.apply2(bones[1], bones[2], target.worldX, target.worldY, self.bendDirection, self.mix)
+		end
+	end
+
+	for i,boneData in ipairs(data.bones) do
+		table.insert(self.bones, skeleton:findBone(boneData.name))
+	end
+	self.target = skeleton:findBone(data.target.name)
+
+	return self
+end
+
+local radDeg = 180 / math.pi
+local degRad = math.pi / 180
+
+function IkConstraint.apply1 (bone, targetX, targetY, alpha)
+	local parentRotation
+	if not bone.data.inheritRotation or not bone.parent then
+		parentRotation = 0
+	else
+		parentRotation = bone.parent.worldRotation
+	end
+	local rotation = bone.rotation
+	local rotationIK = math.atan2(targetY - bone.worldY, targetX - bone.worldX) * radDeg - parentRotation
+	bone.rotationIK = rotation + (rotationIK - rotation) * alpha
+end
+
+local temp = {}
+
+function IkConstraint.apply2 (parent, child, targetX, targetY, bendDirection, alpha)
+	local childRotation = child.rotation
+	local parentRotation = parent.rotation
+	if not alpha then
+		child.rotationIK = childRotation
+		parent.rotationIK = parentRotation
+		return
+	end
+	local positionX, positionY
+	local tempPosition = temp
+	local parentParent = parent.parent
+	if parentParent then
+		tempPosition[1] = targetX
+		tempPosition[2] = targetY
+		parentParent:worldToLocal(tempPosition)
+		targetX = (tempPosition[1] - parent.x) * parentParent.worldScaleX
+		targetY = (tempPosition[2] - parent.y) * parentParent.worldScaleY
+	else
+		targetX = targetX - parent.x
+		targetY = targetY - parent.y
+	end
+	if child.parent == parent then
+		positionX = child.x
+		positionY = child.y
+	else
+		tempPosition[1] = child.x
+		tempPosition[2] = child.y
+		child.parent:localToWorld(tempPosition)
+		parent:worldToLocal(tempPosition)
+		positionX = tempPosition[1]
+		positionY = tempPosition[2]
+	end
+	local childX = positionX * parent.worldScaleX
+	local childY = positionY * parent.worldScaleY
+	local offset = math.atan2(childY, childX)
+	local len1 = math.sqrt(childX * childX + childY * childY)
+	local len2 = child.data.length * child.worldScaleX
+	-- Based on code by Ryan Juckett with permission: Copyright (c) 2008-2009 Ryan Juckett, http://www.ryanjuckett.com/
+	local cosDenom = 2 * len1 * len2
+	if cosDenom < 0.0001 then
+		child.rotationIK = childRotation + (math.atan2(targetY, targetX) * radDeg - parentRotation - childRotation) * alpha
+		return
+	end
+	local cos = (targetX * targetX + targetY * targetY - len1 * len1 - len2 * len2) / cosDenom
+	if cos < -1 then
+		cos = -1
+	elseif cos > 1 then
+		cos = 1
+	end
+	local childAngle = math.acos(cos) * bendDirection
+	local adjacent = len1 + len2 * cos
+	local opposite = len2 * math.sin(childAngle)
+	local parentAngle = math.atan2(targetY * adjacent - targetX * opposite, targetX * adjacent + targetY * opposite)
+	local rotation = (parentAngle - offset) * radDeg - parentRotation
+	if rotation > 180 then
+		rotation = rotation - 360
+	elseif rotation < -180 then
+		rotation = rotation + 360
+	end
+	parent.rotationIK = parentRotation + rotation * alpha
+	rotation = (childAngle + offset) * radDeg - childRotation
+	if rotation > 180 then
+		rotation = rotation - 360
+	elseif rotation < -180 then
+		rotation = rotation + 360
+	end
+	child.rotationIK = childRotation + (rotation + parent.worldRotation - child.parent.worldRotation) * alpha
+end
+
+return IkConstraint

+ 45 - 0
spine-lua/IkConstraintData.lua

@@ -0,0 +1,45 @@
+-------------------------------------------------------------------------------
+-- Spine Runtimes Software License
+-- Version 2.1
+-- 
+-- Copyright (c) 2013, Esoteric Software
+-- All rights reserved.
+-- 
+-- You are granted a perpetual, non-exclusive, non-sublicensable and
+-- non-transferable license to install, execute and perform the Spine Runtimes
+-- Software (the "Software") solely for internal use. Without the written
+-- permission of Esoteric Software (typically granted by licensing Spine), you
+-- may not (a) modify, translate, adapt or otherwise create derivative works,
+-- improvements of the Software or develop new applications using the Software
+-- or (b) remove, delete, alter or obscure any trademarks or any copyright,
+-- trademark, patent or other intellectual property or proprietary rights
+-- notices on or in the Software, including any copy thereof. Redistributions
+-- in binary or source form must include this license and terms.
+-- 
+-- THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
+-- IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+-- MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+-- EVENT SHALL ESOTERIC SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+-- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+-- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+-- OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+-- WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+-- OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+-- ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------
+
+local IkConstraintData = {}
+function IkConstraintData.new (name)
+	if not name then error("name cannot be nil", 2) end
+
+	local self = {
+		name = name,
+		bones = {},
+		target = nil,
+		bendDirection = 1,
+		mix = 1
+	}
+
+	return self
+end
+return IkConstraintData

+ 70 - 3
spine-lua/Skeleton.lua

@@ -30,6 +30,7 @@
 
 local Bone = require "spine-lua.Bone"
 local Slot = require "spine-lua.Slot"
+local IkConstraint = require "spine-lua.IkConstraint"
 local AttachmentLoader = require "spine-lua.AttachmentLoader"
 
 local Skeleton = {}
@@ -42,16 +43,71 @@ function Skeleton.new (skeletonData)
 		slots = {},
 		slotsByName = {},
 		drawOrder = {},
+		ikConstraints = {},
 		r = 1, g = 1, b = 1, a = 1,
-		x = 0, y = 0,
 		skin = nil,
 		flipX = false, flipY = false,
-		time = 0
+		time = 0,
+		x = 0, y = 0
 	}
 
+	-- Caches information about bones and IK constraints. Must be called if bones or IK constraints are added or removed.
+	function self:updateCache ()
+		self.boneCache = {}
+		local boneCache = self.boneCache
+		local ikConstraints = self.ikConstraints
+		local ikConstraintsCount = #ikConstraints
+
+		local arrayCount = ikConstraintsCount + 1
+		while #boneCache < arrayCount do
+			table.insert(boneCache, {})
+		end
+
+		local nonIkBones = boneCache[1]
+
+		for i,bone in ipairs(self.bones) do
+			local current = bone
+			local continueOuter
+			repeat
+				for ii,ikConstraint in ipairs(ikConstraints) do
+					local parent = ikConstraint.bones[0]
+					local child = ikConstraint.bones[#ikConstraint.bones - 1]
+					while true do
+						if current == child then
+							table.insert(boneCache[ii], bone)
+							table.insert(boneCache[ii + 1], bone)
+							ii = ikConstraintsCount
+							continueOuter = true
+							break
+						end
+						if child == parent then break end
+						child = child.parent
+					end
+				end
+				if continueOuter then break end
+				current = current.parent
+			until not current
+			table.insert(nonIkBones, bone)
+		end
+	end
+
+	-- Updates the world transform for each bone and applies IK constraints.
 	function self:updateWorldTransform ()
+		local bones = self.bones
 		for i,bone in ipairs(self.bones) do
-			bone:updateWorldTransform()
+			bone.rotationIK = bone.rotation
+		end
+		local boneCache = self.boneCache
+		local ikConstraints = self.ikConstraints
+		local i = 1
+		local last = #boneCache
+		while true do
+			for ii,bone in ipairs(boneCache[i]) do
+				bone:updateWorldTransform()
+			end
+			if i == last then break end
+			ikConstraints[i]:apply()
+			i = i + 1
 		end
 	end
 
@@ -64,6 +120,11 @@ function Skeleton.new (skeletonData)
 		for i,bone in ipairs(self.bones) do
 			bone:setToSetupPose()
 		end
+
+		for i,ikConstraint in ipairs(self.ikConstraints) do
+			ikConstraint.bendDirection = ikConstraint.data.bendDirection
+			ikConstraint.mix = ikConstraint.data.mix
+		end
 	end
 
 	function self:setSlotsToSetupPose ()
@@ -176,6 +237,12 @@ function Skeleton.new (skeletonData)
 		table.insert(self.drawOrder, slot)
 	end
 
+	for i,ikConstraintData in ipairs(skeletonData.ikConstraints) do
+		table.insert(self.ikConstraints, IkConstraint.new(ikConstraintData, self))
+	end
+	
+	self:updateCache()
+
 	return self
 end
 return Skeleton

+ 11 - 0
spine-lua/SkeletonData.lua

@@ -31,12 +31,15 @@
 local SkeletonData = {}
 function SkeletonData.new ()
 	local self = {
+		version = 0, hash = 0,
+		width = 0, height = 0,
 		bones = {},
 		slots = {},
 		slotNameIndices = {},
 		skins = {},
 		events = {},
 		animations = {},
+		ikConstraints = {},
 		defaultSkin = nil
 	}
 
@@ -93,6 +96,14 @@ function SkeletonData.new ()
 		return nil
 	end
 
+	function self:findIkConstraint (ikConstraintName)
+		if not ikConstraintName then error("ikConstraintName cannot be nil.", 2) end
+		for i,ikConstraint in ipairs(self.ikConstraints) do
+			if ikConstraint.name == ikConstraintName then return ikConstraint end
+		end
+		return nil
+	end
+
 	return self
 end
 return SkeletonData

+ 62 - 3
spine-lua/SkeletonJson.lua

@@ -34,6 +34,8 @@ local SlotData = require "spine-lua.SlotData"
 local Skin = require "spine-lua.Skin"
 local AttachmentLoader = require "spine-lua.AttachmentLoader"
 local Animation = require "spine-lua.Animation"
+local IkConstraintData = require "spine-lua.IkConstraintData"
+local IkConstraint = require "spine-lua.IkConstraint"
 local EventData = require "spine-lua.EventData"
 local Event = require "spine-lua.Event"
 local AttachmentType = require "spine-lua.AttachmentType"
@@ -62,6 +64,15 @@ function SkeletonJson.new (attachmentLoader)
 		local root = spine.utils.readJSON(jsonText)
 		if not root then error("Invalid JSON: " .. jsonText, 2) end
 
+		-- Skeleton.
+		if root["skeleton"] then
+			local skeletonMap = root["skeleton"]
+			skeletonData.hash = skeletonMap["hash"]
+			skeletonData.version = skeletonMap["spine"]
+			skeletonData.width = skeletonMap["width"] or 0
+			skeletonData.height = skeletonMap["height"] or 0
+		end
+
 		-- Bones.
 		for i,boneMap in ipairs(root["bones"]) do
 			local boneName = boneMap["name"]
@@ -101,6 +112,28 @@ function SkeletonJson.new (attachmentLoader)
 			table.insert(skeletonData.bones, boneData)
 		end
 
+		-- IK constraints.
+		if root["ik"] then
+			for i,ikMap in ipairs(root["ik"]) do
+				local ikConstraintData = IkConstraintData.new(ikMap["name"])
+
+				for i,boneName in ipairs(ikMap["bones"]) do
+					local bone = skeletonData:findBone(boneName)
+					if not bone then error("IK bone not found: " .. boneName) end
+					table.insert(ikConstraintData.bones, bone)
+				end
+
+				local targetName = ikMap["target"]
+				ikConstraintData.target = skeletonData:findBone(targetName)
+				if not ikConstraintData.target then error("Target bone not found: " .. targetName) end
+
+				if ikMap["bendPositive"] == false then ikConstraintData.bendDirection = -1 end
+				if ikMap["mix"] ~= nil then ikConstraintData.mix = ikMap["mix"] end
+
+				table.insert(skeletonData.ikConstraints, ikConstraintData)
+			end
+		end
+
 		-- Slots.
 		if root["slots"] then
 			for i,slotMap in ipairs(root["slots"]) do
@@ -391,7 +424,6 @@ function SkeletonJson.new (attachmentLoader)
 
 						local frameIndex = 0
 						for i,valueMap in ipairs(values) do
-							local flip
 							timeline:setFrame(frameIndex, valueMap["time"], valueMap[field] or false)
 							frameIndex = frameIndex + 1
 						end
@@ -405,6 +437,32 @@ function SkeletonJson.new (attachmentLoader)
 			end
 		end
 
+		local ik = map["ik"]
+		if ik then
+			for ikConstraintName,values in pairs(ik) do
+				local ikConstraint = skeletonData.findIkConstraint(ikConstraintName)
+				local timeline = IkConstraintTimeline.new()
+				for i,other in pairs(skeletonData.ikConstraints) do
+					if other == ikConstraint then
+						timeline.ikConstraintIndex = i
+						break
+					end
+				end
+				local frameIndex = 0
+				for i,valueMap in ipairs(values) do
+					local mix = 1
+					if valueMap["mix"] ~= nil then mix = valueMap["mix"] end
+					local bendPositive = 1
+					if valueMap["bendPositive"] == false then bendPositive = -1 end
+					timeline:setFrame(frameIndex, valueMap["time"], mix, bendPositive)
+					readCurve(timeline, frameIndex, valueMap)
+					frameIndex = frameIndex + 1
+				end
+				table.insert(timelines, timeline)
+				duration = math.max(duration, timeline:getDuration())
+			end
+		end
+
 		local ffd = map["ffd"]
 		if ffd then
 			for skinName,slotMap in pairs(ffd) do
@@ -553,8 +611,9 @@ function SkeletonJson.new (attachmentLoader)
 
 	readCurve = function (timeline, frameIndex, valueMap)
 		local curve = valueMap["curve"]
-		if not curve then return end
-		if curve == "stepped" then
+		if not curve then 
+			timeline:setLinear(frameIndex)
+		elseif curve == "stepped" then
 			timeline:setStepped(frameIndex)
 		else
 			timeline:setCurve(frameIndex, curve[1], curve[2], curve[3], curve[4])

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно