Quellcode durchsuchen

Updated to v3.5 (merge dev branch).

# Conflicts:
#	spine-c/src/spine/AnimationState.c
NathanSweet vor 8 Jahren
Ursprung
Commit
36000e3c55
100 geänderte Dateien mit 4061 neuen und 1931 gelöschten Zeilen
  1. 228 221
      examples/alien/export/alien.json
  2. BIN
      examples/alien/export/alien.skel
  3. 4 4
      examples/dragon/export/dragon.json
  4. BIN
      examples/dragon/export/dragon.skel
  5. 34 0
      examples/export/runtimes.sh
  6. 40 33
      examples/goblins/export/goblins-mesh.json
  7. BIN
      examples/goblins/export/goblins-mesh.skel
  8. 9 2
      examples/goblins/export/goblins.json
  9. BIN
      examples/goblins/export/goblins.skel
  10. 31 5
      examples/hero/export/hero-mesh.json
  11. BIN
      examples/hero/export/hero-mesh.skel
  12. 31 5
      examples/hero/export/hero.json
  13. BIN
      examples/hero/export/hero.skel
  14. 15 8
      examples/powerup/export/powerup.json
  15. BIN
      examples/powerup/export/powerup.skel
  16. 23 7
      examples/raptor/export/raptor.json
  17. BIN
      examples/raptor/export/raptor.skel
  18. 39 32
      examples/speedy/export/speedy.json
  19. BIN
      examples/speedy/export/speedy.skel
  20. 14 7
      examples/spineboy-old/export/spineboy-old.json
  21. BIN
      examples/spineboy-old/export/spineboy-old.skel
  22. 54 41
      examples/spineboy/export/spineboy-hover.json
  23. BIN
      examples/spineboy/export/spineboy-hover.skel
  24. 10 6
      examples/spineboy/export/spineboy-mesh.json
  25. BIN
      examples/spineboy/export/spineboy-mesh.skel
  26. 20 13
      examples/spineboy/export/spineboy.json
  27. BIN
      examples/spineboy/export/spineboy.skel
  28. 19 12
      examples/spinosaurus/export/spinosaurus.json
  29. BIN
      examples/spinosaurus/export/spinosaurus.skel
  30. 27 16
      examples/stretchyman/export/stretchyman.json
  31. BIN
      examples/stretchyman/export/stretchyman.skel
  32. 21 6
      examples/tank/export/tank.json
  33. BIN
      examples/tank/export/tank.skel
  34. 36 27
      examples/test/export/test.json
  35. BIN
      examples/test/export/test.skel
  36. 9 1
      examples/vine/export/vine.json
  37. BIN
      examples/vine/export/vine.skel
  38. BIN
      spine-as3/spine-as3-example/lib/spine-as3.swc
  39. 20 13
      spine-as3/spine-as3-example/src/spineboy.json
  40. 132 118
      spine-as3/spine-as3/src/spine/Bone.as
  41. 1 2
      spine-as3/spine-as3/src/spine/BoneData.as
  42. 37 0
      spine-as3/spine-as3/src/spine/Constraint.as
  43. 24 19
      spine-as3/spine-as3/src/spine/IkConstraint.as
  44. 1 0
      spine-as3/spine-as3/src/spine/IkConstraintData.as
  45. 4 0
      spine-as3/spine-as3/src/spine/MathUtils.as
  46. 21 9
      spine-as3/spine-as3/src/spine/PathConstraint.as
  47. 1 0
      spine-as3/spine-as3/src/spine/PathConstraintData.as
  48. 60 49
      spine-as3/spine-as3/src/spine/Pool.as
  49. 5 0
      spine-as3/spine-as3/src/spine/Poolable.as
  50. 103 83
      spine-as3/spine-as3/src/spine/Skeleton.as
  51. 8 1
      spine-as3/spine-as3/src/spine/SkeletonBounds.as
  52. 3 0
      spine-as3/spine-as3/src/spine/SkeletonData.as
  53. 8 4
      spine-as3/spine-as3/src/spine/SkeletonJson.as
  54. 23 9
      spine-as3/spine-as3/src/spine/TransformConstraint.as
  55. 1 0
      spine-as3/spine-as3/src/spine/TransformConstraintData.as
  56. 9 39
      spine-as3/spine-as3/src/spine/TransformMode.as
  57. 3 17
      spine-as3/spine-as3/src/spine/animation/Animation.as
  58. 494 148
      spine-as3/spine-as3/src/spine/animation/AnimationState.as
  59. 21 3
      spine-as3/spine-as3/src/spine/animation/AttachmentTimeline.as
  60. 40 22
      spine-as3/spine-as3/src/spine/animation/ColorTimeline.as
  61. 5 1
      spine-as3/spine-as3/src/spine/animation/CurveTimeline.as
  62. 65 19
      spine-as3/spine-as3/src/spine/animation/DeformTimeline.as
  63. 24 7
      spine-as3/spine-as3/src/spine/animation/DrawOrderTimeline.as
  64. 122 0
      spine-as3/spine-as3/src/spine/animation/EventQueue.as
  65. 6 2
      spine-as3/spine-as3/src/spine/animation/EventTimeline.as
  66. 41 0
      spine-as3/spine-as3/src/spine/animation/EventType.as
  67. 27 7
      spine-as3/spine-as3/src/spine/animation/IkConstraintTimeline.as
  68. 33 17
      spine-as3/spine-as3/src/spine/animation/PathConstraintMixTimeline.as
  69. 23 13
      spine-as3/spine-as3/src/spine/animation/PathConstraintPositionTimeline.as
  70. 24 13
      spine-as3/spine-as3/src/spine/animation/PathConstraintSpacingTimeline.as
  71. 34 25
      spine-as3/spine-as3/src/spine/animation/RotateTimeline.as
  72. 51 15
      spine-as3/spine-as3/src/spine/animation/ScaleTimeline.as
  73. 37 18
      spine-as3/spine-as3/src/spine/animation/ShearTimeline.as
  74. 3 1
      spine-as3/spine-as3/src/spine/animation/Timeline.as
  75. 56 0
      spine-as3/spine-as3/src/spine/animation/TimelineType.as
  76. 44 12
      spine-as3/spine-as3/src/spine/animation/TrackEntry.as
  77. 51 23
      spine-as3/spine-as3/src/spine/animation/TransformConstraintTimeline.as
  78. 33 15
      spine-as3/spine-as3/src/spine/animation/TranslateTimeline.as
  79. 5 6
      spine-as3/spine-as3/src/spine/attachments/VertexAttachment.as
  80. 8 13
      spine-c/include/spine/Animation.h
  81. 45 23
      spine-c/include/spine/AnimationState.h
  82. 6 6
      spine-c/include/spine/Bone.h
  83. 10 2
      spine-c/include/spine/BoneData.h
  84. 1 4
      spine-c/include/spine/IkConstraint.h
  85. 1 1
      spine-c/include/spine/IkConstraintData.h
  86. 1 0
      spine-c/include/spine/PathConstraintData.h
  87. 0 2
      spine-c/include/spine/Skeleton.h
  88. 1 0
      spine-c/include/spine/TransformConstraintData.h
  89. 50 11
      spine-c/include/spine/extension.h
  90. 501 266
      spine-c/src/spine/Animation.c
  91. 706 181
      spine-c/src/spine/AnimationState.c
  92. 121 107
      spine-c/src/spine/Bone.c
  93. 1 2
      spine-c/src/spine/BoneData.c
  94. 21 15
      spine-c/src/spine/IkConstraint.c
  95. 15 8
      spine-c/src/spine/PathConstraint.c
  96. 1 1
      spine-c/src/spine/RegionAttachment.c
  97. 150 96
      spine-c/src/spine/Skeleton.c
  98. 21 7
      spine-c/src/spine/SkeletonBinary.c
  99. 16 2
      spine-c/src/spine/SkeletonJson.c
  100. 18 8
      spine-c/src/spine/TransformConstraint.c

Datei-Diff unterdrückt, da er zu groß ist
+ 228 - 221
examples/alien/export/alien.json


BIN
examples/alien/export/alien.skel


+ 4 - 4
examples/dragon/export/dragon.json

@@ -1,5 +1,5 @@
 {
-"skeleton": { "hash": "9MKo2cmJTDc3IPV4B3LRJxWbl04", "spine": "3.4.02", "width": 897, "height": 716.36, "images": "./images/" },
+"skeleton": { "hash": "Mc0suer5LoUZv7DvFA3mLFATKFU", "spine": "3.5.03-beta", "width": 897, "height": 716.36, "fps": 30, "images": "./images/" },
 "bones": [
 	{ "name": "root", "y": -176.12 },
 	{ "name": "COG", "parent": "root", "y": 176.12 },
@@ -200,7 +200,7 @@
 					{ "time": 0.8, "name": "L_wing03" },
 					{ "time": 0.8333, "name": "L_wing04" },
 					{ "time": 0.8666, "name": "L_wing05" },
-					{ "time": 0.9, "name": "L_wing06" },
+					{ "time": 0.8999, "name": "L_wing06" },
 					{ "time": 0.9333, "name": "L_wing07" },
 					{ "time": 0.9666, "name": "L_wing08" },
 					{ "time": 1, "name": "L_wing01" }
@@ -223,7 +223,7 @@
 					{ "time": 0.8, "name": "R_wing03" },
 					{ "time": 0.8333, "name": "R_wing04" },
 					{ "time": 0.8666, "name": "R_wing05" },
-					{ "time": 0.9, "name": "R_wing06" },
+					{ "time": 0.8999, "name": "R_wing06" },
 					{ "time": 0.9333, "name": "R_wing07" },
 					{ "time": 0.9666, "name": "R_wing08" },
 					{ "time": 1, "name": "R_wing01" }
@@ -757,7 +757,7 @@
 					{ "time": 0.3333, "angle": 23.93 },
 					{
 						"time": 0.6666,
-						"angle": 337.8,
+						"angle": 337.79,
 						"curve": [ 0.41, 0, 0.887, 0.75 ]
 					},
 					{ "time": 1, "angle": 0 }

BIN
examples/dragon/export/dragon.skel


+ 34 - 0
examples/export/runtimes.sh

@@ -68,6 +68,36 @@ cp -f ../tank/export/tank.json ../../spine-cocos2dx/example/Resources/common/
 cp -f ../tank/export/tank.atlas ../../spine-cocos2dx/example/Resources/common/
 cp -f ../tank/export/tank.png ../../spine-cocos2dx/example/Resources/common/
 
+echo "spine-corona"
+rm -f ../../spine-corona/data/*
+cp -f ../goblins/export/goblins-mesh.json ../../spine-corona/data
+cp -f ../goblins/export/goblins.atlas ../../spine-corona/data
+cp -f ../goblins/export/goblins.png ../../spine-corona/data
+
+cp -f ../raptor/export/raptor.json ../../spine-corona/data
+cp -f ../raptor/export/raptor.atlas ../../spine-corona/data
+cp -f ../raptor/export/raptor.png ../../spine-corona/data
+
+cp -f ../spineboy/export/spineboy.json ../../spine-corona/data
+cp -f ../spineboy/export/spineboy.atlas ../../spine-corona/data
+cp -f ../spineboy/export/spineboy.png ../../spine-corona/data
+
+cp -f ../tank/export/tank.json ../../spine-corona/data
+cp -f ../tank/export/tank.atlas ../../spine-corona/data
+cp -f ../tank/export/tank.png ../../spine-corona/data
+
+cp -f ../vine/export/vine.json ../../spine-corona/data
+cp -f ../vine/export/vine.atlas ../../spine-corona/data
+cp -f ../vine/export/vine.png ../../spine-corona/data
+
+cp -f ../stretchyman/export/stretchyman.json ../../spine-corona/data
+cp -f ../stretchyman/export/stretchyman.atlas ../../spine-corona/data
+cp -f ../stretchyman/export/stretchyman.png ../../spine-corona/data
+
+cp -f ../test/export/test.json ../../spine-corona/data
+cp -f ../test/export/test.atlas ../../spine-corona/data
+cp -f ../test/export/test.png ../../spine-corona/data
+
 echo "spine-love"
 rm -f ../../spine-love/data/*
 cp -f ../goblins/export/goblins-mesh.json ../../spine-love/data
@@ -152,6 +182,10 @@ cp -f ../vine/export/vine.json ../../spine-starling/spine-starling-example/src/
 cp -f ../vine/export/vine.atlas ../../spine-starling/spine-starling-example/src/
 cp -f ../vine/export/vine.png ../../spine-starling/spine-starling-example/src/
 
+cp -f ../stretchyman/export/stretchyman.json ../../spine-starling/spine-starling-example/src/
+cp -f ../stretchyman/export/stretchyman.atlas ../../spine-starling/spine-starling-example/src/
+cp -f ../stretchyman/export/stretchyman.png ../../spine-starling/spine-starling-example/src/
+
 
 echo "spine-ts"
 rm -f ../../spine-ts/webgl/example/assets/*

+ 40 - 33
examples/goblins/export/goblins-mesh.json

@@ -1,5 +1,12 @@
 {
-"skeleton": { "hash": "3Z6FWT/Kylgd8cbSWBEwAsOcPEM", "spine": "3.4.02", "width": 266.87, "height": 349.55, "images": "./images/" },
+"skeleton": {
+	"hash": "RVwj3d7R/kLKOn5pep0SkQV51Kc",
+	"spine": "3.5.03-beta",
+	"width": 266.87,
+	"height": 349.55,
+	"fps": 30,
+	"images": "./images/"
+},
 "bones": [
 	{ "name": "root" },
 	{ "name": "hip", "parent": "root", "x": 0.64, "y": 114.41 },
@@ -56,7 +63,7 @@
 				"type": "mesh",
 				"uvs": [ 1, 0.11236, 0.77096, 0.13278, 0.76608, 0.21781, 0.75642, 0.386, 0.74723, 0.54607, 0.72117, 1, 0.28838, 1, 0.24208, 0.54327, 0.22589, 0.38361, 0.2089, 0.21605, 0.20043, 0.13242, 0, 0.11519, 0.4527, 0, 0.58399, 0 ],
 				"triangles": [ 5, 6, 4, 6, 7, 4, 4, 7, 3, 2, 9, 1, 9, 10, 1, 10, 12, 1, 12, 13, 1, 1, 13, 0, 10, 11, 12, 3, 8, 2, 8, 9, 2, 7, 8, 3 ],
-				"vertices": [ 1, 20, 38.54, -10.88, 1, 1, 20, 30.97, -5.93, 1, 2, 19, 61.48, -5.58, 0.5116, 20, -0.31, -6.16, 0.48839, 2, 18, 64.73, -5.03, 0.50272, 19, -0.4, -5.06, 0.49728, 1, 10, 4.56, 23.91, 1, 1, 10, 41.7, -138.95, 1, 1, 10, 32.41999, -141.1, 1, 1, 10, -6.49, 22.4, 1, 2, 18, 65.48, 6.64, 0.50272, 19, 0.52999, 6.59, 0.49728, 2, 19, 62.18, 6.66, 0.5116, 20, 0.2, 6.09, 0.48839, 1, 20, 30.96, 6.61, 1, 1, 20, 37.25999, 11.09, 1, 1, 20, 79.75, 1.59, 1, 1, 20, 79.78, -1.29, 1 ],
+				"vertices": [ 1, 20, 38.54, -10.88, 1, 1, 20, 30.96999, -5.92999, 1, 2, 19, 61.47999, -5.57999, 0.5116, 20, -0.31, -6.15999, 0.48838, 2, 18, 64.73, -5.03, 0.5027, 19, -0.4, -5.05999, 0.49728, 1, 10, 4.55999, 23.90999, 1, 1, 10, 41.7, -138.95, 1, 1, 10, 32.41999, -141.1, 1, 1, 10, -6.48999, 22.39999, 1, 2, 18, 65.48, 6.63999, 0.5027, 19, 0.52999, 6.59, 0.49728, 2, 19, 62.18, 6.65999, 0.5116, 20, 0.2, 6.09, 0.48838, 1, 20, 30.95999, 6.61, 1, 1, 20, 37.25999, 11.09, 1, 1, 20, 79.75, 1.59, 1, 1, 20, 79.77999, -1.28999, 1 ],
 				"hull": 14,
 				"edges": [ 24, 22, 22, 20, 10, 12, 2, 0, 24, 26, 0, 26, 8, 10, 12, 14, 6, 8, 14, 16, 2, 4, 4, 6, 16, 18, 18, 20, 20, 2 ],
 				"width": 22,
@@ -68,7 +75,7 @@
 				"type": "mesh",
 				"uvs": [ 0.78091, 0.38453, 1, 0.38405, 1, 0.44881, 0.73953, 0.4687, 0.74641, 0.81344, 0.34022, 1, 0.15434, 1, 0.11303, 0.78858, 0.23007, 0.47367, 0, 0.45047, 0, 0.38621, 0.22367, 0.38573, 0.24384, 0, 1, 0 ],
 				"triangles": [ 0, 12, 13, 11, 12, 0, 0, 1, 2, 9, 10, 11, 3, 11, 0, 3, 0, 2, 8, 11, 3, 9, 11, 8, 5, 6, 7, 4, 5, 8, 4, 8, 3, 5, 7, 8 ],
-				"vertices": [ 15.49, -12.82, 21.13, -13.57, 20.16, -20.49, 13.15, -21.67, 8.13, -58.56, -5.13, -77.04, -9.92, -76.36, -7.79, -53.6, -0.03, -20.36, -5.6, -17.04, -4.63, -10.17, 1.12, -10.93, 7.46, 30.24, 26.93, 27.49 ],
+				"vertices": [ 15.48999, -12.81999, 21.12999, -13.56999, 20.15999, -20.48999, 13.14999, -21.67, 8.13, -58.56, -5.13, -77.04, -9.92, -76.36, -7.78999, -53.59999, -0.02999, -20.36, -5.59999, -17.04, -4.63, -10.17, 1.12, -10.93, 7.46, 30.23999, 26.93, 27.48999 ],
 				"hull": 14,
 				"edges": [ 22, 20, 24, 26, 22, 24, 2, 0, 0, 22, 0, 26, 12, 14, 14, 16, 18, 20, 16, 18, 2, 4, 4, 6, 6, 8, 10, 12, 8, 10 ],
 				"width": 26,
@@ -89,7 +96,7 @@
 				"type": "mesh",
 				"uvs": [ 0, 0.60494, 0.14172, 0.5145, 0.24218, 0.55229, 0.32667, 0.67806, 0.37969, 0.79352, 0.53505, 0.93014, 0.86056, 1, 0.94071, 0.94169, 0.92098, 0.69923, 0.9888, 0.65497, 0.99003, 0.51643, 0.89632, 0.43561, 0.94487, 0.41916, 1, 0.39713, 1, 0.2836, 0.94017, 0.27027, 0.87906, 0.25666, 0.80754, 0.16044, 0.66698, 0.01997, 0.4734, 0.01805, 0.29215, 0.19893, 0.25392, 0.31823, 0.09117, 0.324, 0, 0.44331, 0.43271, 0.69153, 0.466, 0.47794, 0.35996, 0.31246, 0.73473, 0.68593, 0.72215, 0.57425, 0.88179, 0.5583, 0.80267, 0.51015 ],
 				"triangles": [ 5, 27, 6, 7, 27, 8, 7, 6, 27, 4, 24, 5, 5, 24, 27, 4, 3, 24, 27, 29, 8, 8, 29, 9, 24, 28, 27, 24, 25, 28, 24, 3, 25, 29, 28, 30, 29, 27, 28, 25, 2, 26, 25, 3, 2, 9, 29, 10, 0, 23, 1, 28, 25, 30, 29, 11, 10, 29, 30, 11, 2, 21, 26, 2, 1, 21, 23, 22, 1, 1, 22, 21, 30, 16, 11, 30, 17, 16, 30, 25, 17, 17, 26, 18, 18, 26, 19, 26, 17, 25, 11, 15, 12, 11, 16, 15, 12, 15, 13, 15, 14, 13, 21, 20, 26, 26, 20, 19 ],
-				"vertices": [ 14.56, 50.42, 23.12, 35.47, 17.45999, 26.36, 11.57, 16.86, 3.74, 11.71, -5.89, -3.91, -11.83, -37.23, -8.31, -45.63, 7.75, -44.24, 10.39, -51.33, 19.52, -51.82, 25.21, -43.15, 26.12, -47.43, 27.35, -53.16, 34.84, -53.46, 35.96, -47.33, 37.11, -41.08, 43.75, -33.97, 53.58, -19.87, 54.5, 0.03, 43.31, 19.16, 35.59999, 23.41, 35.89, 40.16999, 28.39, 49.87, 10.25, 5.99, 24.2, 2, 35.55, 12.48, 9.39, -25.1, 16.79999, -24.31, 17.2, -40.65, 20.68, -33.02 ],
+				"vertices": [ 14.56, 50.41999, 23.12, 35.47, 17.45999, 26.36, 11.56999, 16.86, 3.74, 11.71, -5.88999, -3.91, -11.82999, -37.22999, -8.31, -45.63, 7.75, -44.24, 10.39, -51.33, 19.52, -51.81999, 25.20999, -43.15, 26.12, -47.43, 27.35, -53.15999, 34.84, -53.45999, 35.95999, -47.33, 37.11, -41.08, 43.75, -33.97, 53.58, -19.87, 54.5, 0.02999, 43.31, 19.15999, 35.59999, 23.40999, 35.88999, 40.16999, 28.38999, 49.86999, 10.25, 5.98999, 24.2, 2, 35.54999, 12.47999, 9.39, -25.1, 16.79999, -24.30999, 17.2, -40.65, 20.68, -33.02 ],
 				"hull": 24,
 				"edges": [ 0, 2, 6, 8, 8, 10, 10, 12, 12, 14, 14, 16, 16, 18, 18, 20, 20, 22, 26, 28, 32, 34, 34, 36, 36, 38, 38, 40, 40, 42, 42, 44, 44, 46, 0, 46, 6, 48, 48, 50, 50, 52, 52, 42, 2, 4, 4, 6, 4, 52, 2, 44, 22, 32, 22, 24, 24, 26, 28, 30, 30, 32, 24, 30, 16, 54, 54, 56, 20, 58, 58, 54, 16, 58, 22, 60, 60, 56, 58, 60 ],
 				"width": 103,
@@ -102,7 +109,7 @@
 				"type": "mesh",
 				"uvs": [ 0.68992, 0.29284, 1, 0.46364, 1, 0.74643, 0.84089, 1, 0.66344, 1, 0.33765, 0.64284, 0, 0.44124, 0, 0, 0.34295, 0 ],
 				"triangles": [ 3, 4, 2, 4, 5, 2, 5, 0, 2, 0, 1, 2, 0, 5, 8, 5, 6, 8, 6, 7, 8 ],
-				"vertices": [ 18.6, 8.81, 32.18999, 10.31, 38.02, 1.62, 38.08, -9.63, 32.31, -13.49, 14.37, -9.62, -0.75, -10.78, -9.84, 2.77, 1.29, 10.25 ],
+				"vertices": [ 18.6, 8.81, 32.18999, 10.31, 38.02, 1.62, 38.08, -9.63, 32.31, -13.48999, 14.36999, -9.61999, -0.75, -10.77999, -9.84, 2.76999, 1.28999, 10.25 ],
 				"hull": 9,
 				"edges": [ 14, 16, 16, 0, 0, 2, 2, 4, 6, 4, 6, 8, 8, 10, 12, 14, 10, 12 ],
 				"width": 37,
@@ -115,7 +122,7 @@
 				"type": "mesh",
 				"uvs": [ 0.15733, 0.31873, 0.08195, 0.78502, 0.15884, 0.99366, 0.41633, 0.96804, 0.68822, 0.97636, 1, 0.96388, 0.99385, 0.73501, 0.85294, 0.51862, 0.61479, 0.31056, 0.46991, 0, 0.48032, 0.75604, 0.75994, 0.77706 ],
 				"triangles": [ 0, 9, 8, 10, 0, 8, 10, 8, 7, 11, 10, 7, 11, 7, 6, 1, 0, 10, 11, 6, 5, 3, 1, 10, 4, 10, 11, 4, 11, 5, 3, 10, 4, 2, 1, 3 ],
-				"vertices": [ 2.28, 13.07, -1.76, -1.64, 3.59, -7.8, 20.25, -6.04, 37.91, -5.27, 58.12, -3.71, 57.31, 3.34, 47.78, 9.51, 31.95, 15.05, 21.99, 24.11, 24.03, 0.75, 42.21, 1.16 ],
+				"vertices": [ 2.27999, 13.06999, -1.75999, -1.63999, 3.58999, -7.8, 20.25, -6.03999, 37.90999, -5.26999, 58.11999, -3.71, 57.31, 3.33999, 47.77999, 9.51, 31.95, 15.05, 21.98999, 24.11, 24.03, 0.75, 42.20999, 1.15999 ],
 				"hull": 10,
 				"edges": [ 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10, 12, 12, 14, 14, 16, 16, 18, 0, 18, 6, 20, 20, 16, 2, 20, 8, 22, 22, 14, 20, 22, 22, 10 ],
 				"width": 65,
@@ -128,7 +135,7 @@
 				"type": "mesh",
 				"uvs": [ 0.518, 0.12578, 1, 0.16285, 0.99788, 0.50578, 0.69745, 1, 0.37445, 1, 0, 0.80051, 0, 0.42792, 0.17601, 0, 0.43567, 0 ],
 				"triangles": [ 2, 0, 1, 0, 5, 6, 6, 7, 0, 0, 7, 8, 3, 4, 0, 4, 5, 0, 2, 3, 0 ],
-				"vertices": [ -3.11, 15.42, 10.83, 22.27, 15.5, 14.55, 18.35, -8.96, 9.47999, -14.32, -4.58, -14.3, -11.63, -2.63, -14.89, 13.68, -7.75, 17.99 ],
+				"vertices": [ -3.10999, 15.42, 10.82999, 22.27, 15.5, 14.55, 18.35, -8.96, 9.47999, -14.31999, -4.57999, -14.3, -11.63, -2.63, -14.89, 13.68, -7.75, 17.98999 ],
 				"hull": 9,
 				"edges": [ 16, 0, 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10, 12, 14, 16, 12, 14 ],
 				"width": 36,
@@ -139,9 +146,9 @@
 			"left lower leg": {
 				"name": "goblin/left-lower-leg",
 				"type": "mesh",
-				"uvs": [ 0.95508, 0.20749, 0.81927, 0.65213, 0.94754, 0.77308, 0.67842, 0.97346, 0.46463, 1, 0.26845, 1, 0.04963, 0.90706, 0.2106, 0.60115, 0.07478, 0.40195, 0.18545, 0, 0.28857, 0 ],
+				"uvs": [ 0.95508, 0.20749, 0.81927, 0.65213, 0.94754, 0.77307, 0.67842, 0.97346, 0.46463, 1, 0.26845, 1, 0.04963, 0.90706, 0.2106, 0.60115, 0.07478, 0.40195, 0.18545, 0, 0.28857, 0 ],
 				"triangles": [ 10, 8, 9, 1, 7, 10, 7, 8, 10, 0, 1, 10, 1, 4, 7, 3, 1, 2, 5, 6, 7, 7, 4, 5, 1, 3, 4 ],
-				"vertices": [ -0.19, 6.82, 30.97, 10.96, 37.97, 17.33, 53.88, 12.6, 57.58, 6.31, 59.34, 0.08, 55.04, -8.63, 32.99, -9.33, 20.79, -17.43, -7.27, -21.56, -8.18999, -18.29 ],
+				"vertices": [ -0.18999, 6.82, 30.96999, 10.96, 37.97, 17.32999, 53.88, 12.6, 57.58, 6.30999, 59.34, 0.07999, 55.04, -8.63, 32.99, -9.32999, 20.79, -17.43, -7.26999, -21.55999, -8.18999, -18.29 ],
 				"hull": 11,
 				"edges": [ 20, 0, 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10, 12, 12, 14, 14, 16, 18, 20, 16, 18 ],
 				"width": 33,
@@ -154,7 +161,7 @@
 				"type": "mesh",
 				"uvs": [ 0.7377, 0.40692, 1, 0.75237, 1, 1, 0.62046, 1, 0.26184, 0.56601, 0, 0.29783, 0, 0, 0.44115, 0 ],
 				"triangles": [ 3, 1, 2, 3, 0, 1, 3, 4, 0, 4, 7, 0, 4, 5, 7, 5, 6, 7 ],
-				"vertices": [ 15.18, 5.74, 32.16999, 5.32, 41.79, 0.21, 36.63, -9.5, 14.88, -9.72, 0.9, -10.89, -10.66, -4.73999, -4.66, 6.54 ],
+				"vertices": [ 15.18, 5.73999, 32.16999, 5.32, 41.79, 0.20999, 36.63, -9.5, 14.88, -9.72, 0.89999, -10.89, -10.65999, -4.73999, -4.65999, 6.53999 ],
 				"hull": 8,
 				"edges": [ 12, 14, 14, 0, 4, 2, 0, 2, 4, 6, 6, 8, 10, 12, 8, 10 ],
 				"width": 29,
@@ -167,7 +174,7 @@
 				"type": "mesh",
 				"uvs": [ 1, 0.12167, 1, 0.54873, 0.91067, 0.78907, 0.76567, 1, 0.3087, 0.9579, 0, 0.68777, 0, 0.219, 0.51961, 0, 0.87552, 0 ],
 				"triangles": [ 7, 8, 0, 5, 6, 7, 0, 1, 7, 4, 5, 7, 1, 4, 7, 2, 4, 1, 3, 4, 2 ],
-				"vertices": [ 2.33, 13.06, 33.5, 12.57, 51, 9.34, 66.32, 4.31, 63, -10.71, 43.13, -20.58, 8.91, -20.04, -6.79, -2.64, -6.61, 9.1 ],
+				"vertices": [ 2.32999, 13.06, 33.5, 12.56999, 51, 9.34, 66.31999, 4.30999, 63, -10.71, 43.13, -20.57999, 8.90999, -20.04, -6.78999, -2.64, -6.61, 9.1 ],
 				"hull": 9,
 				"edges": [ 10, 8, 8, 6, 6, 4, 4, 2, 10, 12, 12, 14, 14, 16, 2, 0, 16, 0 ],
 				"width": 33,
@@ -180,7 +187,7 @@
 				"type": "mesh",
 				"uvs": [ 0.81967, 0.27365, 0.92101, 0.82048, 0.47134, 1, 0.15679, 0.9354, 0, 0.7556, 0.19268, 0.51833, 0.15468, 0.35706, 0, 0.21989, 0.13568, 0, 0.68878, 0, 0.70145, 0.53872 ],
 				"triangles": [ 3, 5, 2, 2, 10, 1, 2, 5, 10, 3, 4, 5, 10, 0, 1, 0, 10, 6, 10, 5, 6, 7, 8, 6, 6, 9, 0, 6, 8, 9 ],
-				"vertices": [ 18.62, -11.65, -3.98, -13.85, -10.28, 2.76, -6.91, 13.89, 0.8, 19.04999, 10.06, 11.51, 16.74, 12.45, 22.71, 17.64, 31.4, 12.19, 30.12, -7.67, 8.05, -6.71 ],
+				"vertices": [ 18.62, -11.64999, -3.98, -13.85, -10.27999, 2.75999, -6.90999, 13.89, 0.8, 19.04999, 10.06, 11.51, 16.73999, 12.44999, 22.70999, 17.63999, 31.39999, 12.18999, 30.12, -7.67, 8.05, -6.71 ],
 				"hull": 10,
 				"edges": [ 14, 12, 12, 10, 10, 8, 8, 6, 6, 4, 4, 2, 2, 20, 20, 0, 0, 18, 16, 18, 14, 16, 0, 2 ],
 				"width": 36,
@@ -193,7 +200,7 @@
 				"type": "mesh",
 				"uvs": [ 1, 1, 0, 1, 0, 0, 1, 0 ],
 				"triangles": [ 1, 2, 3, 1, 3, 0 ],
-				"vertices": [ 25.38, -20.73, -36.61, -20.73, -36.61, 22.26, 25.38, 22.26 ],
+				"vertices": [ 25.37999, -20.72999, -36.61, -20.72999, -36.61, 22.26, 25.37999, 22.26 ],
 				"hull": 4,
 				"edges": [ 0, 2, 2, 4, 4, 6, 0, 6 ],
 				"width": 62,
@@ -206,7 +213,7 @@
 				"type": "mesh",
 				"uvs": [ 1, 0.09223, 1, 0.8501, 0.72058, 1, 0.24384, 1, 0, 0.86558, 0.20822, 0.10919, 0.50903, 0, 0.85342, 0 ],
 				"triangles": [ 1, 2, 6, 6, 2, 5, 1, 6, 0, 4, 5, 3, 2, 3, 5, 6, 7, 0 ],
-				"vertices": [ -4.75, 8.89, 33.03, 11.74, 40.99, 5.89, 41.81, -5.03, 35.53, -11.13, -2.53, -9.2, -8.5, -2.71, -9.09, 5.17999 ],
+				"vertices": [ -4.75, 8.89, 33.02999, 11.73999, 40.99, 5.88999, 41.81, -5.03, 35.52999, -11.13, -2.52999, -9.19999, -8.5, -2.71, -9.09, 5.17999 ],
 				"hull": 8,
 				"edges": [ 8, 6, 4, 6, 4, 2, 12, 14, 2, 0, 14, 0, 10, 12, 8, 10 ],
 				"width": 23,
@@ -219,7 +226,7 @@
 				"type": "mesh",
 				"uvs": [ 0.40851, 0.0047, 0.59087, 0.33404, 0.75959, 0.48311, 0.88907, 0.59751, 0.97532, 0.89391, 0.90385, 1, 0.6722, 1, 0.38633, 1, 0.08074, 1, 0, 0.88921, 0, 0.65984, 0, 0.46577, 0.0906, 0.0988, 0.305, 0, 0.47461, 0.71257, 0.715, 0.74681 ],
 				"triangles": [ 1, 10, 11, 1, 13, 0, 14, 1, 2, 1, 12, 13, 12, 1, 11, 14, 10, 1, 15, 14, 2, 15, 2, 3, 9, 10, 14, 15, 3, 4, 7, 8, 9, 14, 7, 9, 6, 14, 15, 5, 6, 15, 7, 14, 6, 4, 5, 15 ],
-				"vertices": [ 17.36, 25.99, 29.13, 15.44, 39.89, 10.8, 48.14, 7.24, 53.84, -2.38, 49.43, -6, 34.84, -6.39, 16.84, -6.87, -2.4, -7.38, -7.58, -3.86, -7.78, 3.7, -7.95, 10.1, -2.57, 22.36, 10.84, 25.97, 22.14, 2.75, 37.31, 2.03 ],
+				"vertices": [ 17.36, 25.98999, 29.12999, 15.43999, 39.88999, 10.8, 48.13999, 7.23999, 53.84, -2.38, 49.43, -6, 34.84, -6.38999, 16.84, -6.86999, -2.4, -7.38, -7.57999, -3.85999, -7.78, 3.7, -7.94999, 10.1, -2.56999, 22.36, 10.84, 25.96999, 22.13999, 2.75, 37.31, 2.02999 ],
 				"hull": 14,
 				"edges": [ 0, 2, 6, 8, 8, 10, 16, 18, 22, 24, 24, 26, 0, 26, 10, 12, 2, 4, 4, 6, 12, 14, 14, 16, 18, 20, 20, 22, 2, 28, 28, 14, 20, 28, 4, 30, 30, 12, 28, 30, 30, 8 ],
 				"width": 63,
@@ -232,7 +239,7 @@
 				"type": "mesh",
 				"uvs": [ 0.17957, 0, 0, 0.44772, 0, 0.79734, 0.20057, 0.94264, 0.55057, 1, 0.8539, 1, 0.89823, 0.82004, 0.8259, 0.74285, 0.84223, 0.49993, 0.96356, 0.34102, 0.66023, 0 ],
 				"triangles": [ 8, 10, 9, 0, 10, 1, 8, 2, 1, 8, 1, 10, 7, 3, 8, 3, 2, 8, 4, 3, 7, 5, 7, 6, 4, 7, 5 ],
-				"vertices": [ -10.82, -9.45, 5.95, -15.34, 18.87999, -14.9, 24, -7.5, 25.69, 5.16, 25.31, 16.07, 18.61, 17.44, 15.84, 14.74, 6.84, 15.02, 0.81, 19.18, -11.41, 7.83 ],
+				"vertices": [ -10.81999, -9.44999, 5.94999, -15.34, 18.87999, -14.89999, 24, -7.5, 25.69, 5.15999, 25.30999, 16.06999, 18.61, 17.44, 15.84, 14.73999, 6.84, 15.02, 0.81, 19.18, -11.40999, 7.82999 ],
 				"hull": 11,
 				"edges": [ 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10, 12, 12, 14, 14, 16, 16, 18, 18, 20, 0, 20 ],
 				"width": 36,
@@ -245,7 +252,7 @@
 				"type": "mesh",
 				"uvs": [ 0.88538, 0.22262, 0.76167, 0.3594, 0.75088, 0.78308, 0.95326, 0.84981, 1, 0.60302 ],
 				"triangles": [ 1, 0, 4, 2, 1, 4, 3, 2, 4 ],
-				"vertices": [ -2.82, 15.97, 2.4, 11.71, 18.08, 11.9, 20.27, 19.27, 11.09, 20.62 ],
+				"vertices": [ -2.81999, 15.97, 2.4, 11.71, 18.07999, 11.89999, 20.27, 19.27, 11.09, 20.62 ],
 				"hull": 5,
 				"edges": [ 2, 4, 4, 6, 6, 8, 2, 0, 0, 8 ],
 				"width": 36,
@@ -258,7 +265,7 @@
 				"type": "mesh",
 				"uvs": [ 1, 0.27261, 0.81312, 0.52592, 0.79587, 0.71795, 0.95544, 0.80988, 0.85193, 0.95493, 0.47241, 1, 0.14033, 1, 0, 0.8773, 0.14896, 0.67914, 0.1619, 0.30325, 0.60611, 0 ],
 				"triangles": [ 1, 10, 0, 9, 10, 1, 8, 9, 1, 2, 8, 1, 4, 2, 3, 6, 7, 8, 5, 6, 8, 2, 5, 8, 4, 5, 2 ],
-				"vertices": [ 6.26, 8.46, 23.32, 8.04, 37.09999, 12.89, 41.45, 20.82, 53.07, 21.46, 61.33, 10.06, 65.76999, -1.03, 58.99, -9.18999, 43.02, -9.81, 16.33, -20, -12.79, -9.26 ],
+				"vertices": [ 6.26, 8.46, 23.31999, 8.03999, 37.09999, 12.89, 41.45, 20.81999, 53.06999, 21.45999, 61.33, 10.06, 65.76999, -1.02999, 58.99, -9.18999, 43.02, -9.81, 16.32999, -20, -12.78999, -9.26 ],
 				"hull": 11,
 				"edges": [ 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10, 12, 12, 14, 14, 16, 16, 18, 0, 20, 18, 20 ],
 				"width": 36,
@@ -271,7 +278,7 @@
 				"type": "mesh",
 				"uvs": [ 0.62008, 0.03708, 0.92131, 0.09048, 1, 0.38319, 0.72049, 0.6937, 0.31656, 1, 0, 1, 0, 0.75106, 0.28233, 0.49988 ],
 				"triangles": [ 4, 6, 7, 4, 7, 3, 4, 5, 6, 7, 0, 3, 2, 0, 1, 2, 3, 0 ],
-				"vertices": [ -3.17, -11.05, -9, -0.57, -1.01, 10.33, 16.69, 11.17, 37.41, 8.2, 45.45, -1.16, 36.95, -8.46, 21.2, -7.47 ],
+				"vertices": [ -3.17, -11.05, -9, -0.56999, -1.00999, 10.32999, 16.69, 11.17, 37.40999, 8.19999, 45.45, -1.15999, 36.95, -8.46, 21.2, -7.46999 ],
 				"hull": 8,
 				"edges": [ 10, 12, 12, 14, 14, 0, 0, 2, 2, 4, 4, 6, 8, 10, 6, 8 ],
 				"width": 39,
@@ -284,7 +291,7 @@
 				"type": "mesh",
 				"uvs": [ 0.27018, 0, 0.11618, 0.18177, 0, 0.70688, 0, 0.89577, 0.26668, 1, 0.48718, 1, 0.67618, 0.83532, 1, 0.5161, 1, 0.25543, 0.74618, 0.0571 ],
 				"triangles": [ 9, 8, 7, 9, 1, 0, 6, 9, 7, 6, 1, 9, 2, 1, 6, 4, 3, 2, 6, 4, 2, 5, 4, 6 ],
-				"vertices": [ -9.85, -10.37, 2.17, -14.07, 35.49, -13.66, 47.29, -12.11, 52.61, -2.26, 51.63, 5.16, 40.50999, 10.18, 19.12999, 18.46999, 2.85, 16.32, -8.39999, 6.14 ],
+				"vertices": [ -9.85, -10.36999, 2.17, -14.06999, 35.49, -13.65999, 47.29, -12.10999, 52.61, -2.25999, 51.63, 5.15999, 40.50999, 10.18, 19.12999, 18.46999, 2.84999, 16.31999, -8.39999, 6.13999 ],
 				"hull": 10,
 				"edges": [ 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10, 12, 12, 14, 14, 16, 16, 18, 0, 18 ],
 				"width": 34,
@@ -297,7 +304,7 @@
 				"type": "mesh",
 				"uvs": [ 0, 0.33287, 0.15945, 0.46488, 0.15761, 0.60314, 0.15502, 0.79806, 0.32807, 0.93478, 0.6875, 1, 0.80731, 1, 1, 0.77763, 1, 0.66147, 1, 0.56703, 0.93207, 0.4771, 0.86944, 0.39416, 0.83837, 0.226, 0.68085, 0, 0.14836, 0, 0, 0.07199, 0.78734, 0.86249, 0.43679, 0.79649, 0.76738, 0.61733, 0.44345, 0.58747, 0.54329, 0.38316, 0.77692, 0.73446, 0.66478, 0.51012 ],
 				"triangles": [ 5, 16, 6, 6, 16, 7, 4, 17, 5, 5, 17, 16, 4, 3, 17, 17, 21, 16, 16, 21, 7, 3, 2, 17, 21, 19, 18, 21, 17, 19, 17, 2, 19, 21, 8, 7, 21, 18, 8, 18, 9, 8, 19, 22, 18, 18, 10, 9, 18, 22, 10, 2, 1, 19, 19, 20, 22, 19, 1, 20, 22, 11, 10, 22, 20, 11, 20, 1, 14, 20, 12, 11, 1, 0, 14, 20, 13, 12, 20, 14, 13, 0, 15, 14 ],
-				"vertices": [ 56.93, 27.95, 43.37, 18.23, 30.16, 19.5, 11.53, 21.28, -2.55, 10.69, -10.89, -13.12, -11.59, -21.23, 8.54, -36.12, 19.65, -37.08, 28.68, -37.86, 37.68, -34, 45.98, -30.44, 56.4, -29.07, 84.78, -20.92, 87.9, 15.15, 81.87999, 25.79, 1.67, -21.01, 10.03, 2.18, 25.23, -18.25, 29.98, 0, 48.54, -8.39, 13.98, -21.36, 35.9, -15.6 ],
+				"vertices": [ 56.93, 27.95, 43.36999, 18.22999, 30.15999, 19.5, 11.52999, 21.28, -2.54999, 10.68999, -10.89, -13.11999, -11.59, -21.22999, 8.53999, -36.11999, 19.64999, -37.08, 28.68, -37.86, 37.68, -34, 45.97999, -30.44, 56.4, -29.06999, 84.77999, -20.92, 87.9, 15.14999, 81.87999, 25.79, 1.66999, -21.01, 10.02999, 2.18, 25.22999, -18.25, 29.97999, 0, 48.54, -8.39, 13.97999, -21.36, 35.9, -15.6 ],
 				"hull": 16,
 				"edges": [ 0, 2, 6, 8, 8, 10, 10, 12, 12, 14, 22, 24, 24, 26, 26, 28, 28, 30, 0, 30, 14, 32, 32, 34, 34, 6, 18, 36, 36, 38, 2, 4, 4, 6, 38, 4, 2, 40, 40, 22, 40, 38, 38, 34, 32, 10, 34, 8, 40, 28, 14, 16, 16, 18, 32, 42, 42, 36, 16, 42, 42, 34, 18, 20, 20, 22, 36, 44, 44, 40, 20, 44 ],
 				"width": 68,
@@ -310,7 +317,7 @@
 				"type": "mesh",
 				"uvs": [ 0.36097, 0.44959, 0.66297, 0.60591, 1, 0.19486, 1, 0.57117, 0.75897, 1, 0.38697, 1, 0, 0.26433, 0, 0, 0.12497, 0 ],
 				"triangles": [ 6, 7, 8, 6, 8, 0, 3, 1, 2, 5, 0, 1, 6, 0, 5, 4, 1, 3, 5, 1, 4 ],
-				"vertices": [ -10.56, 12.87, 6.53, 9.89999, 25.62, 17.70999, 25.62, 10.56, 11.97, 2.41, -9.09, 2.41, -31, 16.39, -31, 21.41, -23.92, 21.41 ],
+				"vertices": [ -10.56, 12.86999, 6.53, 9.89999, 25.62, 17.70999, 25.62, 10.56, 11.97, 2.41, -9.09, 2.41, -31, 16.38999, -31, 21.40999, -23.92, 21.40999 ],
 				"hull": 9,
 				"edges": [ 14, 16, 16, 0, 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 12, 14, 10, 12, 0, 10, 2, 8 ],
 				"width": 55,
@@ -323,7 +330,7 @@
 				"type": "mesh",
 				"uvs": [ 0, 0.32029, 0.14893, 0.59457, 0.22437, 1, 0.35909, 1, 0.50998, 1, 0.79559, 0.58453, 0.9842, 0.28015, 1, 0.00588, 0.46957, 0.17646, 0, 0.03933, 0.48843, 0.59122, 0.48114, 0.43099 ],
 				"triangles": [ 6, 8, 7, 0, 9, 8, 11, 8, 6, 0, 8, 11, 5, 11, 6, 10, 11, 5, 1, 0, 11, 1, 11, 10, 3, 2, 1, 10, 3, 1, 4, 10, 5, 3, 10, 4 ],
-				"vertices": [ -13.22, 5.56, -8, -2.47, -5.49, -14.27, -0.64, -14.36, 4.78, -14.45, 15.27, -2.58999, 22.22, 6.11, 22.92, 14.05, 3.75, 9.43999, -13.08, 13.71, 4.21, -2.58999, 4.03, 2.05 ],
+				"vertices": [ -13.22, 5.55999, -8, -2.47, -5.48999, -14.27, -0.63999, -14.35999, 4.78, -14.44999, 15.27, -2.58999, 22.21999, 6.11, 22.92, 14.05, 3.75, 9.43999, -13.07999, 13.71, 4.21, -2.58999, 4.03, 2.04999 ],
 				"hull": 10,
 				"edges": [ 0, 2, 2, 4, 8, 10, 10, 12, 12, 14, 14, 16, 16, 18, 0, 18, 4, 6, 6, 8, 6, 20, 16, 22, 22, 20, 0, 22, 22, 12, 2, 20, 20, 10 ],
 				"width": 36,
@@ -408,7 +415,7 @@
 		"slots": {
 			"eyes": {
 				"attachment": [
-					{ "time": 0.7, "name": "eyes closed" },
+					{ "time": 0.6999, "name": "eyes closed" },
 					{ "time": 0.8, "name": null }
 				]
 			}
@@ -805,7 +812,7 @@
 							"curve": [ 0.621, 0, 0.75, 1 ]
 						},
 						{
-							"time": 0.7,
+							"time": 0.6999,
 							"vertices": [ -10.97826, -6.68962, -4.68015, -2.46175, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1.1755, -0.17183, -1.1755, -0.17182, -1.1755, -0.17183, 0, 0, -2.22324, 2.66465, -4.83295, 2.70084, -5.70553, -0.51941, -3.15962, -1.61501, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -6.64741, 0.81612, -11.82285, -1.34955, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1.1755, -0.17183 ],
 							"curve": [ 0.25, 0, 0.75, 1 ]
 						},
@@ -840,7 +847,7 @@
 						{
 							"time": 0.7333,
 							"offset": 8,
-							"vertices": [ -2.97737, 9.40254, -6.91661, 19.92794, -10.55287, 18.41085, -12.37161, 12.38473, -4.72606, 6.30798, 0, 0, -1.48902, 4.88944, -7.06773, 10.70101 ]
+							"vertices": [ -2.97737, 9.40254, -6.91661, 19.92794, -10.55287, 18.41085, -12.37161, 12.38473, -4.72606, 6.30798, 0, 0, -1.48902, 4.88944, -7.06772, 10.70101 ]
 						},
 						{
 							"time": 0.8333,
@@ -868,7 +875,7 @@
 							"vertices": [ -1.04945, -3.10476 ]
 						},
 						{
-							"time": 0.7,
+							"time": 0.6999,
 							"offset": 6,
 							"vertices": [ -1.4245, -6.30616 ]
 						},
@@ -896,7 +903,7 @@
 						{
 							"time": 0.3,
 							"offset": 2,
-							"vertices": [ -8.27184, 6.68821, -9.29764, 10.13797, -8.62231, 14.71339, -4.58629, 18.81939, -2.20304, 17.10709, -0.07794, 9.9046, 2.54451, 1.01642, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2.94624, 2.38007, -4.59398, 10.01888 ]
+							"vertices": [ -8.27184, 6.68821, -9.29764, 10.13797, -8.62231, 14.71339, -4.58629, 18.81939, -2.20304, 17.10709, -0.07794, 9.9046, 2.54451, 1.01642, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2.94624, 2.38007, -4.59398, 10.01887 ]
 						},
 						{
 							"time": 0.3666,
@@ -912,12 +919,12 @@
 						{
 							"time": 0.7333,
 							"offset": 4,
-							"vertices": [ 1.31462, -6.84099, -0.87905, -12.54479, -5.9851, -14.08367, -7.15892, -11.63193, -5.6792, -4.83544, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2.06163, -6.93844 ]
+							"vertices": [ 1.31462, -6.84099, -0.87905, -12.54479, -5.98509, -14.08367, -7.15892, -11.63193, -5.6792, -4.83544, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2.06163, -6.93844 ]
 						},
 						{
 							"time": 0.8,
 							"offset": 4,
-							"vertices": [ 0.65731, -3.42049, -0.43952, -6.27239, -2.99255, -7.04183, -3.57946, -5.81596, -2.83959, -2.41772, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2.79687, -1.2802, 0, 0, 0, 0, -1.03081, -3.46922 ]
+							"vertices": [ 0.65731, -3.42049, -0.43952, -6.27239, -2.99254, -7.04183, -3.57946, -5.81596, -2.83959, -2.41772, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2.79687, -1.2802, 0, 0, 0, 0, -1.03081, -3.46922 ]
 						},
 						{ "time": 0.8666 }
 					]
@@ -1023,7 +1030,7 @@
 							"vertices": [ -2.37974, -0.05431, -0.49433, 0.19436, -0.90861, 1.16519, -1.60956, 2.70798, 0.96186, 0.80615 ]
 						},
 						{
-							"time": 0.7,
+							"time": 0.6999,
 							"offset": 2,
 							"vertices": [ -0.91714, -2.76567, -0.62214, -3.63489, -0.8494, -2.26772, -2.56076, 0.5297 ]
 						},
@@ -1058,7 +1065,7 @@
 							"vertices": [ 1.16999, 0, -0.234, -0.93599, -2.92499, 0.35099, 0, 0, 0, 0, 0.49999, -0.24999, -0.64078, -2.07914, -0.64078, -2.07914 ]
 						},
 						{
-							"time": 0.7,
+							"time": 0.6999,
 							"vertices": [ 1.8627, -0.11514, 4.66326, -0.09099, -1.76428, 0.21171, 0, 0, -0.56832, 0.32832, -1.13833, -1.1511, -2.19996, -3.47068, -1.29718, -3.47068, 0, 0, 0, 0, 1.58785, -0.04642, 2.65941, 0.16714 ]
 						},
 						{
@@ -1067,7 +1074,7 @@
 						},
 						{
 							"time": 0.8666,
-							"vertices": [ 2.01969, -0.0214, 8.98545, 0.4446, -0.20937, 0.08022, 0.45919, 0, -0.35919, 0.47279, -1.84159, -1.4488, -0.79153, 1.2642, 0.53285, 1.23981, 0.45919, 0, 0.11759, -0.09759, 3.2839, -0.09025, 5.13115, 0.19388 ]
+							"vertices": [ 2.01969, -0.0214, 8.98545, 0.4446, -0.20937, 0.08022, 0.45919, 0, -0.35919, 0.47279, -1.84159, -1.4488, -0.79153, 1.2642, 0.53285, 1.2398, 0.45919, 0, 0.11759, -0.09759, 3.2839, -0.09025, 5.13115, 0.19388 ]
 						},
 						{
 							"time": 1,

BIN
examples/goblins/export/goblins-mesh.skel


+ 9 - 2
examples/goblins/export/goblins.json

@@ -1,5 +1,12 @@
 {
-"skeleton": { "hash": "XWev9W//Ru9qTfd6z9xOs5YG8F0", "spine": "3.4.02", "width": 234.01, "height": 354.84, "images": "./images/" },
+"skeleton": {
+	"hash": "9Twfw0ImCCb/C3Vx15ucH1FZP6U",
+	"spine": "3.5.03-beta",
+	"width": 234.01,
+	"height": 354.84,
+	"fps": 30,
+	"images": "./images/"
+},
 "bones": [
 	{ "name": "root" },
 	{ "name": "hip", "parent": "root", "x": 0.64, "y": 114.41 },
@@ -208,7 +215,7 @@
 		"slots": {
 			"eyes": {
 				"attachment": [
-					{ "time": 0.7, "name": "eyes closed" },
+					{ "time": 0.6999, "name": "eyes closed" },
 					{ "time": 0.8, "name": null }
 				]
 			}

BIN
examples/goblins/export/goblins.skel


Datei-Diff unterdrückt, da er zu groß ist
+ 31 - 5
examples/hero/export/hero-mesh.json


BIN
examples/hero/export/hero-mesh.skel


+ 31 - 5
examples/hero/export/hero.json

@@ -1,5 +1,12 @@
 {
-"skeleton": { "hash": "G8Hn+I+pLLpQJV630iP0YYkYAHQ", "spine": "3.4.02", "width": 319.35, "height": 337.01, "images": "./images/" },
+"skeleton": {
+	"hash": "A6YUo0HfQU4n+BxWujsTVoekIjA",
+	"spine": "3.5.03-beta",
+	"width": 319.35,
+	"height": 337.01,
+	"fps": 30,
+	"images": "./images/"
+},
 "bones": [
 	{ "name": "root" },
 	{ "name": "Hip", "parent": "root", "y": 94.88 },
@@ -12,10 +19,26 @@
 	{ "name": "body", "parent": "Hip", "length": 60.79, "rotation": 96.39, "x": 4.09, "y": 3.17 },
 	{ "name": "thigh1", "parent": "Hip", "length": 23.94, "rotation": -107.07, "x": -17.51, "y": -5.22 },
 	{ "name": "shin1", "parent": "thigh1", "length": 28.23, "rotation": 10.26, "x": 29.76, "y": -0.91 },
-	{ "name": "foot1", "parent": "shin1", "length": 27.07, "rotation": -90.96, "x": 28.17, "y": -0.48, "inheritRotation": false },
+	{
+		"name": "foot1",
+		"parent": "shin1",
+		"length": 27.07,
+		"rotation": -90.96,
+		"x": 28.17,
+		"y": -0.48,
+		"transform": "noRotationOrReflection"
+	},
 	{ "name": "thigh2", "parent": "Hip", "length": 23.65, "rotation": -85.46, "x": 21.85, "y": -5.86 },
 	{ "name": "shin2", "parent": "thigh2", "length": 25.35, "rotation": -2.25, "x": 31.99, "y": 2.96 },
-	{ "name": "foot2", "parent": "shin2", "length": 22.28, "rotation": -90.42, "x": 25.54, "y": 0.27, "inheritRotation": false },
+	{
+		"name": "foot2",
+		"parent": "shin2",
+		"length": 22.28,
+		"rotation": -90.42,
+		"x": 25.54,
+		"y": 0.27,
+		"transform": "noRotationOrReflection"
+	},
 	{ "name": "upperarm1", "parent": "body", "length": 19.35, "rotation": 130.11, "x": 52.49, "y": 41.84 },
 	{ "name": "forearm1", "parent": "upperarm1", "length": 22.06, "rotation": 30.56, "x": 23.97, "y": 5.16 },
 	{ "name": "upperarm2", "parent": "body", "length": 25.98, "rotation": -178.95, "x": 57.94, "y": -38.93 },
@@ -51,18 +74,21 @@
 "ik": [
 	{
 		"name": "L_Leg",
+		"order": 0,
 		"bones": [ "thigh1", "shin1" ],
 		"target": "L_Ankle",
 		"bendPositive": false
 	},
 	{
 		"name": "LookConstraint",
+		"order": 1,
 		"bones": [ "head" ],
 		"target": "LookConstraintGoal",
 		"mix": 0
 	},
 	{
 		"name": "R_Leg",
+		"order": 2,
 		"bones": [ "thigh2", "shin2" ],
 		"target": "R_Ankle",
 		"bendPositive": false
@@ -1201,8 +1227,8 @@
 					{ "time": 1, "angle": 0 }
 				],
 				"translate": [
-					{ "time": 0, "x": -6.83, "y": 12.06, "curve": "stepped" },
-					{ "time": 1, "x": -6.83, "y": 12.06 }
+					{ "time": 0, "x": -6.83, "y": 12.05, "curve": "stepped" },
+					{ "time": 1, "x": -6.83, "y": 12.05 }
 				]
 			},
 			"L_Ground": {

BIN
examples/hero/export/hero.skel


+ 15 - 8
examples/powerup/export/powerup.json

@@ -1,5 +1,12 @@
 {
-"skeleton": { "hash": "NDIwB/eCc9kyjtXoMAlUrSBw0+Q", "spine": "3.4.02", "width": 413.18, "height": 195.71, "images": "./images/" },
+"skeleton": {
+	"hash": "DRH0bKbb0s4lzWxWdYTHHpv4S4I",
+	"spine": "3.5.03-beta",
+	"width": 413.18,
+	"height": 195.71,
+	"fps": 30,
+	"images": "./images/"
+},
 "bones": [
 	{ "name": "root" },
 	{ "name": "powerup", "parent": "root", "x": 1.48, "y": 134.02 },
@@ -137,7 +144,7 @@
 				"color": [
 					{ "time": 0.6333, "color": "ffffff00" },
 					{ "time": 1.1666, "color": "ffffffff" },
-					{ "time": 1.3, "color": "ffffff00" }
+					{ "time": 1.2999, "color": "ffffff00" }
 				]
 			},
 			"star8": {
@@ -338,7 +345,7 @@
 					{ "time": 0.9333, "angle": 89.61 }
 				],
 				"translate": [
-					{ "time": 0.2666, "x": -12.16, "y": -2.27 },
+					{ "time": 0.2666, "x": -12.15, "y": -2.27 },
 					{ "time": 0.9333, "x": -76.4, "y": 67.76 }
 				],
 				"scale": [
@@ -396,10 +403,10 @@
 					{ "time": 1.3333, "angle": 18.27 }
 				],
 				"translate": [
-					{ "time": 0, "x": -67.74, "y": -7.78 },
+					{ "time": 0, "x": -67.73, "y": -7.78 },
 					{ "time": 0.3333, "x": -102.83, "y": -22.36, "curve": "stepped" },
 					{ "time": 1, "x": -32.63, "y": 6.8 },
-					{ "time": 1.3333, "x": -67.74, "y": -7.78 }
+					{ "time": 1.3333, "x": -67.73, "y": -7.78 }
 				],
 				"scale": [
 					{ "time": 0, "x": 0.565, "y": 0.565 },
@@ -431,15 +438,15 @@
 			"star7": {
 				"rotate": [
 					{ "time": 0.6333, "angle": 41.11 },
-					{ "time": 1.3, "angle": -89.84 }
+					{ "time": 1.2999, "angle": -89.84 }
 				],
 				"translate": [
 					{ "time": 0.6333, "x": 20.27, "y": 32.75 },
-					{ "time": 1.3, "x": -67.79, "y": -68.56 }
+					{ "time": 1.2999, "x": -67.79, "y": -68.56 }
 				],
 				"scale": [
 					{ "time": 0.6333, "x": 0.258, "y": 0.258 },
-					{ "time": 1.3, "x": 0.999, "y": 0.999 }
+					{ "time": 1.2999, "x": 0.999, "y": 0.999 }
 				]
 			},
 			"star9": {

BIN
examples/powerup/export/powerup.skel


Datei-Diff unterdrückt, da er zu groß ist
+ 23 - 7
examples/raptor/export/raptor.json


BIN
examples/raptor/export/raptor.skel


+ 39 - 32
examples/speedy/export/speedy.json

@@ -1,5 +1,12 @@
 {
-"skeleton": { "hash": "tmPhvJxT1yYlfE25wzds+8X1E44", "spine": "3.4.02", "width": 196.03, "height": 305.44, "images": "./images/" },
+"skeleton": {
+	"hash": "E5R9cboxfxSjYUhqQd9WdsoQ5E0",
+	"spine": "3.5.03-beta",
+	"width": 196.03,
+	"height": 305.44,
+	"fps": 30,
+	"images": "./images/"
+},
 "bones": [
 	{ "name": "root" },
 	{ "name": "hip", "parent": "root", "x": -0.02, "y": 51.91 },
@@ -188,7 +195,7 @@
 					{ "time": 0.4333, "angle": 51.9 },
 					{ "time": 0.5, "angle": 0.96 },
 					{ "time": 0.5666, "angle": 0.34 },
-					{ "time": 0.7, "angle": -52.09 },
+					{ "time": 0.6999, "angle": -52.09 },
 					{ "time": 0.8666, "angle": -146 }
 				],
 				"translate": [
@@ -203,7 +210,7 @@
 					{ "time": 0.4333, "x": 43.67, "y": -9.79 },
 					{ "time": 0.5, "x": 17.35, "y": 0.84 },
 					{ "time": 0.5666, "x": -15.6, "y": 2.95 },
-					{ "time": 0.7, "x": -68.64, "y": 42.69 },
+					{ "time": 0.6999, "x": -68.64, "y": 42.69 },
 					{ "time": 0.8666, "x": -64.33, "y": 66.23 }
 				]
 			},
@@ -215,7 +222,7 @@
 					{ "time": 0.2666, "angle": -53.09 },
 					{ "time": 0.4333, "angle": -140.76 },
 					{ "time": 0.5666, "angle": -105.72 },
-					{ "time": 0.7, "angle": -40.18 },
+					{ "time": 0.6999, "angle": -40.18 },
 					{ "time": 0.8666, "angle": 52.51 }
 				],
 				"translate": [
@@ -225,7 +232,7 @@
 					{ "time": 0.4333, "x": -31.23, "y": 65.13 },
 					{ "time": 0.5666, "x": 0.36, "y": 69.13 },
 					{
-						"time": 0.7,
+						"time": 0.6999,
 						"x": 44.36,
 						"y": 71.35,
 						"curve": [ 0.35, 0, 0.704, 1 ]
@@ -251,7 +258,7 @@
 						"curve": [ 0.416, 0, 0.62, 0.99 ]
 					},
 					{
-						"time": 0.7,
+						"time": 0.6999,
 						"angle": -11.11,
 						"curve": [ 0.77, 0, 0.62, 0.99 ]
 					},
@@ -277,7 +284,7 @@
 						"curve": [ 0.436, 0, 0.589, 1 ]
 					},
 					{
-						"time": 0.7,
+						"time": 0.6999,
 						"x": -16.04,
 						"y": 68.2,
 						"curve": [ 0.531, 0, 0.735, 1 ]
@@ -303,7 +310,7 @@
 						"curve": [ 0.379, 0, 0.658, 1 ]
 					},
 					{
-						"time": 0.7,
+						"time": 0.6999,
 						"angle": 17.86,
 						"curve": [ 0.379, 0, 0.658, 1 ]
 					},
@@ -329,7 +336,7 @@
 						"curve": [ 0.247, 0, 0.758, 1 ]
 					},
 					{
-						"time": 0.7,
+						"time": 0.6999,
 						"x": 20.22,
 						"y": -13.82,
 						"curve": [ 0.247, 0, 0.758, 1 ]
@@ -355,7 +362,7 @@
 						"curve": [ 0.364, 0, 0.663, 1 ]
 					},
 					{
-						"time": 0.7,
+						"time": 0.6999,
 						"angle": -31.28,
 						"curve": [ 0.364, 0, 0.663, 1 ]
 					},
@@ -381,7 +388,7 @@
 						"curve": [ 0.284, 0, 0.764, 1 ]
 					},
 					{
-						"time": 0.7,
+						"time": 0.6999,
 						"x": 1.27,
 						"y": 12.47,
 						"curve": [ 0.284, 0, 0.764, 1 ]
@@ -417,7 +424,7 @@
 						"curve": [ 0.227, 0, 0.577, 0.99 ]
 					},
 					{
-						"time": 0.7,
+						"time": 0.6999,
 						"angle": -0.38,
 						"curve": [ 0.583, 0, 0.649, 1.91 ]
 					},
@@ -461,7 +468,7 @@
 						"curve": [ 0.39, 0, 0.689, 1 ]
 					},
 					{
-						"time": 0.7,
+						"time": 0.6999,
 						"x": 4.19,
 						"y": -3.73,
 						"curve": [ 0.316, 0, 0.692, 1 ]
@@ -501,7 +508,7 @@
 					},
 					{ "time": 0.4333, "x": 0, "y": 4.99 },
 					{
-						"time": 0.7,
+						"time": 0.6999,
 						"x": 0,
 						"y": -0.35,
 						"curve": [ 0.189, -0.01, 0.675, 1 ]
@@ -522,7 +529,7 @@
 					{ "time": 0.2666, "angle": 11.5 },
 					{ "time": 0.4333, "angle": 15.84 },
 					{ "time": 0.5666, "angle": 16.03 },
-					{ "time": 0.7, "angle": 13.7 },
+					{ "time": 0.6999, "angle": 13.7 },
 					{ "time": 0.8666, "angle": 15.41 }
 				],
 				"translate": [
@@ -541,7 +548,7 @@
 					{ "time": 0, "x": -4.18, "y": 0 },
 					{ "time": 0.2666, "x": -1.81, "y": 0 },
 					{ "time": 0.4333, "x": -4.18, "y": 0 },
-					{ "time": 0.7, "x": -1.81, "y": 0 },
+					{ "time": 0.6999, "x": -1.81, "y": 0 },
 					{ "time": 0.8666, "x": -4.18, "y": 0 }
 				],
 				"scale": [
@@ -614,7 +621,7 @@
 						"curve": [ 0.25, 0, 0.75, 1 ]
 					},
 					{
-						"time": 0.7,
+						"time": 0.6999,
 						"angle": -39.16,
 						"curve": [ 0.25, 0, 0.75, 1 ]
 					},
@@ -711,7 +718,7 @@
 					{ "time": 0.4333, "x": 35.67, "y": -9.79 },
 					{ "time": 0.5, "x": 17.17, "y": -0.23 },
 					{ "time": 0.5666, "x": -1.32, "y": 0.2 },
-					{ "time": 0.7, "x": -45.32, "y": 42.16 },
+					{ "time": 0.6999, "x": -45.32, "y": 42.16 },
 					{ "time": 0.8666, "x": -67.77, "y": 66.23 }
 				]
 			},
@@ -723,7 +730,7 @@
 					{ "time": 0.1333, "angle": 5.41 },
 					{ "time": 0.4333, "angle": -126.4 },
 					{ "time": 0.5666, "angle": -105.72 },
-					{ "time": 0.7, "angle": -40.18 },
+					{ "time": 0.6999, "angle": -40.18 },
 					{ "time": 0.8666, "angle": 52.51 }
 				],
 				"translate": [
@@ -733,7 +740,7 @@
 					{ "time": 0.2666, "x": -12.2, "y": 37.48 },
 					{ "time": 0.4333, "x": -31.63, "y": 65.13 },
 					{ "time": 0.5666, "x": 0.36, "y": 62.13 },
-					{ "time": 0.7, "x": 44.36, "y": 71.35 },
+					{ "time": 0.6999, "x": 44.36, "y": 71.35 },
 					{ "time": 0.8666, "x": 82.79, "y": -5.51 }
 				]
 			},
@@ -742,14 +749,14 @@
 					{ "time": 0, "angle": -34.76 },
 					{ "time": 0.2666, "angle": -18.25 },
 					{ "time": 0.4333, "angle": -34.76 },
-					{ "time": 0.7, "angle": -18.25 },
+					{ "time": 0.6999, "angle": -18.25 },
 					{ "time": 0.8666, "angle": -34.76 }
 				],
 				"translate": [
 					{ "time": 0, "x": -10.12, "y": 0.91 },
 					{ "time": 0.2666, "x": -10.12, "y": 60.08 },
 					{ "time": 0.4333, "x": -10.12, "y": 0.91 },
-					{ "time": 0.7, "x": -10.12, "y": 60.08 },
+					{ "time": 0.6999, "x": -10.12, "y": 60.08 },
 					{ "time": 0.8666, "x": -10.12, "y": 0.91 }
 				]
 			},
@@ -758,14 +765,14 @@
 					{ "time": 0, "angle": 72.22 },
 					{ "time": 0.2666, "angle": 17.86 },
 					{ "time": 0.4333, "angle": 72.22 },
-					{ "time": 0.7, "angle": 17.86 },
+					{ "time": 0.6999, "angle": 17.86 },
 					{ "time": 0.8666, "angle": 72.22 }
 				],
 				"translate": [
 					{ "time": 0, "x": 51.46, "y": -4.94 },
 					{ "time": 0.2666, "x": 20.22, "y": -13.82 },
 					{ "time": 0.4333, "x": 51.46, "y": -4.94 },
-					{ "time": 0.7, "x": 20.22, "y": -13.82 },
+					{ "time": 0.6999, "x": 20.22, "y": -13.82 },
 					{ "time": 0.8666, "x": 51.46, "y": -4.94 }
 				]
 			},
@@ -774,14 +781,14 @@
 					{ "time": 0, "angle": -49.84 },
 					{ "time": 0.2666, "angle": -31.28 },
 					{ "time": 0.4333, "angle": -49.84 },
-					{ "time": 0.7, "angle": -31.28 },
+					{ "time": 0.6999, "angle": -31.28 },
 					{ "time": 0.8666, "angle": -49.84 }
 				],
 				"translate": [
 					{ "time": 0, "x": 7.79, "y": 12.99 },
 					{ "time": 0.2666, "x": 1.27, "y": 12.47 },
 					{ "time": 0.4333, "x": 7.79, "y": 12.99 },
-					{ "time": 0.7, "x": 1.27, "y": 12.47 },
+					{ "time": 0.6999, "x": 1.27, "y": 12.47 },
 					{ "time": 0.8666, "x": 7.79, "y": 12.99 }
 				]
 			},
@@ -792,7 +799,7 @@
 					{ "time": 0.2666, "angle": 16.09 },
 					{ "time": 0.4333, "angle": 26.87 },
 					{ "time": 0.5, "angle": 17.67 },
-					{ "time": 0.7, "angle": 16.09 },
+					{ "time": 0.6999, "angle": 16.09 },
 					{ "time": 0.8666, "angle": 26.87 }
 				],
 				"translate": [
@@ -802,7 +809,7 @@
 					{ "time": 0.3666, "x": 11.32, "y": 6.51 },
 					{ "time": 0.4333, "x": -6.24, "y": -1.04 },
 					{ "time": 0.5666, "x": -5.23, "y": -9.91 },
-					{ "time": 0.7, "x": 4.19, "y": -3.73 },
+					{ "time": 0.6999, "x": 4.19, "y": -3.73 },
 					{ "time": 0.8, "x": 11.32, "y": 6.51 },
 					{ "time": 0.8666, "x": -6.24, "y": -1.04 }
 				]
@@ -839,7 +846,7 @@
 					{ "time": 0.2666, "x": 0, "y": -3.03 },
 					{ "time": 0.3666, "x": 0, "y": 17.66 },
 					{ "time": 0.4333, "x": 0, "y": 4.99 },
-					{ "time": 0.7, "x": 0, "y": -0.35 },
+					{ "time": 0.6999, "x": 0, "y": -0.35 },
 					{ "time": 0.8, "x": 0, "y": 17.66 },
 					{ "time": 0.8666, "x": 0, "y": 4.99 }
 				]
@@ -851,7 +858,7 @@
 					{ "time": 0.2666, "angle": 11.5 },
 					{ "time": 0.4333, "angle": 15.84 },
 					{ "time": 0.5666, "angle": 16.03 },
-					{ "time": 0.7, "angle": 13.7 },
+					{ "time": 0.6999, "angle": 13.7 },
 					{ "time": 0.8666, "angle": 15.41 }
 				],
 				"translate": [
@@ -865,7 +872,7 @@
 					{ "time": 0, "x": -4.18, "y": 0 },
 					{ "time": 0.2666, "x": -1.81, "y": 0 },
 					{ "time": 0.4333, "x": -4.18, "y": 0 },
-					{ "time": 0.7, "x": -1.81, "y": 0 },
+					{ "time": 0.6999, "x": -1.81, "y": 0 },
 					{ "time": 0.8666, "x": -4.18, "y": 0 }
 				]
 			},
@@ -949,7 +956,7 @@
 				],
 				"translate": [
 					{ "time": 0, "x": -71.5, "y": 85.79 },
-					{ "time": 0.1333, "x": -32.89, "y": 71.49 },
+					{ "time": 0.1333, "x": -32.88, "y": 71.49 },
 					{ "time": 0.2666, "x": 27.16, "y": 47.9 },
 					{ "time": 0.4, "x": 53.16, "y": -0.71 },
 					{ "time": 0.5333, "x": -17.87, "y": 6.43 },

BIN
examples/speedy/export/speedy.skel


+ 14 - 7
examples/spineboy-old/export/spineboy-old.json

@@ -1,5 +1,12 @@
 {
-"skeleton": { "hash": "DIwMYpCxf9hwB7YgCS6TSSPK/5A", "spine": "3.4.02", "width": 161.12, "height": 343.58, "images": "./images/" },
+"skeleton": {
+	"hash": "MfbzRfUXCtXYzTG2gPcZOR6jXYY",
+	"spine": "3.5.03-beta",
+	"width": 161.12,
+	"height": 343.58,
+	"fps": 30,
+	"images": "./images/"
+},
 "bones": [
 	{ "name": "root" },
 	{ "name": "hip", "parent": "root", "x": 0.64, "y": 114.41 },
@@ -46,7 +53,7 @@
 			"bb-head": {
 				"type": "boundingbox",
 				"vertexCount": 5,
-				"vertices": [ 77.26, -53.82, 105.8, -32.41999, 58.43, 40.34, 5.82, 37, -7.01, -42.23 ]
+				"vertices": [ 77.26, -53.81999, 105.8, -32.41999, 58.43, 40.34, 5.82, 37, -7.01, -42.22999 ]
 			}
 		},
 		"eyes": {
@@ -122,7 +129,7 @@
 					{ "time": 5.2195, "angle": -37.12 },
 					{ "time": 5.9086, "angle": 2.07 },
 					{ "time": 6.9044, "angle": 34.72 },
-					{ "time": 7.7675, "angle": 359.99 }
+					{ "time": 7.7674, "angle": 359.99 }
 				],
 				"translate": [
 					{
@@ -441,7 +448,7 @@
 					{ "time": 0.4, "angle": -48.36 },
 					{ "time": 0.5666, "angle": -76.86 },
 					{ "time": 0.7666, "angle": -26.89 },
-					{ "time": 0.9, "angle": -18.97 },
+					{ "time": 0.8999, "angle": -18.97 },
 					{ "time": 0.9333, "angle": -14.18 },
 					{ "time": 1.0666, "angle": -80.45 },
 					{ "time": 1.3666, "angle": -27.46 }
@@ -515,7 +522,7 @@
 					{ "time": 0.7666, "angle": 1.34 },
 					{ "time": 0.9333, "angle": 2.35 },
 					{ "time": 1.0666, "angle": 6.08 },
-					{ "time": 1.3, "angle": 21.23 },
+					{ "time": 1.2999, "angle": 21.23 },
 					{ "time": 1.3666, "angle": 12.78 }
 				],
 				"translate": [
@@ -538,7 +545,7 @@
 					{ "time": 0.7666, "angle": -11.02 },
 					{ "time": 0.9333, "angle": -24.38 },
 					{ "time": 1.0666, "angle": 11.99 },
-					{ "time": 1.3, "angle": 4.86 },
+					{ "time": 1.2999, "angle": 4.86 },
 					{ "time": 1.3666, "angle": 5.19 }
 				],
 				"translate": [
@@ -975,7 +982,7 @@
 						"angle": 5.17,
 						"curve": [ 0, 0, 0.704, 1.61 ]
 					},
-					{ "time": 0.7, "angle": 1.1 },
+					{ "time": 0.6999, "angle": 1.1 },
 					{ "time": 0.8, "angle": 6.09 },
 					{ "time": 0.9333, "angle": 2.28 },
 					{ "time": 1.0666, "angle": 3.6 }

BIN
examples/spineboy-old/export/spineboy-old.skel


Datei-Diff unterdrückt, da er zu groß ist
+ 54 - 41
examples/spineboy/export/spineboy-hover.json


BIN
examples/spineboy/export/spineboy-hover.skel


Datei-Diff unterdrückt, da er zu groß ist
+ 10 - 6
examples/spineboy/export/spineboy-mesh.json


BIN
examples/spineboy/export/spineboy-mesh.skel


+ 20 - 13
examples/spineboy/export/spineboy.json

@@ -1,5 +1,12 @@
 {
-"skeleton": { "hash": "rPoYyBLFG6F0CGZ5wsUEBKDJU9U", "spine": "3.4.02", "width": 470.9, "height": 731.49, "images": "./images/" },
+"skeleton": {
+	"hash": "H7+xXqKKrnDC1fiM9L7F+WiY4ro",
+	"spine": "3.5.03-beta",
+	"width": 470.9,
+	"height": 731.49,
+	"fps": 30,
+	"images": "./images/"
+},
 "bones": [
 	{ "name": "hip", "y": 247.47 },
 	{ "name": "torso", "parent": "hip", "length": 127.55, "rotation": 103.82, "x": -1.61, "y": 4.9, "color": "e0da19ff" },
@@ -89,7 +96,7 @@
 			"head": {
 				"type": "boundingbox",
 				"vertexCount": 6,
-				"vertices": [ -19.14, -70.3, 40.8, -118.07, 257.77, -115.61, 285.16, 57.18, 120.77, 164.95, -5.06, 76.94 ]
+				"vertices": [ -19.13999, -70.3, 40.79999, -118.06999, 257.77, -115.61, 285.16, 57.18, 120.76999, 164.95, -5.05999, 76.94 ]
 			}
 		},
 		"mouth": {
@@ -230,7 +237,7 @@
 					{ "time": 0.4, "angle": -292.35 },
 					{ "time": 0.4333, "angle": -315.84 },
 					{ "time": 0.5, "angle": -347.94 },
-					{ "time": 0.7, "angle": -347.33, "curve": "stepped" },
+					{ "time": 0.6999, "angle": -347.33, "curve": "stepped" },
 					{ "time": 2.2333, "angle": -347.33 },
 					{ "time": 2.7, "angle": -290.68 },
 					{ "time": 2.7666, "angle": -285.1 },
@@ -271,7 +278,7 @@
 					{ "time": 0.4, "angle": -18.92 },
 					{ "time": 0.4333, "angle": -18.28 },
 					{ "time": 0.5, "angle": 60.61 },
-					{ "time": 0.7, "angle": -18.87, "curve": "stepped" },
+					{ "time": 0.6999, "angle": -18.87, "curve": "stepped" },
 					{ "time": 2.2333, "angle": -18.87 },
 					{ "time": 2.7, "angle": -1.95, "curve": "stepped" },
 					{ "time": 4.6666, "angle": -1.95 },
@@ -289,7 +296,7 @@
 				"rotate": [
 					{ "time": 0, "angle": -2.33 },
 					{ "time": 0.2666, "angle": 26.34 },
-					{ "time": 0.7, "angle": -6.07, "curve": "stepped" },
+					{ "time": 0.6999, "angle": -6.07, "curve": "stepped" },
 					{ "time": 2.2333, "angle": -6.07 },
 					{ "time": 2.7, "angle": 5.72, "curve": "stepped" },
 					{ "time": 4.6666, "angle": 5.72 },
@@ -2054,7 +2061,7 @@
 					{ "time": 0.4, "angle": -28.62 },
 					{ "time": 0.5, "angle": -19.3 },
 					{ "time": 0.6, "angle": -3.08 },
-					{ "time": 0.7, "angle": 29.51 },
+					{ "time": 0.6999, "angle": 29.51 },
 					{ "time": 0.8, "angle": 15.79 }
 				],
 				"translate": [
@@ -2078,7 +2085,7 @@
 					{ "time": 0.3, "angle": 15.98 },
 					{ "time": 0.4, "angle": 5.94 },
 					{ "time": 0.5, "angle": -26.76 },
-					{ "time": 0.7, "angle": -55.44 },
+					{ "time": 0.6999, "angle": -55.44 },
 					{ "time": 0.8, "angle": 5.12 }
 				],
 				"translate": [
@@ -2099,14 +2106,14 @@
 					{ "time": 0.4, "angle": 8.69 },
 					{ "time": 0.5, "angle": 12.16 },
 					{ "time": 0.6, "angle": -24.62 },
-					{ "time": 0.7, "angle": -27.26 },
+					{ "time": 0.6999, "angle": -27.26 },
 					{ "time": 0.8, "angle": -34.38 }
 				],
 				"translate": [
 					{ "time": 0, "x": 0, "y": 0 },
 					{ "time": 0.4, "x": 4.08, "y": -9.53 },
 					{ "time": 0.5, "x": 0, "y": 0 },
-					{ "time": 0.7, "x": -21.14, "y": -9.6 },
+					{ "time": 0.6999, "x": -21.14, "y": -9.6 },
 					{ "time": 0.8, "x": 0, "y": 0 }
 				],
 				"scale": [
@@ -2123,7 +2130,7 @@
 					{ "time": 0.4, "angle": 15.95 },
 					{ "time": 0.5, "angle": -9 },
 					{ "time": 0.6, "angle": 26.06 },
-					{ "time": 0.7, "angle": 21.85 },
+					{ "time": 0.6999, "angle": 21.85 },
 					{ "time": 0.8, "angle": 14.26 }
 				],
 				"translate": [
@@ -2146,7 +2153,7 @@
 					{ "time": 0.4, "angle": 13.45 },
 					{ "time": 0.5, "angle": -3.57 },
 					{ "time": 0.6, "angle": -0.97 },
-					{ "time": 0.7, "angle": 2.97 },
+					{ "time": 0.6999, "angle": 2.97 },
 					{ "time": 0.8, "angle": 10.13 }
 				],
 				"translate": [
@@ -2290,7 +2297,7 @@
 						"curve": [ 0.287, 0.37, 0.718, 0.76 ]
 					},
 					{
-						"time": 0.7,
+						"time": 0.6999,
 						"x": -23.93,
 						"y": 10.34,
 						"curve": [ 0.615, 0, 0.75, 1 ]
@@ -2327,7 +2334,7 @@
 					{ "time": 0.4, "angle": 10.22 },
 					{ "time": 0.5, "angle": 11.44 },
 					{ "time": 0.6, "angle": -0.33 },
-					{ "time": 0.7, "angle": 0.15 },
+					{ "time": 0.6999, "angle": 0.15 },
 					{ "time": 0.8, "angle": 12.49 }
 				],
 				"translate": [

BIN
examples/spineboy/export/spineboy.skel


+ 19 - 12
examples/spinosaurus/export/spinosaurus.json

@@ -1,5 +1,12 @@
 {
-"skeleton": { "hash": "+jy97FaQm8tosWwFNGbQGD2+FlY", "spine": "3.4.02", "width": 1680, "height": 1782.27, "images": "./images/" },
+"skeleton": {
+	"hash": "vfzL8SvUQWTNRil1kGbSbWF5R60",
+	"spine": "3.5.03-beta",
+	"width": 1680,
+	"height": 1782.27,
+	"fps": 30,
+	"images": "./images/"
+},
 "bones": [
 	{ "name": "root", "y": -526.31 },
 	{ "name": "leaves", "parent": "root", "x": -0.16, "y": 1310.1 },
@@ -67,9 +74,9 @@
 			"settings": {
 				"color": [
 					{ "time": 1.8666, "color": "fff99dff" },
-					{ "time": 2.1, "color": "ffffffff", "curve": "stepped" },
+					{ "time": 2.0999, "color": "ffffffff", "curve": "stepped" },
 					{ "time": 2.3666, "color": "ffffffff" },
-					{ "time": 2.6, "color": "fff99dff" }
+					{ "time": 2.5999, "color": "fff99dff" }
 				]
 			}
 		},
@@ -129,8 +136,8 @@
 					},
 					{
 						"time": 1,
-						"x": 1.02,
-						"y": 1.03,
+						"x": 1.019,
+						"y": 1.029,
 						"curve": [ 0.37, 0, 0.66, 0.99 ]
 					},
 					{
@@ -141,8 +148,8 @@
 					},
 					{
 						"time": 2.3333,
-						"x": 1.02,
-						"y": 1.03,
+						"x": 1.019,
+						"y": 1.029,
 						"curve": [ 0.37, 0, 0.66, 0.99 ]
 					},
 					{
@@ -151,7 +158,7 @@
 						"y": 1,
 						"curve": [ 0.387, 0.01, 0.686, 1 ]
 					},
-					{ "time": 3.6666, "x": 1.02, "y": 1.03 }
+					{ "time": 3.6666, "x": 1.019, "y": 1.029 }
 				]
 			},
 			"play": {
@@ -199,14 +206,14 @@
 						"y": 1,
 						"curve": [ 0, 0, 0.525, 2.46 ]
 					},
-					{ "time": 2.1, "x": 1.23, "y": 1.23, "curve": "stepped" },
+					{ "time": 2.0999, "x": 1.23, "y": 1.23, "curve": "stepped" },
 					{
 						"time": 2.3666,
 						"x": 1.23,
 						"y": 1.23,
 						"curve": [ 0, 0, 0.525, 2.46 ]
 					},
-					{ "time": 2.6, "x": 1, "y": 1 }
+					{ "time": 2.5999, "x": 1, "y": 1 }
 				]
 			},
 			"quit": {
@@ -266,9 +273,9 @@
 						"curve": [ 0, 0, 0.744, 0.4 ]
 					},
 					{ "time": 0.3333, "x": 1, "y": 1 },
-					{ "time": 1.3333, "x": 1.03, "y": 1.02 },
+					{ "time": 1.3333, "x": 1.029, "y": 1.019 },
 					{ "time": 2, "x": 1, "y": 1 },
-					{ "time": 2.6666, "x": 1.03, "y": 1.02 },
+					{ "time": 2.6666, "x": 1.029, "y": 1.019 },
 					{ "time": 3.3333, "x": 1, "y": 1 }
 				]
 			}

BIN
examples/spinosaurus/export/spinosaurus.skel


Datei-Diff unterdrückt, da er zu groß ist
+ 27 - 16
examples/stretchyman/export/stretchyman.json


BIN
examples/stretchyman/export/stretchyman.skel


Datei-Diff unterdrückt, da er zu groß ist
+ 21 - 6
examples/tank/export/tank.json


BIN
examples/tank/export/tank.skel


+ 36 - 27
examples/test/export/test.json

@@ -1,18 +1,25 @@
 {
-"skeleton": { "hash": "xbu5IFg+2tCx4+2MavNPurEogzA", "spine": "3.4.02", "width": 1333.78, "height": 1112.91, "images": "./images/" },
+"skeleton": {
+	"hash": "i05EldfljpjLC64I/kHnNrqzJ5A",
+	"spine": "3.5.03-beta",
+	"width": 1333.78,
+	"height": 1112.91,
+	"fps": 30,
+	"images": "./images/"
+},
 "bones": [
-	{ "name": "root", "x": 28.68, "y": -105.71 },
-	{ "name": "attachment", "parent": "root", "x": -267.71, "y": -366.62 },
-	{ "name": "color", "parent": "root", "x": -298.29, "y": 44.1 },
-	{ "name": "draworder", "parent": "root", "x": 283.53, "y": -356.27 },
-	{ "name": "mesh", "parent": "root", "x": -320.74, "y": 413.7 },
-	{ "name": "meshweighted", "parent": "root", "rotation": -11.22, "x": -353.28, "y": 627.53 },
-	{ "name": "pathfollower", "parent": "root", "x": 177.52, "y": 56.62 },
+	{ "name": "root", "x": 28.68, "y": -105.7 },
+	{ "name": "attachment", "parent": "root", "x": -267.7, "y": -366.61 },
+	{ "name": "color", "parent": "root", "x": -298.29, "y": 44.09 },
+	{ "name": "draworder", "parent": "root", "x": 283.52, "y": -356.26 },
+	{ "name": "mesh", "parent": "root", "x": -320.73, "y": 413.7 },
+	{ "name": "meshweighted", "parent": "root", "rotation": -11.22, "x": -353.27, "y": 627.53 },
+	{ "name": "pathfollower", "parent": "root", "x": 177.52, "y": 56.61 },
 	{ "name": "rotate", "parent": "root", "x": -305, "y": 201 },
 	{ "name": "scale", "parent": "root", "x": 183.99, "y": -138 },
 	{ "name": "shear", "parent": "root", "x": -304, "y": -146 },
-	{ "name": "transformconstrainttarget", "parent": "root", "x": 604.27, "y": 420.7, "color": "abe323ff" },
 	{ "name": "transformconstrained", "parent": "root", "x": 169.6, "y": 420.7 },
+	{ "name": "transformconstrainttarget", "parent": "root", "x": 604.27, "y": 420.7, "color": "abe323ff" },
 	{ "name": "translate", "parent": "root", "x": 190.99, "y": 201, "color": "abe323ff" }
 ],
 "slots": [
@@ -34,6 +41,7 @@
 "transform": [
 	{
 		"name": "transformconstraint",
+		"order": 1,
 		"bones": [ "transformconstrained" ],
 		"target": "transformconstrainttarget",
 		"x": -400,
@@ -46,6 +54,7 @@
 "path": [
 	{
 		"name": "pathconstraint",
+		"order": 0,
 		"bones": [ "pathfollower" ],
 		"target": "path2",
 		"rotation": 34.2
@@ -72,7 +81,7 @@
 				"path": "spine",
 				"uvs": [ 0, 0, 0.25, 0, 0.5, 0, 0.75, 0, 1, 0, 1, 0.25, 1, 0.5, 1, 0.75, 1, 1, 0.75, 1, 0.5, 1, 0.25, 1, 0, 1, 0, 0.75, 0, 0.5, 0, 0.25, 0.25, 0.25, 0.25, 0.5, 0.25, 0.75, 0.5, 0.25, 0.5, 0.5, 0.5, 0.75, 0.75, 0.25, 0.75, 0.5, 0.75, 0.75 ],
 				"triangles": [ 15, 0, 1, 16, 1, 2, 15, 1, 16, 19, 2, 3, 16, 2, 19, 22, 3, 4, 19, 3, 22, 22, 4, 5, 14, 15, 16, 17, 16, 19, 14, 16, 17, 20, 19, 22, 17, 19, 20, 23, 22, 5, 20, 22, 23, 23, 5, 6, 13, 14, 17, 18, 17, 20, 13, 17, 18, 21, 20, 23, 18, 20, 21, 24, 23, 6, 21, 23, 24, 24, 6, 7, 12, 13, 18, 11, 18, 21, 12, 18, 11, 10, 21, 24, 11, 21, 10, 9, 24, 7, 10, 24, 9, 9, 7, 8 ],
-				"vertices": [ 1, 5, -194.67, 21.29, 1, 1, 5, -102.08, 38.91, 1, 1, 5, -9.49, 56.54, 1, 1, 5, 83.08, 74.16, 1, 1, 5, 175.67, 91.79, 1, 1, 5, 181.28, 62.32, 1, 1, 5, 186.89, 32.84, 1, 1, 5, 192.5, 3.38, 1, 1, 5, 198.11, -26.09, 1, 1, 5, 105.52, -43.71, 1, 1, 5, 12.94, -61.33, 1, 1, 5, -79.64, -78.96, 1, 1, 5, -172.23, -96.58, 1, 1, 5, -177.84, -67.11, 1, 1, 5, -183.45, -37.64, 1, 1, 5, -189.06, -8.17, 1, 1, 5, -96.47, 9.45, 1, 1, 5, -90.86, -20.01, 1, 1, 5, -85.25, -49.48, 1, 1, 5, -3.88, 27.07, 1, 1, 5, 1.72, -2.39, 1, 1, 5, 7.33, -31.86, 1, 1, 5, 88.7, 44.69, 1, 1, 5, 94.31, 15.22, 1, 1, 5, 99.92, -14.24, 1 ],
+				"vertices": [ 1, 5, -194.66, 21.29, 1, 1, 5, -102.08, 38.90999, 1, 1, 5, -9.47999, 56.54, 1, 1, 5, 83.08, 74.16, 1, 1, 5, 175.66, 91.79, 1, 1, 5, 181.27, 62.31, 1, 1, 5, 186.88, 32.84, 1, 1, 5, 192.5, 3.38, 1, 1, 5, 198.11, -26.09, 1, 1, 5, 105.51, -43.7, 1, 1, 5, 12.93, -61.33, 1, 1, 5, -79.62999, -78.94999, 1, 1, 5, -172.22, -96.58, 1, 1, 5, -177.83, -67.11, 1, 1, 5, -183.44, -37.63, 1, 1, 5, -189.05, -8.17, 1, 1, 5, -96.47, 9.43999, 1, 1, 5, -90.86, -20.01, 1, 1, 5, -85.25, -49.47, 1, 1, 5, -3.88, 27.05999, 1, 1, 5, 1.72, -2.39, 1, 1, 5, 7.32, -31.86, 1, 1, 5, 88.69, 44.68, 1, 1, 5, 94.3, 15.22, 1, 1, 5, 99.91, -14.22999, 1 ],
 				"hull": 16,
 				"edges": [ 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10, 12, 12, 14, 14, 16, 16, 18, 18, 20, 20, 22, 22, 24, 24, 26, 26, 28, 28, 30, 30, 0 ],
 				"width": 377,
@@ -105,9 +114,9 @@
 			"images/spine": {
 				"type": "mesh",
 				"path": "spine",
-				"uvs": [ 1, 0.50124, 1, 1, 0.72412, 1, 0.49981, 1, 0.28581, 1, 0, 1, 0, 0.50124, 0, 0, 0.49207, 0, 1, 0, 0.56941, 0.57414, 0.84014, 0.50124, 0.61841, 0.22584, 0.20589, 0.20154, 0.19558, 0.67134, 0.39152, 0.86574, 0.3941, 0.41214, 0.3941, 0.12864, 0.76021, 0.19344, 0.76537, 0.59034, 0.74474, 0.84144, 0.57716, 0.81714 ],
+				"uvs": [ 1, 0.50124, 1, 1, 0.72412, 1, 0.49981, 1, 0.2858, 1, 0, 1, 0, 0.50124, 0, 0, 0.49206, 0, 1, 0, 0.56941, 0.57414, 0.84013, 0.50124, 0.6184, 0.22584, 0.20588, 0.20153, 0.19558, 0.67133, 0.39151, 0.86574, 0.3941, 0.41214, 0.3941, 0.12863, 0.7602, 0.19344, 0.76537, 0.59034, 0.74474, 0.84144, 0.57716, 0.81713 ],
 				"triangles": [ 17, 7, 8, 18, 8, 9, 13, 7, 17, 12, 8, 18, 17, 8, 12, 16, 17, 12, 13, 17, 16, 6, 7, 13, 6, 13, 16, 18, 9, 0, 11, 18, 0, 10, 16, 12, 12, 18, 11, 10, 12, 11, 19, 10, 11, 14, 6, 16, 14, 16, 10, 21, 10, 19, 15, 14, 10, 20, 21, 19, 21, 15, 10, 5, 6, 14, 4, 5, 14, 15, 4, 14, 3, 15, 21, 4, 15, 3, 2, 21, 20, 3, 21, 2, 19, 11, 0, 0, 20, 19, 1, 20, 0, 2, 20, 1 ],
-				"vertices": [ 191.41, -1.12, 191.41, -60.97, 87.4, -60.97, 2.84, -60.97, -77.82, -60.97, -185.58, -60.97, -185.58, -1.12, -185.58, 59.02, -0.07, 59.02, 191.41, 59.02, 29.09, -9.85, 131.14, -1.12, 47.55, 31.92, -107.96, 34.84, -111.85, -21.53, -37.97, -44.86, -37, 9.57, -37, 43.59, 101.01, 35.81, 102.96, -11.81, 95.18, -41.93, 32, -39.02 ],
+				"vertices": [ 191.41, -1.12, 191.41, -60.97, 87.4, -60.97, 2.82999, -60.97, -77.80999, -60.97, -185.58, -60.97, -185.58, -1.12, -185.58, 59.02, -0.07, 59.02, 191.41, 59.02, 29.09, -9.85, 131.13, -1.12, 47.54, 31.92, -107.94999, 34.84, -111.83999, -21.53, -37.97, -44.86, -37, 9.56, -37, 43.59, 101.01, 35.81, 102.94999, -11.81, 95.18, -41.93, 32, -39.02 ],
 				"hull": 10,
 				"edges": [ 10, 12, 12, 14, 14, 16, 16, 18, 2, 0, 0, 18, 6, 8, 8, 10, 2, 4, 4, 6 ],
 				"width": 377,
@@ -120,7 +129,7 @@
 				"closed": true,
 				"lengths": [ 128.85, 310.24, 465.06, 600.88, 773.97 ],
 				"vertexCount": 15,
-				"vertices": [ 258.07, -42.67, 291.94, -17.53, 326.41, 7.22, 391.41, 2.75, 398.05, 46.72, 399.83, 84.07, 274.03, 106.4, 236.7, 117.74, 199.37, 129.09, 116.55, 135.12, 84.41, 115.47, 50.34, 94.04, 56.53, 1.48, 89.01, -5.59, 122.89, -13.58 ]
+				"vertices": [ 258.07, -42.65999, 291.94, -17.53, 326.41, 7.21, 391.41, 2.75, 398.04, 46.72, 399.82, 84.05999, 274.02, 106.4, 236.69, 117.73, 199.36, 129.08, 116.55, 135.11, 84.41, 115.47, 50.34, 94.04, 56.52, 1.48, 89.01, -5.59, 122.87999, -13.56999 ]
 			}
 		}
 	}
@@ -132,7 +141,7 @@
 				"color": [
 					{ "time": 0, "color": "ffffffff" },
 					{ "time": 0.4333, "color": "ff0901ff" },
-					{ "time": 0.9, "color": "2dff00ff" },
+					{ "time": 0.8999, "color": "2dff00ff" },
 					{ "time": 1.3333, "color": "ffffffff" }
 				]
 			},
@@ -159,40 +168,40 @@
 						"y": 0,
 						"curve": [ 1, 0, 0, 1 ]
 					},
-					{ "time": 0.6666, "x": 96.99, "y": -151 },
+					{ "time": 0.6666, "x": 96.98, "y": -151 },
 					{ "time": 1.3333, "x": 0, "y": 0 }
 				]
 			},
 			"scale": {
 				"scale": [
 					{ "time": 0, "x": 1, "y": 1 },
-					{ "time": 0.6666, "x": 1, "y": 2.34 },
+					{ "time": 0.6666, "x": 1, "y": 2.339 },
 					{ "time": 1.3333, "x": 1, "y": 1 }
 				]
 			},
 			"rotate": {
 				"rotate": [
 					{ "time": 0, "angle": 0 },
-					{ "time": 0.6666, "angle": -125.34 },
+					{ "time": 0.6666, "angle": -125.33 },
 					{ "time": 1.3333, "angle": 0 }
 				]
 			},
 			"shear": {
 				"shear": [
 					{ "time": 0, "x": 0, "y": 0 },
-					{ "time": 0.6666, "x": 29.4, "y": 0 },
+					{ "time": 0.6666, "x": 29.39, "y": 0 },
 					{ "time": 1.3333, "x": 0, "y": 0 }
 				]
 			},
 			"transformconstrainttarget": {
 				"rotate": [
 					{ "time": 0, "angle": 0 },
-					{ "time": 0.6666, "angle": -74.6 },
+					{ "time": 0.6666, "angle": -74.59 },
 					{ "time": 1.3333, "angle": 0 }
 				],
 				"translate": [
 					{ "time": 0, "x": 0, "y": 0 },
-					{ "time": 0.6666, "x": 176.22, "y": -170.34 },
+					{ "time": 0.6666, "x": 176.22, "y": -170.33 },
 					{ "time": 1.3333, "x": 0, "y": 0 }
 				],
 				"scale": [
@@ -202,7 +211,7 @@
 				],
 				"shear": [
 					{ "time": 0, "x": 0, "y": 0 },
-					{ "time": 0.6666, "x": 23.4, "y": 0 },
+					{ "time": 0.6666, "x": 23.39, "y": 0 },
 					{ "time": 1.3333, "x": 0, "y": 0 }
 				]
 			}
@@ -221,11 +230,11 @@
 					{ "time": 1.3333, "position": 1 }
 				],
 				"spacing": [
-					{ "time": 0, "spacing": 40.1 },
+					{ "time": 0, "spacing": 40.0999 },
 					{ "time": 1.3333 }
 				],
 				"mix": [
-					{ "time": 0.6666, "rotateMix": 0.473, "translateMix": 0.473 },
+					{ "time": 0.6666, "rotateMix": 0.4729, "translateMix": 0.4729 },
 					{ "time": 1.3333 }
 				]
 			}
@@ -237,7 +246,7 @@
 						{ "time": 0 },
 						{
 							"time": 0.6666,
-							"vertices": [ 43.09784, 58.11693, 0, 0, 0, 0, -0.653, -57.46399, -50.93396, -47.01593, -43.098, -24.81398, -43.098, -24.81399, -1.0E-5, 1.0E-5, 0, 1.0E-5, 43.09784, 58.11695, 0, 0, 43.09784, 58.11693, 1.0E-5, -1.0E-5, 0, 0, -43.09797, -24.81399, 0, 1.0E-5, 0, 0, 0, 0, 43.09784, 58.11694, 43.09784, 58.11693 ]
+							"vertices": [ 43.09783, 58.11693, 0, 0, 0, 0, -0.653, -57.46398, -50.93395, -47.01593, -43.098, -24.81398, -43.098, -24.81399, -1.0E-5, 1.0E-5, 0, 1.0E-5, 43.09783, 58.11695, 0, 0, 43.09783, 58.11693, 1.0E-5, -1.0E-5, 0, 0, -43.09797, -24.81399, 0, 1.0E-5, 0, 0, 0, 0, 43.09783, 58.11694, 43.09783, 58.11693 ]
 						},
 						{ "time": 1.3333 }
 					]
@@ -248,16 +257,16 @@
 						{
 							"time": 0.3333,
 							"offset": 4,
-							"vertices": [ -9.45232, 83.5133, -3.90008, 44.76635, -3.90008, 44.76635, -3.90008, 44.76635, 0, 0, 0, 0, 0, 0, 0, 0, 5.15667, -29.57763, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -3.90008, 44.76635 ]
+							"vertices": [ -9.45232, 83.51329, -3.90007, 44.76634, -3.90007, 44.76634, -3.90007, 44.76634, 0, 0, 0, 0, 0, 0, 0, 0, 5.15667, -29.57762, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -3.90007, 44.76634 ]
 						},
 						{ "time": 0.6666 },
 						{
 							"time": 0.6679,
-							"vertices": [ -1.21159, -14.52716, -2.76092, -7.75683, -4.30999, -0.98669, 2.38385, 9.78289, -1.56903, 5.49316, -2.93444, 6.75128, -4.30011, 8.00933, -5.66552, 9.2677, -7.031, 10.52575, -3.07855, 14.81561, 4.30986, 0.9859, 5.85907, -5.78436, 7.40826, -12.55462, 5.25317, -13.04797, 3.09832, -13.54083, 0.94323, -14.03411, -0.60595, -7.26379, 1.54907, -6.77056, 3.70422, -6.27746, -2.15495, -0.49346, 0, -3.0E-4, 2.15495, 0.49273, 1.01812, 11.04132, -0.3471, 12.29895, -1.71313, 13.557 ]
+							"vertices": [ -1.21159, -14.52715, -2.76092, -7.75683, -4.30998, -0.98668, 2.38385, 9.78289, -1.56903, 5.49315, -2.93443, 6.75127, -4.3001, 8.00932, -5.66552, 9.2677, -7.031, 10.52575, -3.07855, 14.8156, 4.30986, 0.98589, 5.85906, -5.78435, 7.40825, -12.55461, 5.25317, -13.04796, 3.09832, -13.54082, 0.94322, -14.03411, -0.60594, -7.26379, 1.54907, -6.77055, 3.70422, -6.27746, -2.15495, -0.49345, 0, -3.0E-4, 2.15495, 0.49272, 1.01812, 11.04131, -0.34709, 12.29895, -1.71312, 13.557 ]
 						},
 						{
 							"time": 1.3333,
-							"vertices": [ -10.57342, 25.16797, -9.16716, 14.48486, -7.76074, 3.80212, 131.25482, 21.05285, 128.23181, -0.04534, 121.51611, 0.91668, 114.80023, 1.8789, 108.08459, 2.84124, 101.3689, 3.80316, 104.39154, 24.90191, -21.3623, 2.01171, -22.76855, 12.69433, -24.17505, 23.37719, -20.77471, 23.82476, -17.37414, 24.27276, -13.97393, 24.72009, -12.56768, 14.03753, -15.96789, 13.58978, -19.36809, 13.14215, -11.16095, 3.35455, -14.56134, 2.90704, -17.96167, 2.45922, 124.53894, 22.01538, 117.82336, 22.97705, 111.10718, 23.93933 ]
+							"vertices": [ -10.57341, 25.16797, -9.16716, 14.48486, -7.76073, 3.80211, 131.25482, 21.05285, 128.23181, -0.04534, 121.51611, 0.91667, 114.80023, 1.8789, 108.08458, 2.84123, 101.36889, 3.80315, 104.39154, 24.90191, -21.3623, 2.0117, -22.76855, 12.69433, -24.17505, 23.37718, -20.7747, 23.82476, -17.37413, 24.27276, -13.97393, 24.72009, -12.56768, 14.03752, -15.96788, 13.58977, -19.36809, 13.14214, -11.16094, 3.35454, -14.56134, 2.90704, -17.96166, 2.45921, 124.53894, 22.01538, 117.82335, 22.97705, 111.10717, 23.93932 ]
 						}
 					]
 				}

BIN
examples/test/export/test.skel


Datei-Diff unterdrückt, da er zu groß ist
+ 9 - 1
examples/vine/export/vine.json


BIN
examples/vine/export/vine.skel


BIN
spine-as3/spine-as3-example/lib/spine-as3.swc


+ 20 - 13
spine-as3/spine-as3-example/src/spineboy.json

@@ -1,5 +1,12 @@
 {
-"skeleton": { "hash": "rPoYyBLFG6F0CGZ5wsUEBKDJU9U", "spine": "3.4.02", "width": 470.9, "height": 731.49, "images": "./images/" },
+"skeleton": {
+	"hash": "H7+xXqKKrnDC1fiM9L7F+WiY4ro",
+	"spine": "3.5.03-beta",
+	"width": 470.9,
+	"height": 731.49,
+	"fps": 30,
+	"images": "./images/"
+},
 "bones": [
 	{ "name": "hip", "y": 247.47 },
 	{ "name": "torso", "parent": "hip", "length": 127.55, "rotation": 103.82, "x": -1.61, "y": 4.9, "color": "e0da19ff" },
@@ -89,7 +96,7 @@
 			"head": {
 				"type": "boundingbox",
 				"vertexCount": 6,
-				"vertices": [ -19.14, -70.3, 40.8, -118.07, 257.77, -115.61, 285.16, 57.18, 120.77, 164.95, -5.06, 76.94 ]
+				"vertices": [ -19.13999, -70.3, 40.79999, -118.06999, 257.77, -115.61, 285.16, 57.18, 120.76999, 164.95, -5.05999, 76.94 ]
 			}
 		},
 		"mouth": {
@@ -230,7 +237,7 @@
 					{ "time": 0.4, "angle": -292.35 },
 					{ "time": 0.4333, "angle": -315.84 },
 					{ "time": 0.5, "angle": -347.94 },
-					{ "time": 0.7, "angle": -347.33, "curve": "stepped" },
+					{ "time": 0.6999, "angle": -347.33, "curve": "stepped" },
 					{ "time": 2.2333, "angle": -347.33 },
 					{ "time": 2.7, "angle": -290.68 },
 					{ "time": 2.7666, "angle": -285.1 },
@@ -271,7 +278,7 @@
 					{ "time": 0.4, "angle": -18.92 },
 					{ "time": 0.4333, "angle": -18.28 },
 					{ "time": 0.5, "angle": 60.61 },
-					{ "time": 0.7, "angle": -18.87, "curve": "stepped" },
+					{ "time": 0.6999, "angle": -18.87, "curve": "stepped" },
 					{ "time": 2.2333, "angle": -18.87 },
 					{ "time": 2.7, "angle": -1.95, "curve": "stepped" },
 					{ "time": 4.6666, "angle": -1.95 },
@@ -289,7 +296,7 @@
 				"rotate": [
 					{ "time": 0, "angle": -2.33 },
 					{ "time": 0.2666, "angle": 26.34 },
-					{ "time": 0.7, "angle": -6.07, "curve": "stepped" },
+					{ "time": 0.6999, "angle": -6.07, "curve": "stepped" },
 					{ "time": 2.2333, "angle": -6.07 },
 					{ "time": 2.7, "angle": 5.72, "curve": "stepped" },
 					{ "time": 4.6666, "angle": 5.72 },
@@ -2054,7 +2061,7 @@
 					{ "time": 0.4, "angle": -28.62 },
 					{ "time": 0.5, "angle": -19.3 },
 					{ "time": 0.6, "angle": -3.08 },
-					{ "time": 0.7, "angle": 29.51 },
+					{ "time": 0.6999, "angle": 29.51 },
 					{ "time": 0.8, "angle": 15.79 }
 				],
 				"translate": [
@@ -2078,7 +2085,7 @@
 					{ "time": 0.3, "angle": 15.98 },
 					{ "time": 0.4, "angle": 5.94 },
 					{ "time": 0.5, "angle": -26.76 },
-					{ "time": 0.7, "angle": -55.44 },
+					{ "time": 0.6999, "angle": -55.44 },
 					{ "time": 0.8, "angle": 5.12 }
 				],
 				"translate": [
@@ -2099,14 +2106,14 @@
 					{ "time": 0.4, "angle": 8.69 },
 					{ "time": 0.5, "angle": 12.16 },
 					{ "time": 0.6, "angle": -24.62 },
-					{ "time": 0.7, "angle": -27.26 },
+					{ "time": 0.6999, "angle": -27.26 },
 					{ "time": 0.8, "angle": -34.38 }
 				],
 				"translate": [
 					{ "time": 0, "x": 0, "y": 0 },
 					{ "time": 0.4, "x": 4.08, "y": -9.53 },
 					{ "time": 0.5, "x": 0, "y": 0 },
-					{ "time": 0.7, "x": -21.14, "y": -9.6 },
+					{ "time": 0.6999, "x": -21.14, "y": -9.6 },
 					{ "time": 0.8, "x": 0, "y": 0 }
 				],
 				"scale": [
@@ -2123,7 +2130,7 @@
 					{ "time": 0.4, "angle": 15.95 },
 					{ "time": 0.5, "angle": -9 },
 					{ "time": 0.6, "angle": 26.06 },
-					{ "time": 0.7, "angle": 21.85 },
+					{ "time": 0.6999, "angle": 21.85 },
 					{ "time": 0.8, "angle": 14.26 }
 				],
 				"translate": [
@@ -2146,7 +2153,7 @@
 					{ "time": 0.4, "angle": 13.45 },
 					{ "time": 0.5, "angle": -3.57 },
 					{ "time": 0.6, "angle": -0.97 },
-					{ "time": 0.7, "angle": 2.97 },
+					{ "time": 0.6999, "angle": 2.97 },
 					{ "time": 0.8, "angle": 10.13 }
 				],
 				"translate": [
@@ -2290,7 +2297,7 @@
 						"curve": [ 0.287, 0.37, 0.718, 0.76 ]
 					},
 					{
-						"time": 0.7,
+						"time": 0.6999,
 						"x": -23.93,
 						"y": 10.34,
 						"curve": [ 0.615, 0, 0.75, 1 ]
@@ -2327,7 +2334,7 @@
 					{ "time": 0.4, "angle": 10.22 },
 					{ "time": 0.5, "angle": 11.44 },
 					{ "time": 0.6, "angle": -0.33 },
-					{ "time": 0.7, "angle": 0.15 },
+					{ "time": 0.6999, "angle": 0.15 },
 					{ "time": 0.8, "angle": 12.49 }
 				],
 				"translate": [

+ 132 - 118
spine-as3/spine-as3/src/spine/Bone.as

@@ -44,7 +44,14 @@ public class Bone implements Updatable {
 	public var scaleY:Number;
 	public var shearX:Number;
 	public var shearY:Number;
-	public var appliedRotation:Number;	
+	public var ax:Number;
+	public var ay:Number;
+	public var arotation:Number;
+	public var ascaleX:Number;
+	public var ascaleY:Number;
+	public var ashearX:Number;
+	public var ashearY:Number;
+	public var appliedValid:Boolean;	
 
 	internal var _a:Number;
 	internal var _b:Number;
@@ -52,8 +59,6 @@ public class Bone implements Updatable {
 	internal var _d:Number;
 	internal var _worldX:Number;
 	internal var _worldY:Number;
-	internal var _worldSignX:Number;
-	internal var _worldSignY:Number;
 	
 	internal var _sorted:Boolean;
 
@@ -79,14 +84,26 @@ public class Bone implements Updatable {
 
 	/** Computes the world SRT using the parent bone and the specified local SRT. */
 	public function updateWorldTransformWith (x:Number, y:Number, rotation:Number, scaleX:Number, scaleY:Number, shearX:Number, shearY:Number) : void {
-		appliedRotation = rotation;
-
-		var rotationY:Number = rotation + 90 + shearY;
-		var la:Number = MathUtils.cosDeg(rotation + shearX) * scaleX, lb:Number = MathUtils.cosDeg(rotationY) * scaleY;
-		var lc:Number = MathUtils.sinDeg(rotation + shearX) * scaleX, ld:Number = MathUtils.sinDeg(rotationY) * scaleY;
+		ax = x;
+		ay = y;
+		arotation = rotation;
+		ascaleX = scaleX;
+		ascaleY = scaleY;
+		ashearX = shearX;
+		ashearY = shearY;
+		appliedValid = true;
+		
+		var rotationY:Number = 0, la:Number = 0, lb:Number = 0, lc:Number = 0, ld:Number = 0;
+		var sin:Number = 0, cos:Number = 0;
+		var s:Number = 0;
 		
 		var parent:Bone = _parent;
 		if (!parent) { // Root bone.
+			rotationY = rotation + 90 + shearY;
+			la = MathUtils.cosDeg(rotation + shearX) * scaleX;
+			lb = MathUtils.cosDeg(rotationY) * scaleY;
+			lc = MathUtils.sinDeg(rotation + shearX) * scaleX;
+			ld = MathUtils.sinDeg(rotationY) * scaleY;
 			var skeleton:Skeleton = _skeleton;
 			if (skeleton.flipX) {
 				x = -x;
@@ -102,91 +119,97 @@ public class Bone implements Updatable {
 			_b = lb;
 			_c = lc;
 			_d = ld;
-			_worldX = x;
-			_worldY = y;
-			_worldSignX = scaleX < 0 ? -1 : 1;
-			_worldSignY = scaleY < 0 ? -1 : 1;
+			_worldX = x + skeleton.x;
+			_worldY = y + skeleton.y;	
 			return;
 		}
 
 		var pa:Number = parent._a, pb:Number = parent._b, pc:Number = parent._c, pd:Number = parent._d;
 		_worldX = pa * x + pb * y + parent._worldX;
-		_worldY = pc * x + pd * y + parent._worldY;
-		_worldSignX = parent._worldSignX * (scaleX < 0 ? -1 : 1);
-		_worldSignY = parent._worldSignY * (scaleY < 0 ? -1 : 1);
-
-		if (data.inheritRotation && data.inheritScale) {
+		_worldY = pc * x + pd * y + parent._worldY;		
+
+		switch (_data.transformMode) {
+		case TransformMode.normal: {
+			rotationY = rotation + 90 + shearY;
+			la = MathUtils.cosDeg(rotation + shearX) * scaleX;
+			lb = MathUtils.cosDeg(rotationY) * scaleY;
+			lc = MathUtils.sinDeg(rotation + shearX) * scaleX;
+			ld = MathUtils.sinDeg(rotationY) * scaleY;
 			_a = pa * la + pb * lc;
 			_b = pa * lb + pb * ld;
 			_c = pc * la + pd * lc;
 			_d = pc * lb + pd * ld;
-		} else {
-			if (data.inheritRotation) { // No scale inheritance.
-				pa = 1;
-				pb = 0;
-				pc = 0;
-				pd = 1;
-				do {
-					var cos:Number = MathUtils.cosDeg(parent.appliedRotation), sin:Number = MathUtils.sinDeg(parent.appliedRotation);
-					var temp:Number = pa * cos + pb * sin;
-					pb = pb * cos - pa * sin;
-					pa = temp;
-					temp = pc * cos + pd * sin;
-					pd = pd * cos - pc * sin;
-					pc = temp;
-	
-					if (!parent.data.inheritRotation) break;
-					parent = parent.parent;
-				} while (parent != null);
-				_a = pa * la + pb * lc;
-				_b = pa * lb + pb * ld;
-				_c = pc * la + pd * lc;
-				_d = pc * lb + pd * ld;
-			} else if (data.inheritScale) { // No rotation inheritance.
-				pa = 1;
-				pb = 0;
-				pc = 0;
-				pd = 1;
-				do {
-					cos = MathUtils.cosDeg(parent.appliedRotation), sin = MathUtils.sinDeg(parent.appliedRotation);
-					var psx:Number = parent.scaleX, psy:Number = parent.scaleY;
-					var za:Number = cos * psx, zb:Number = sin * psy, zc:Number = sin * psx, zd:Number = cos * psy;
-					temp = pa * za + pb * zc;
-					pb = pb * zd - pa * zb;
-					pa = temp;
-					temp = pc * za + pd * zc;
-					pd = pd * zd - pc * zb;
-					pc = temp;
-
-					if (psx >= 0) sin = -sin;
-					temp = pa * cos + pb * sin;
-					pb = pb * cos - pa * sin;
-					pa = temp;
-					temp = pc * cos + pd * sin;
-					pd = pd * cos - pc * sin;
-					pc = temp;
-	
-					if (!parent.data.inheritScale) break;
-					parent = parent.parent;
-				} while (parent != null);
-				_a = pa * la + pb * lc;
-				_b = pa * lb + pb * ld;
-				_c = pc * la + pd * lc;
-				_d = pc * lb + pd * ld;
+			return;
+		}
+		case TransformMode.onlyTranslation: {
+			rotationY = rotation + 90 + shearY;
+			_a = MathUtils.cosDeg(rotation + shearX) * scaleX;
+			_b = MathUtils.cosDeg(rotationY) * scaleY;
+			_c = MathUtils.sinDeg(rotation + shearX) * scaleX;
+			_d = MathUtils.sinDeg(rotationY) * scaleY;
+			break;
+		}
+		case TransformMode.noRotationOrReflection: {
+			s = pa * pa + pc * pc;
+			var prx:Number = 0;
+			if (s > 0.0001) {
+				s = Math.abs(pa * pd - pb * pc) / s;
+				pb = pc * s;
+				pd = pa * s;
+				prx = Math.atan2(pc, pa) * MathUtils.radDeg;
 			} else {
-				_a = la;
-				_b = lb;
-				_c = lc;
-				_d = ld;
+				pa = 0;
+				pc = 0;
+				prx = 90 - Math.atan2(pd, pb) * MathUtils.radDeg;
 			}
-			if (_skeleton.flipX) {
-				_a = -_a;
+			var rx:Number = rotation + shearX - prx;
+			var ry:Number = rotation + shearY - prx + 90;
+			la = MathUtils.cosDeg(rx) * scaleX;
+			lb = MathUtils.cosDeg(ry) * scaleY;
+			lc = MathUtils.sinDeg(rx) * scaleX;
+			ld = MathUtils.sinDeg(ry) * scaleY;
+			_a = pa * la - pb * lc;
+			_b = pa * lb - pb * ld;
+			_c = pc * la + pd * lc;
+			_d = pc * lb + pd * ld;
+			break;
+		}
+		case TransformMode.noScale:
+		case TransformMode.noScaleOrReflection: {
+			cos = MathUtils.cosDeg(rotation);
+			sin = MathUtils.sinDeg(rotation);
+			var za:Number = pa * cos + pb * sin;
+			var zc:Number = pc * cos + pd * sin;
+			s = Math.sqrt(za * za + zc * zc);
+			if (s > 0.00001) s = 1 / s;
+			za *= s;
+			zc *= s;
+			s = Math.sqrt(za * za + zc * zc);
+			var r:Number = Math.PI / 2 + Math.atan2(zc, za);
+			var zb:Number = Math.cos(r) * s;
+			var zd:Number = Math.sin(r) * s;
+			la = MathUtils.cosDeg(shearX) * scaleX;
+			lb = MathUtils.cosDeg(90 + shearY) * scaleY;
+			lc = MathUtils.sinDeg(shearX) * scaleX;
+			ld = MathUtils.sinDeg(90 + shearY) * scaleY;
+			_a = za * la + zb * lc;
+			_b = za * lb + zb * ld;
+			_c = zc * la + zd * lc;
+			_d = zc * lb + zd * ld;
+			if (_data.transformMode != TransformMode.noScaleOrReflection ? pa * pd - pb * pc < 0 : skeleton.flipX != skeleton.flipY) {
 				_b = -_b;
-			}
-			if (_skeleton.flipY != yDown) {
-				_c = -_c;
 				_d = -_d;
 			}
+			return;
+		}
+		}
+		if (_skeleton.flipX) {
+			_a = -_a;
+			_b = -_b;
+		}
+		if (_skeleton.flipY != yDown) {
+			_c = -_c;
+			_d = -_d;
 		}
 	}
 
@@ -240,14 +263,6 @@ public class Bone implements Updatable {
 		return _worldY;
 	}
 
-	public function get worldSignX () : Number {
-		return _worldSignX;
-	}
-
-	public function get worldSignY () : Number {
-		return _worldSignY;
-	}
-
 	public function get worldRotationX () : Number {
 		return Math.atan2(_c, _a) * MathUtils.radDeg;
 	}
@@ -257,23 +272,23 @@ public class Bone implements Updatable {
 	}
 
 	public function get worldScaleX () : Number {
-		return Math.sqrt(_a * _a + _b * _b) * _worldSignX;
+		return Math.sqrt(_a * _a + _c * _c);
 	}
 
 	public function get worldScaleY () : Number {
-		return Math.sqrt(_c * _c + _d * _d) * _worldSignY;
+		return Math.sqrt(_b * _b + _d * _d);
 	}
 	
 	public function worldToLocalRotationX () : Number {
 		var parent:Bone = _parent;
-		if (parent == null) return rotation;
+		if (parent == null) return arotation;
 		var pa:Number = parent.a, pb:Number = parent.b, pc:Number = parent.c, pd:Number = parent.d, a:Number = this.a, c:Number = this.c;
 		return Math.atan2(pa * c - pc * a, pd * a - pb * c) * MathUtils.radDeg;
 	}
 
 	public function worldToLocalRotationY () : Number {
 		var parent:Bone = _parent;
-		if (parent == null) return rotation;
+		if (parent == null) return arotation;
 		var pa:Number = parent.a, pb:Number = parent.b, pc:Number = parent.c, pd:Number = parent.d, b:Number = this.b, d:Number = this.d;
 		return Math.atan2(pa * d - pc * b, pd * b - pb * d) * MathUtils.radDeg;
 	}
@@ -285,31 +300,31 @@ public class Bone implements Updatable {
 		this._b = cos * b - sin * d;
 		this._c = sin * a + cos * c;
 		this._d = sin * b + cos * d;
+		this.appliedValid = false;
 	}
 
-	/** Computes the local transform from the world transform. This can be useful to perform processing on the local transform
-	 * after the world transform has been modified directly (eg, by a constraint).
+	/** Computes the individual applied transform values from the world transform. This can be useful to perform processing using
+	 * the applied transform after the world transform has been modified directly (eg, by a constraint).
 	 * <p>
-	 * Some redundant information is lost by the world transform, such as -1,-1 scale versus 180 rotation. The computed local
-	 * transform values may differ from the original values but are functionally the same. */
-	public function updateLocalTransform () : void {
+	 * Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. */
+	internal function updateAppliedTransform () : void {
+		appliedValid = true;
 		var parent:Bone = this.parent;
 		if (parent == null) {
-			x = worldX;
-			y = worldY;
-			rotation = Math.atan2(c, a) * MathUtils.radDeg;
-			scaleX = Math.sqrt(a * a + c * c);
-			scaleY = Math.sqrt(b * b + d * d);
-			var det:Number = a * d - b * c;
-			shearX = 0;
-			shearY = Math.atan2(a * b + c * d, det) * MathUtils.radDeg;
+			ax = worldX;
+			ay = worldY;
+			arotation = Math.atan2(c, a) * MathUtils.radDeg;
+			ascaleX = Math.sqrt(a * a + c * c);
+			ascaleY = Math.sqrt(b * b + d * d);			
+			ashearX = 0;
+			ashearY = Math.atan2(a * b + c * d, a * d - b * c) * MathUtils.radDeg;
 			return;
 		}
 		var pa:Number = parent.a, pb:Number = parent.b, pc:Number = parent.c, pd:Number = parent.d;
 		var pid:Number = 1 / (pa * pd - pb * pc);
 		var dx:Number = worldX - parent.worldX, dy:Number = worldY - parent.worldY;
-		x = (dx * pd * pid - dy * pb * pid);
-		y = (dy * pa * pid - dx * pc * pid);
+		ax = (dx * pd * pid - dy * pb * pid);
+		ay = (dy * pa * pid - dx * pc * pid);
 		var ia:Number = pid * pd;
 		var id:Number = pid * pa;
 		var ib:Number = pid * pb;
@@ -318,20 +333,19 @@ public class Bone implements Updatable {
 		var rb:Number = ia * b - ib * d;
 		var rc:Number = id * c - ic * a;
 		var rd:Number = id * d - ic * b;
-		shearX = 0;
-		scaleX = Math.sqrt(ra * ra + rc * rc);
+		ashearX = 0;
+		ascaleX = Math.sqrt(ra * ra + rc * rc);
 		if (scaleX > 0.0001) {
-			det = ra * rd - rb * rc;
-			scaleY = det / scaleX;
-			shearY = Math.atan2(ra * rb + rc * rd, det) * MathUtils.radDeg;
-			rotation = Math.atan2(rc, ra) * MathUtils.radDeg;
+			var det:Number = ra * rd - rb * rc;
+			ascaleY = det /ascaleX;
+			ashearY = Math.atan2(ra * rb + rc * rd, det) * MathUtils.radDeg;
+			arotation = Math.atan2(rc, ra) * MathUtils.radDeg;
 		} else {
-			scaleX = 0;
-			scaleY = Math.sqrt(rb * rb + rd * rd);
-			shearY = 0;
-			rotation = 90 - Math.atan2(rd, rb) * MathUtils.radDeg;
-		}
-		appliedRotation = rotation;
+			ascaleX = 0;
+			ascaleY = Math.sqrt(rb * rb + rd * rd);
+			ashearY = 0;
+			arotation = 90 - Math.atan2(rd, rb) * MathUtils.radDeg;
+		}		
 	}
 
 	public function worldToLocal (world:Vector.<Number>) : void {

+ 1 - 2
spine-as3/spine-as3/src/spine/BoneData.as

@@ -42,8 +42,7 @@ public class BoneData {
 	public var scaleY:Number = 1;
 	public var shearX:Number;
 	public var shearY:Number;	
-	public var inheritRotation:Boolean = true;
-	public var inheritScale:Boolean = true;
+	public var transformMode:TransformMode = TransformMode.normal;
 
 	/** @param parent May be null. */
 	public function BoneData (index:int, name:String, parent:BoneData) {

+ 37 - 0
spine-as3/spine-as3/src/spine/Constraint.as

@@ -0,0 +1,37 @@
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, Esoteric Software
+ * All rights reserved.
+ *
+ * You are granted a perpetual, non-exclusive, non-sublicensable, and
+ * non-transferable license to use, install, execute, and perform the Spine
+ * Runtimes software and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes 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 SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) 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.
+ *****************************************************************************/
+
+package spine {
+
+public interface Constraint extends Updatable {
+	function getOrder () : Number;
+}
+
+}

+ 24 - 19
spine-as3/spine-as3/src/spine/IkConstraint.as

@@ -30,14 +30,12 @@
 
 package spine {
 
-public class IkConstraint implements Updatable {
+public class IkConstraint implements Constraint {
 	internal var _data:IkConstraintData;
 	public var bones:Vector.<Bone>;
 	public var target:Bone;
 	public var mix:Number;
 	public var bendDirection:int;
-	
-	public var level:int;	
 
 	public function IkConstraint (data:IkConstraintData, skeleton:Skeleton) {
 		if (data == null) throw new ArgumentError("data cannot be null.");
@@ -66,6 +64,10 @@ public class IkConstraint implements Updatable {
 			break;
 		}
 	}
+	
+	public function getOrder() : Number {
+		return _data.order;
+	}
 
 	public function get data () : IkConstraintData {
 		return _data;
@@ -78,17 +80,18 @@ public class IkConstraint implements Updatable {
 	/** Adjusts the bone rotation so the tip is as close to the target position as possible. The target is specified in the world
 	 * coordinate system. */
 	static public function apply1 (bone:Bone, targetX:Number, targetY:Number, alpha:Number) : void {
-		var pp:Bone = bone.parent;
-		var id:Number = 1 / (pp.a * pp.d - pp.b * pp.c);
-		var x:Number = targetX - pp.worldX, y:Number = targetY - pp.worldY;
-		var tx:Number = (x * pp.d - y * pp.b) * id - bone.x, ty:Number = (y * pp.a - x * pp.c) * id - bone.y;
-		var rotationIK:Number = Math.atan2(ty, tx) * MathUtils.radDeg - bone.shearX - bone.rotation;
-		if (bone.scaleX < 0) rotationIK += 180;
+		if (!bone.appliedValid) bone.updateAppliedTransform();
+		var p:Bone = bone.parent;
+		var id:Number = 1 / (p.a * p.d - p.b * p.c);
+		var x:Number = targetX - p.worldX, y:Number = targetY - p.worldY;
+		var tx:Number = (x * p.d - y * p.b) * id - bone.ax, ty:Number = (y * p.a - x * p.c) * id - bone.ay;
+		var rotationIK:Number = Math.atan2(ty, tx) * MathUtils.radDeg - bone.ashearX - bone.arotation;
+		if (bone.ascaleX < 0) rotationIK += 180;
 		if (rotationIK > 180)
 			rotationIK -= 360;
 		else if (rotationIK < -180) rotationIK += 360;
-		bone.updateWorldTransformWith(bone.x, bone.y, bone.rotation + rotationIK * alpha, bone.scaleX, bone.scaleY, bone.shearX,
-			bone.shearY);
+		bone.updateWorldTransformWith(bone.ax, bone.ay, bone.arotation + rotationIK * alpha, bone.ascaleX, bone.ascaleY, bone.ashearX,
+			bone.ashearY);
 	}
 
 	/** Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as possible. The
@@ -99,7 +102,9 @@ public class IkConstraint implements Updatable {
 			child.updateWorldTransform();
 			return;
 		}
-		var px:Number = parent.x, py:Number = parent.y, psx:Number = parent.scaleX, psy:Number = parent.scaleY, csx:Number = child.scaleX;;
+		if (!parent.appliedValid) parent.updateAppliedTransform();
+		if (!child.appliedValid) child.updateAppliedTransform();
+		var px:Number = parent.ax, py:Number = parent.ay, psx:Number = parent.ascaleX, psy:Number = parent.ascaleY, csx:Number = child.ascaleX;
 		var os1:int, os2:int, s2:int;
 		if (psx < 0) {
 			psx = -psx;
@@ -118,14 +123,14 @@ public class IkConstraint implements Updatable {
 			os2 = 180;
 		} else
 			os2 = 0;
-		var cx:Number = child.x, cy:Number, cwx:Number, cwy:Number, a:Number = parent.a, b:Number = parent.b, c:Number = parent.c, d:Number = parent.d;
+		var cx:Number = child.ax, cy:Number, cwx:Number, cwy:Number, a:Number = parent.a, b:Number = parent.b, c:Number = parent.c, d:Number = parent.d;
 		var u:Boolean = Math.abs(psx - psy) <= 0.0001;
 		if (!u) {
 			cy = 0;
 			cwx = a * cx + parent.worldX;
 			cwy = c * cx + parent.worldY;
 		} else {
-			cy = child.y;
+			cy = child.ay;
 			cwx = a * cx + b * cy + parent.worldX;
 			cwy = c * cx + d * cy + parent.worldY;
 		}
@@ -212,18 +217,18 @@ public class IkConstraint implements Updatable {
 			}
 		}
 		var os:Number = Math.atan2(cy, cx) * s2;
-		var rotation:Number = parent.rotation;
+		var rotation:Number = parent.arotation;
 		a1 = (a1 - os) * MathUtils.radDeg + os1 - rotation;
 		if (a1 > 180)
 			a1 -= 360;
 		else if (a1 < -180) a1 += 360;
-		parent.updateWorldTransformWith(px, py, rotation + a1 * alpha, parent.scaleX, parent.scaleY, 0, 0);
-		rotation = child.rotation;
-		a2 = ((a2 + os) * MathUtils.radDeg - child.shearX) * s2 + os2 - rotation;
+		parent.updateWorldTransformWith(px, py, rotation + a1 * alpha, parent.ascaleX, parent.ascaleY, 0, 0);
+		rotation = child.arotation;
+		a2 = ((a2 + os) * MathUtils.radDeg - child.ashearX) * s2 + os2 - rotation;
 		if (a2 > 180)
 			a2 -= 360;
 		else if (a2 < -180) a2 += 360;
-		child.updateWorldTransformWith(cx, cy, rotation + a2 * alpha, child.scaleX, child.scaleY, child.shearX, child.shearY);
+		child.updateWorldTransformWith(cx, cy, rotation + a2 * alpha, child.ascaleX, child.ascaleY, child.ashearX, child.ashearY);
 	}
 }
 

+ 1 - 0
spine-as3/spine-as3/src/spine/IkConstraintData.as

@@ -32,6 +32,7 @@ package spine {
 
 public class IkConstraintData {
 	internal var _name:String;
+	public var order:Number;
 	public var bones:Vector.<BoneData> = new Vector.<BoneData>();
 	public var target:BoneData;
 	public var bendDirection:int = 1;

+ 4 - 0
spine-as3/spine-as3/src/spine/MathUtils.as

@@ -47,6 +47,10 @@ public class MathUtils {
 		if (value > max) return max;
 		return value;
 	}
+	
+	static public function signum (value: Number):Number {
+		return value > 0 ? 1 : value < 0 ? -1 : 0;
+	}
 }
 
 }

+ 21 - 9
spine-as3/spine-as3/src/spine/PathConstraint.as

@@ -31,7 +31,7 @@
 package spine {
 import spine.attachments.PathAttachment;
 
-public class PathConstraint implements Updatable {
+public class PathConstraint implements Constraint {
 	private static const NONE:int = -1, BEFORE:int = -2, AFTER:int = -3;
 
 	internal var _data:PathConstraintData;
@@ -100,16 +100,21 @@ public class PathConstraint implements Updatable {
 		}
 
 		var positions:Vector.<Number> = computeWorldPositions(attachment, spacesCount, tangents,
-			data.positionMode == PositionMode.percent, spacingMode == SpacingMode.percent);
-		var skeleton:Skeleton = target.skeleton;
-		var skeletonX:Number = skeleton.x, skeletonY:Number = skeleton.y;
+			data.positionMode == PositionMode.percent, spacingMode == SpacingMode.percent);		
 		var boneX:Number = positions[0], boneY:Number = positions[1], offsetRotation:Number = data.offsetRotation;
-		var tip:Boolean = rotateMode == RotateMode.chain && offsetRotation == 0;
+		var tip:Boolean = false;
+		if (offsetRotation == 0)
+			tip = rotateMode == RotateMode.chain;
+		else {
+			tip = false;
+			var pa:Bone = target.bone;
+			offsetRotation *= pa.a * pa.d - pa.b * pa.c > 0 ? MathUtils.degRad : -MathUtils.degRad;
+		}
 		var p:Number;
 		for (i = 0, p = 3; i < boneCount; i++, p += 3) {
 			bone = bones[i];
-			bone._worldX += (boneX - skeletonX - bone.worldX) * translateMix;
-			bone._worldY += (boneY - skeletonY - bone.worldY) * translateMix;
+			bone._worldX += (boneX - bone.worldX) * translateMix;
+			bone._worldY += (boneY - bone.worldY) * translateMix;
 			x = positions[p]; y = positions[p + 1]; var dx:Number = x - boneX, dy:Number = y - boneY;
 			if (scale) {
 				length = lengths[i];
@@ -129,13 +134,15 @@ public class PathConstraint implements Updatable {
 					r = positions[p + 2];
 				else
 					r = Math.atan2(dy, dx);
-				r -= Math.atan2(c, a) - offsetRotation * MathUtils.degRad;
+				r -= Math.atan2(c, a);	
 				if (tip) {
 					cos = Math.cos(r);
 					sin = Math.sin(r);
 					length = bone.data.length;
 					boneX += (length * (cos * a - sin * c) - dx) * rotateMix;
 					boneY += (length * (sin * a + cos * c) - dy) * rotateMix;
+				} else {
+					r += offsetRotation;
 				}
 				if (r > Math.PI)
 					r -= (Math.PI * 2);
@@ -149,6 +156,7 @@ public class PathConstraint implements Updatable {
 				bone._c = sin * a + cos * c;
 				bone._d = sin * b + cos * d;
 			}
+			bone.appliedValid = false;
 		}
 	}
 
@@ -394,7 +402,7 @@ public class PathConstraint implements Updatable {
 
 	private function addCurvePosition (p:Number, x1:Number, y1:Number, cx1:Number, cy1:Number, cx2:Number, cy2:Number, x2:Number, y2:Number,
 		out:Vector.<Number>, o:int, tangents:Boolean) : void {
-		if (p == 0) p = 0.0001;
+		if (p == 0 || isNaN(p)) p = 0.0001;
 		var tt:Number = p * p, ttt:Number = tt * p, u:Number = 1 - p, uu:Number = u * u, uuu:Number = uu * u;
 		var ut:Number = u * p, ut3:Number = ut * 3, uut3:Number = u * ut3, utt3:Number = ut3 * p;
 		var x:Number = x1 * uuu + cx1 * uut3 + cx2 * utt3 + x2 * ttt, y:Number = y1 * uuu + cy1 * uut3 + cy2 * utt3 + y2 * ttt;
@@ -410,6 +418,10 @@ public class PathConstraint implements Updatable {
 	public function get data () : PathConstraintData {
 		return _data;
 	}
+	
+	public function getOrder () : Number {
+		return _data.order;
+	}
 
 	public function toString () : String {
 		return _data.name;

+ 1 - 0
spine-as3/spine-as3/src/spine/PathConstraintData.as

@@ -32,6 +32,7 @@ package spine {
 
 public dynamic class PathConstraintData {
 	internal var _name:String;
+	public var order:Number;
 	internal var _bones:Vector.<BoneData> = new Vector.<BoneData>();
 	public var target:SlotData;
 	public var positionMode:PositionMode;

+ 60 - 49
spine-unity/Assets/spine-unity/SkeletonUtility/Editor/SkeletonUtilitySubmeshRendererInspector.cs → spine-as3/spine-as3/src/spine/Pool.as

@@ -1,49 +1,60 @@
-/******************************************************************************
- * Spine Runtimes Software License v2.5
- *
- * Copyright (c) 2013-2016, Esoteric Software
- * All rights reserved.
- *
- * You are granted a perpetual, non-exclusive, non-sublicensable, and
- * non-transferable license to use, install, execute, and perform the Spine
- * Runtimes software and derivative works solely for personal or internal
- * use. Without the written permission of Esoteric Software (see Section 2 of
- * the Spine Software License Agreement), you may not (a) modify, translate,
- * adapt, or develop new applications using the Spine Runtimes or otherwise
- * create derivative works or improvements of the Spine Runtimes 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 SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF
- * USE, DATA, OR PROFITS) 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.
- *****************************************************************************/
-
-using UnityEngine;
-using UnityEditor;
-using Spine.Unity.Editor;
-
-namespace Spine.Unity.Modules {
-	[CustomEditor(typeof(SkeletonUtilitySubmeshRenderer))]
-	public class SkeletonUtilitySubmeshRendererInspector : UnityEditor.Editor {
-		public SpineInspectorUtility.SerializedSortingProperties sorting;
-
-		void OnEnable () {			
-			sorting = new SpineInspectorUtility.SerializedSortingProperties((target as Component).GetComponent<Renderer>());
-		}
-
-		public override void OnInspectorGUI () {
-			EditorGUILayout.HelpBox("SkeletonUtilitySubmeshRenderer is now obsolete. We recommend using SkeletonRenderSeparator.", MessageType.Info);
-			SpineInspectorUtility.SortingPropertyFields(sorting, true);
-		}
-	}
-}
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, Esoteric Software
+ * All rights reserved.
+ *
+ * You are granted a perpetual, non-exclusive, non-sublicensable, and
+ * non-transferable license to use, install, execute, and perform the Spine
+ * Runtimes software and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes 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 SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) 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.
+ *****************************************************************************/
+
+package spine {
+	
+public class Pool {
+	internal var items:Vector.<Object> = new Vector.<Object>();
+	internal var instantiator:Function;
+	
+	public function Pool(instantiator:Function) {
+		this.instantiator = instantiator;
+	}
+	
+	public function obtain (): Object {
+		return this.items.length > 0 ? this.items.pop() : this.instantiator();
+	}
+	
+	public function free (item:Object):void {
+		if (item is Poolable) Poolable(item).reset();
+		items.push(item);
+	}
+	
+	public function freeAll (items:Vector):void {
+		for (var i:int = 0; i < items.length; i++) {
+			free(items[i]);
+		}
+	}
+	
+	public function clear ():void {
+		items.length = 0;
+	}
+}
+}

+ 5 - 0
spine-as3/spine-as3/src/spine/Poolable.as

@@ -0,0 +1,5 @@
+package spine {
+public interface Poolable {
+	function reset ():void;
+}
+}

+ 103 - 83
spine-as3/spine-as3/src/spine/Skeleton.as

@@ -38,10 +38,11 @@ public class Skeleton {
 	public var bones:Vector.<Bone>;
 	public var slots:Vector.<Slot>;
 	public var drawOrder:Vector.<Slot>;
-	public var ikConstraints:Vector.<IkConstraint>, ikConstraintsSorted:Vector.<IkConstraint>;
+	public var ikConstraints:Vector.<IkConstraint>;
 	public var transformConstraints:Vector.<TransformConstraint>;
 	public var pathConstraints:Vector.<PathConstraint>;
 	private var _updateCache:Vector.<Updatable> = new Vector.<Updatable>();
+	private var _updateCacheReset:Vector.<Bone> = new Vector.<Bone>();
 	private var _skin:Skin;
 	public var r:Number = 1, g:Number = 1, b:Number = 1, a:Number = 1;
 	public var time:Number = 0;
@@ -75,8 +76,7 @@ public class Skeleton {
 			drawOrder[drawOrder.length] = slot;
 		}
 		
-		ikConstraints = new Vector.<IkConstraint>();
-		ikConstraintsSorted = new Vector.<IkConstraint>();
+		ikConstraints = new Vector.<IkConstraint>();		
 		for each (var ikConstraintData:IkConstraintData in data.ikConstraints)
 			ikConstraints.push(new IkConstraint(ikConstraintData, this));
 		
@@ -98,101 +98,110 @@ public class Skeleton {
 		updateCache.length = 0;
 
 		var bones:Vector.<Bone> = this.bones;
-		for (var i:int = 0, n:int = bones.length; i < n; i++)
+		var i:Number = 0;
+		var n:Number = 0;
+		for (i = 0, n = bones.length; i < n; i++)
 			bones[i]._sorted = false;
 
 		// IK first, lowest hierarchy depth first.
-		var ikConstraints:Vector.<IkConstraint> = this.ikConstraintsSorted;
-		ikConstraints.length = 0;
-		for each (var c:IkConstraint in this.ikConstraints)
-			ikConstraints.push(c);
-		var ikCount:int = ikConstraints.length;
-		var level:int;
-		for (i = 0, n = ikCount; i < n; i++) {
-			var ik:IkConstraint = ikConstraints[i];
-			var bone:Bone = ik.bones[0].parent;
-			for (level = 0; bone != null; level++)
-				bone = bone.parent;
-			ik.level = level;
-		}
-		var ii:int;
-		for (i = 1; i < ikCount; i++) {
-			ik = ikConstraints[i];
-			level = ik.level;
-			for (ii = i - 1; ii >= 0; ii--) {
-				var other:IkConstraint = ikConstraints[ii];
-				if (other.level < level) break;
-				ikConstraints[ii + 1] = other;
+		var ikConstraints:Vector.<IkConstraint> = this.ikConstraints;
+		var transformConstraints:Vector.<TransformConstraint> = this.transformConstraints;
+		var pathConstraints:Vector.<PathConstraint> = this.pathConstraints;
+		var ikCount:Number = ikConstraints.length, transformCount:Number = transformConstraints.length, pathCount:Number = pathConstraints.length;
+		var constraintCount:Number = ikCount + transformCount + pathCount;
+		
+		outer:			
+		for (i = 0; i < constraintCount; i++) {
+			var ii:Number = 0;
+			for (ii = 0; ii < ikCount; ii++) {
+				var ikConstraint:IkConstraint = ikConstraints[ii];
+				if (ikConstraint.data.order == i) {
+					sortIkConstraint(ikConstraint);
+					continue outer;
+				}
+			}
+			for (ii = 0; ii < transformCount; ii++) {
+				var transformConstraint:TransformConstraint = transformConstraints[ii];
+				if (transformConstraint.data.order == i) {
+					sortTransformConstraint(transformConstraint);
+					continue outer;
+				}
+			}
+			for (ii = 0; ii < pathCount; ii++) {
+				var pathConstraint:PathConstraint = pathConstraints[ii];
+				if (pathConstraint.data.order == i) {
+					sortPathConstraint(pathConstraint);
+					continue outer;
+				}
 			}
-			ikConstraints[ii + 1] = ik;
 		}
-		for (i = 0, n = ikConstraints.length; i < n; i++) {
-			var ikConstraint:IkConstraint = ikConstraints[i];
-			var target:Bone = ikConstraint.target;
-			sortBone(target);
-
-			var constrained:Vector.<Bone> = ikConstraint.bones;
-			var parent:Bone = constrained[0];
-			sortBone(parent);
+		
+		for (i = 0, n = bones.length; i < n; i++)
+			sortBone(bones[i]);
+	}
+	
+	private function sortIkConstraint (constraint:IkConstraint): void {
+		var target:Bone = constraint.target;
+		sortBone(target);
 
-			updateCache.push(ikConstraint);
+		var constrained:Vector.<Bone> = constraint.bones;
+		var parent:Bone = constrained[0];
+		sortBone(parent);
 
-			sortReset(parent.children);
-			constrained[constrained.length - 1]._sorted = true;
+		if (constrained.length > 1) {
+			var child:Bone = constrained[constrained.length - 1];
+			if (!(_updateCache.indexOf(child) > -1)) _updateCacheReset.push(child);
 		}
 
-		var pathConstraints:Vector.<PathConstraint> = this.pathConstraints;
-		for (i = 0, n = pathConstraints.length; i < n; i++) {
-			var pathConstraint:PathConstraint = pathConstraints[i];
-
-			var slot:Slot = pathConstraint.target;
-			var slotIndex:int = slot.data.index;
-			var slotBone:Bone = slot.bone;
-			if (skin != null) sortPathConstraintAttachment(skin, slotIndex, slotBone);
-			if (_data.defaultSkin != null && _data.defaultSkin != skin)
-				sortPathConstraintAttachment(_data.defaultSkin, slotIndex, slotBone);
-				
-			var nn:int;
-			for (ii = 0, nn = _data.skins.length; ii < nn; ii++)
-				sortPathConstraintAttachment(_data.skins[ii], slotIndex, slotBone);
-
-			var attachment:PathAttachment = slot.attachment as PathAttachment;
-			if (attachment != null) sortPathConstraintAttachment2(attachment, slotBone);
-
-			constrained = pathConstraint.bones;
-			var boneCount:int = constrained.length;
-			for (ii = 0; ii < boneCount; ii++)
-				sortBone(constrained[ii]);
-
-			updateCache.push(pathConstraint);
-
-			for (ii = 0; ii < boneCount; ii++)
-				sortReset(constrained[ii].children);
-			for (ii = 0; ii < boneCount; ii++)
-				constrained[ii]._sorted = true;
-		}
+		_updateCache.push(constraint);
 
-		var transformConstraints:Vector.<TransformConstraint> = this.transformConstraints;
-		for (i = 0, n = transformConstraints.length; i < n; i++) {
-			var transformConstraint:TransformConstraint = transformConstraints[i];
+		sortReset(parent.children);
+		constrained[constrained.length - 1]._sorted = true;
+	}
 
-			sortBone(transformConstraint.target);
+	private function sortPathConstraint (constraint:PathConstraint): void {
+		var slot:Slot = constraint.target;
+		var slotIndex:Number = slot.data.index;
+		var slotBone:Bone = slot.bone;
+		if (skin != null) sortPathConstraintAttachment(skin, slotIndex, slotBone);
+		if (data.defaultSkin != null && data.defaultSkin != skin)
+			sortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone);
+		var ii:Number = 0;
+		var nn:Number = 0;		
+		for (ii = 0, nn = data.skins.length; ii < nn; ii++)
+			sortPathConstraintAttachment(data.skins[ii], slotIndex, slotBone);
+
+		var attachment:Attachment = slot.attachment;
+		if (attachment is PathAttachment) sortPathConstraintAttachment2(attachment, slotBone);
+
+		var constrained:Vector.<Bone> = constraint.bones;
+		var boneCount:Number = constrained.length;
+		for (ii = 0; ii < boneCount; ii++)
+			sortBone(constrained[ii]);
+
+		_updateCache.push(constraint);
+
+		for (ii = 0; ii < boneCount; ii++)
+			sortReset(constrained[ii].children);
+		for (ii = 0; ii < boneCount; ii++)
+			constrained[ii]._sorted = true;
+	}
 
-			constrained = transformConstraint.bones;
-			boneCount = constrained.length;
-			for (ii = 0; ii < boneCount; ii++)
-				sortBone(constrained[ii]);
+	private function sortTransformConstraint (constraint:TransformConstraint): void {
+		sortBone(constraint.target);
 
-			updateCache.push(transformConstraint);
+		var constrained:Vector.<Bone> = constraint.bones;
+		var boneCount:Number = constrained.length;
+		var ii:Number = 0;
+		for (ii = 0; ii < boneCount; ii++)
+			sortBone(constrained[ii]);
 
-			for (ii = 0; ii < boneCount; ii++)
-				sortReset(constrained[ii].children);
-			for (ii = 0; ii < boneCount; ii++)
-				constrained[ii]._sorted = true;
-		}
+		_updateCache.push(constraint);
 
-		for (i = 0, n = bones.length; i < n; i++)
-			sortBone(bones[i]);
+		for (ii = 0; ii < boneCount; ii++)
+			sortReset(constrained[ii].children);
+		for (ii = 0; ii < boneCount; ii++)
+			constrained[ii]._sorted = true;
 	}
 	
 	private function sortPathConstraintAttachment (skin:Skin, slotIndex:int, slotBone:Bone) : void {
@@ -240,6 +249,17 @@ public class Skeleton {
 
 	/** Updates the world transform for each bone and applies constraints. */
 	public function updateWorldTransform () : void {
+		var updateCacheReset:Vector.<Bone> = this._updateCacheReset;
+		for each (var bone:Bone in updateCacheReset) {
+			bone.ax = bone.x;
+			bone.ay = bone.y;
+			bone.arotation = bone.rotation;
+			bone.ascaleX = bone.scaleX;
+			bone.ascaleY = bone.scaleY;
+			bone.ashearX = bone.shearX;
+			bone.ashearY = bone.shearY;
+			bone.appliedValid = true;
+		}
 		for each (var updatable:Updatable in _updateCache)
 			updatable.update();
 	}

+ 8 - 1
spine-as3/spine-as3/src/spine/SkeletonBounds.as

@@ -68,7 +68,14 @@ public class SkeletonBounds {
 			boundingBox.computeWorldVertices(slot, polygon.vertices);
 		}
 
-		if (updateAabb) aabbCompute();
+		if (updateAabb) 
+			aabbCompute();
+		else {
+			minX = Number.MIN_VALUE;
+			minY = Number.MIN_VALUE;
+			maxX = Number.MAX_VALUE;
+			maxY = Number.MAX_VALUE;
+		}
 	}
 
 	private function aabbCompute () : void {

+ 3 - 0
spine-as3/spine-as3/src/spine/SkeletonData.as

@@ -46,6 +46,9 @@ public class SkeletonData {
 	public var width:Number, height:Number;
 	public var version:String, hash:String;
 	
+	public var fps:Number;
+	public var imagesPath:String;
+	
 	public function SkeletonData () {		
 	}
 

+ 8 - 4
spine-as3/spine-as3/src/spine/SkeletonJson.as

@@ -89,7 +89,9 @@ public class SkeletonJson {
 			skeletonData.hash = skeletonMap["hash"];
 			skeletonData.version = skeletonMap["spine"];
 			skeletonData.width = skeletonMap["width"] || 0;
-			skeletonData.height = skeletonMap["height"] || 0;			
+			skeletonData.height = skeletonMap["height"] || 0;
+			skeletonData.fps = skeletonMap["fps"] || 0;
+			skeletonData.imagesPath = skeletonMap["images"];		
 		}			
 
 		// Bones.
@@ -110,8 +112,7 @@ public class SkeletonJson {
 			boneData.scaleY = boneMap.hasOwnProperty("scaleY") ? boneMap["scaleY"] : 1;
 			boneData.shearX = Number(boneMap["shearX"] || 0);
 			boneData.shearY = Number(boneMap["shearY"] || 0);
-			boneData.inheritRotation = boneMap.hasOwnProperty("inheritRotation") ? Boolean(boneMap["inheritRotation"]) : true;
-			boneData.inheritScale = boneMap.hasOwnProperty("inheritScale") ? Boolean(boneMap["inheritScale"]) : true;			
+			boneData.transformMode = TransformMode[boneMap["transform"] || "normal"];			
 			skeletonData.bones.push(boneData);
 		}
 		
@@ -139,6 +140,7 @@ public class SkeletonJson {
 		// IK constraints.
 		for each (var constraintMap:Object in root["ik"]) {
 			var ikConstraintData:IkConstraintData = new IkConstraintData(constraintMap["name"]);
+			ikConstraintData.order = constraintMap["order"] || 0;
 
 			for each (boneName in constraintMap["bones"]) {
 				var bone:BoneData = skeletonData.findBone(boneName);
@@ -158,6 +160,7 @@ public class SkeletonJson {
 		// Transform constraints.
 		for each (constraintMap in root["transform"]) {
 			var transformConstraintData:TransformConstraintData = new TransformConstraintData(constraintMap["name"]);
+			transformConstraintData.order = constraintMap["order"] || 0;
 
 			for each (boneName in constraintMap["bones"]) {
 				bone = skeletonData.findBone(boneName);
@@ -186,6 +189,7 @@ public class SkeletonJson {
 		// Path constraints.
 		for each (constraintMap in root["path"]) {
 			var pathConstraintData:PathConstraintData = new PathConstraintData(constraintMap["name"]);
+			pathConstraintData.order = constraintMap["order"] || 0;
 
 			for each (boneName in constraintMap["bones"]) {
 				bone = skeletonData.findBone(boneName);
@@ -249,7 +253,7 @@ public class SkeletonJson {
 				var eventData:EventData = new EventData(eventName);
 				eventData.intValue = eventMap["int"] || 0;
 				eventData.floatValue = eventMap["float"] || 0;
-				eventData.stringValue = eventMap["string"] || null;
+				eventData.stringValue = eventMap["string"] || "";
 				skeletonData.events.push(eventData);
 			}
 		}

+ 23 - 9
spine-as3/spine-as3/src/spine/TransformConstraint.as

@@ -30,7 +30,7 @@
 
 package spine {
 
-public class TransformConstraint implements Updatable {
+public class TransformConstraint implements Constraint {
 	internal var _data:TransformConstraintData;
 	internal var _bones:Vector.<Bone>;
 	public var target:Bone;
@@ -62,13 +62,17 @@ public class TransformConstraint implements Updatable {
 		var rotateMix:Number = this.rotateMix, translateMix:Number = this.translateMix, scaleMix:Number = this.scaleMix, shearMix:Number = this.shearMix;
 		var target:Bone = this.target;
 		var ta:Number = target.a, tb:Number = target.b, tc:Number = target.c, td:Number = target.d;
+		var degRadReflect:Number = ta * td - tb * tc > 0 ? MathUtils.degRad : -MathUtils.degRad;
+		var offsetRotation:Number = data.offsetRotation * degRadReflect;
+		var offsetShearY:Number = data.offsetShearY * degRadReflect;
 		var bones:Vector.<Bone> = this._bones;
 		for (var i:int = 0, n:int = bones.length; i < n; i++) {
 			var bone:Bone = bones[i];
+			var modified:Boolean = false;
 
-			if (rotateMix > 0) {
+			if (rotateMix != 0) {
 				var a:Number = bone.a, b:Number = bone.b, c:Number = bone.c, d:Number = bone.d;
-				var r:Number = Math.atan2(tc, ta) - Math.atan2(c, a) + data.offsetRotation * MathUtils.degRad;
+				var r:Number = Math.atan2(tc, ta) - Math.atan2(c, a) + offsetRotation;
 				if (r > Math.PI)
 					r -= Math.PI * 2;
 				else if (r < -Math.PI) r += Math.PI * 2;
@@ -78,27 +82,30 @@ public class TransformConstraint implements Updatable {
 				bone._b = cos * b - sin * d;
 				bone._c = sin * a + cos * c;
 				bone._d = sin * b + cos * d;
+				modified = true;
 			}
 
-			if (translateMix > 0) {
+			if (translateMix != 0) {
 				_temp[0] = data.offsetX;
 				_temp[1] = data.offsetY;
 				target.localToWorld(_temp);
 				bone._worldX += (_temp[0] - bone.worldX) * translateMix;
 				bone._worldY += (_temp[1] - bone.worldY) * translateMix;
+				modified = true;
 			}
 
 			if (scaleMix > 0) {
-				var bs:Number = Math.sqrt(bone.a * bone.a + bone.c * bone.c);
+				var s:Number = Math.sqrt(bone.a * bone.a + bone.c * bone.c);
 				var ts:Number = Math.sqrt(ta * ta + tc * tc);
-				var s:Number = bs > 0.00001 ? (bs + (ts - bs + data.offsetScaleX) * scaleMix) / bs : 0;
+				if (s > 0.00001) s = (s + (ts - s + data.offsetScaleX) * scaleMix) / s;
 				bone._a *= s;
 				bone._c *= s;
-				bs = Math.sqrt(bone.b * bone.b + bone.d * bone.d);
+				s = Math.sqrt(bone.b * bone.b + bone.d * bone.d);
 				ts = Math.sqrt(tb * tb + td * td);
-				s = bs > 0.00001 ? (bs + (ts - bs + data.offsetScaleY) * scaleMix) / bs : 0;
+				if (s > 0.00001) s = (s + (ts - s + data.offsetScaleY) * scaleMix) / s;
 				bone._b *= s;
 				bone._d *= s;
+				modified = true;
 			}
 
 			if (shearMix > 0) {
@@ -108,13 +115,20 @@ public class TransformConstraint implements Updatable {
 				if (r > Math.PI)
 					r -= Math.PI * 2;
 				else if (r < -Math.PI) r += Math.PI * 2;
-				r = by + (r + data.offsetShearY * MathUtils.degRad) * shearMix;
+				r = by + (r + offsetShearY) * shearMix;
 				s = Math.sqrt(b * b + d * d);
 				bone._b = Math.cos(r) * s;
 				bone._d = Math.sin(r) * s;
+				modified = true;
 			}
+			
+			if (modified) bone.appliedValid = false;
 		}
 	}
+	
+	public function getOrder () : Number {
+		return _data.order;
+	}
 
 	public function get data () : TransformConstraintData {
 		return _data;

+ 1 - 0
spine-as3/spine-as3/src/spine/TransformConstraintData.as

@@ -32,6 +32,7 @@ package spine {
 
 public class TransformConstraintData {
 	internal var _name:String;
+	public var order:Number;
 	internal var _bones:Vector.<BoneData> = new Vector.<BoneData>();
 	public var target:BoneData;
 	public var rotateMix:Number;

+ 9 - 39
spine-unity/Assets/spine-unity/SkeletonUtility/SkeletonUtilitySubmeshRenderer.cs → spine-as3/spine-as3/src/spine/TransformMode.as

@@ -28,44 +28,14 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
 
-using UnityEngine;
-
-namespace Spine.Unity.Modules {
-	[ExecuteInEditMode]
-	public class SkeletonUtilitySubmeshRenderer : MonoBehaviour {
-		[System.NonSerialized]
-		public Mesh mesh;
-		public int submeshIndex = 0;
-		public Material hiddenPassMaterial;
-		Renderer cachedRenderer;
-		MeshFilter filter;
-		Material[] sharedMaterials;
-
-		void Awake () {
-			cachedRenderer = GetComponent<Renderer>();
-			filter = GetComponent<MeshFilter>();
-			sharedMaterials = new Material[0];
-		}
-
-		public void SetMesh (Renderer parentRenderer, Mesh mesh, Material mat) {
-			if (cachedRenderer == null)
-				return;
-
-			cachedRenderer.enabled = true;
-			filter.sharedMesh = mesh;
-			if (cachedRenderer.sharedMaterials.Length != parentRenderer.sharedMaterials.Length) {
-				sharedMaterials = parentRenderer.sharedMaterials;
-			}
-
-			for (int i = 0; i < sharedMaterials.Length; i++) {
-				if (i == submeshIndex)
-					sharedMaterials[i] = mat;
-				else
-					sharedMaterials[i] = hiddenPassMaterial;
-			}
-
-			cachedRenderer.sharedMaterials = sharedMaterials;
-		}
-	}
+package spine {
+
+public class TransformMode {
+	public static const normal:TransformMode = new TransformMode();
+	public static const onlyTranslation:TransformMode = new TransformMode();
+	public static const noRotationOrReflection:TransformMode = new TransformMode();
+	public static const noScale:TransformMode = new TransformMode();
+	public static const noScaleOrReflection:TransformMode = new TransformMode();
+}
 
 }

+ 3 - 17
spine-as3/spine-as3/src/spine/animation/Animation.as

@@ -34,7 +34,7 @@ import spine.Skeleton;
 
 public class Animation {
 	internal var _name:String;
-	private var _timelines:Vector.<Timeline>;
+	public var _timelines:Vector.<Timeline>;
 	public var duration:Number;
 
 	public function Animation (name:String, timelines:Vector.<Timeline>, duration:Number) {
@@ -50,7 +50,7 @@ public class Animation {
 	}
 
 	/** Poses the skeleton at the specified time for this animation. */
-	public function apply (skeleton:Skeleton, lastTime:Number, time:Number, loop:Boolean, events:Vector.<Event>) : void {
+	public function apply (skeleton:Skeleton, lastTime:Number, time:Number, loop:Boolean, events:Vector.<Event>, alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void {
 		if (skeleton == null) throw new ArgumentError("skeleton cannot be null.");
 
 		if (loop && duration != 0) {
@@ -59,21 +59,7 @@ public class Animation {
 		}
 
 		for (var i:int = 0, n:int = timelines.length; i < n; i++)
-			timelines[i].apply(skeleton, lastTime, time, events, 1);
-	}
-
-	/** Poses the skeleton at the specified time for this animation mixed with the current pose.
-	 * @param alpha The amount of this animation that affects the current pose. */
-	public function mix (skeleton:Skeleton, lastTime:Number, time:Number, loop:Boolean, events:Vector.<Event>, alpha:Number) : void {
-		if (skeleton == null) throw new ArgumentError("skeleton cannot be null.");
-
-		if (loop && duration != 0) {
-			time %= duration;
-			if (lastTime > 0) lastTime %= duration;
-		}
-
-		for (var i:int = 0, n:int = timelines.length; i < n; i++)
-			timelines[i].apply(skeleton, lastTime, time, events, alpha);
+			timelines[i].apply(skeleton, lastTime, time, events, alpha, setupPose, mixingOut);
 	}
 
 	public function get name () : String {

+ 494 - 148
spine-as3/spine-as3/src/spine/animation/AnimationState.as

@@ -29,213 +29,559 @@
  *****************************************************************************/
 
 package spine.animation {
-
+import spine.MathUtils;
+import spine.Bone;
+import spine.Pool;
+import flash.utils.Dictionary;
 import spine.Event;
 import spine.Skeleton;
 
 public class AnimationState {
-	private var _data:AnimationStateData;
-	private var _tracks:Vector.<TrackEntry> = new Vector.<TrackEntry>();
-	private var _events:Vector.<Event> = new Vector.<Event>();
+	internal static var emptyAnimation:Animation = new Animation("<empty>", new Vector.<Timeline>(), 0);
+	
+	public var data:AnimationStateData;
+	public var tracks:Vector.<TrackEntry> = new Vector.<TrackEntry>();
+	internal var events:Vector.<Event> = new Vector.<Event>();
 	public var onStart:Listeners = new Listeners();
+	public var onInterrupt:Listeners = new Listeners();
 	public var onEnd:Listeners = new Listeners();
+	public var onDispose:Listeners = new Listeners();
 	public var onComplete:Listeners = new Listeners();
 	public var onEvent:Listeners = new Listeners();
+	internal var queue:EventQueue;
+	internal var propertyIDs:Dictionary = new Dictionary();
+	internal var animationsChanged:Boolean;
 	public var timeScale:Number = 1;
-
-	public function AnimationState (data:AnimationStateData) {
-		if (!data) throw new ArgumentError("data cannot be null.");
-		_data = data;
+	internal var trackEntryPool:Pool;
+	
+	public function AnimationState(data:AnimationStateData) {
+		if (data == null) throw new ArgumentError("data can not be null");
+		this.data = data;
+		this.queue = new EventQueue(this);
+		this.trackEntryPool = new Pool(function():Object {
+			return new TrackEntry();
+		});
 	}
-
-	public function update (delta:Number) : void {
+	
+	public function update (delta:Number):void {
 		delta *= timeScale;
-		for (var i:int = 0; i < _tracks.length; i++) {
-			var current:TrackEntry = _tracks[i];
-			if (!current) continue;
-
-			current.time += delta * current.timeScale;
-			if (current.previous) {
-				var previousDelta:Number = delta * current.previous.timeScale;
-				current.previous.time += previousDelta;
-				current.mixTime += previousDelta;
+		for (var i:int = 0, n:int = tracks.length; i < n; i++) {
+			var current:TrackEntry = tracks[i];
+			if (current == null) continue;
+
+			current.animationLast = current.nextAnimationLast;
+			current.trackLast = current.nextTrackLast;
+
+			var currentDelta:Number = delta * current.timeScale;
+
+			if (current.delay > 0) {
+				current.delay -= currentDelta;
+				if (current.delay > 0) continue;
+				currentDelta = -current.delay;
+				current.delay = 0;
 			}
 
 			var next:TrackEntry = current.next;
-			if (next) {
-				next.time = current.lastTime - next.delay;
-				if (next.time >= 0) setCurrent(i, next);
+			if (next != null) {
+				// When the next entry's delay is passed, change to the next entry, preserving leftover time.
+				var nextTime:Number = current.trackLast - next.delay;
+				if (nextTime >= 0) {
+					next.delay = 0;
+					next.trackTime = nextTime + delta * next.timeScale;
+					current.trackTime += currentDelta;
+					setCurrent(i, next);
+					while (next.mixingFrom != null) {
+						next.mixTime += currentDelta;
+						next = next.mixingFrom;
+					}
+					continue;
+				}
+				updateMixingFrom(current, delta, true);
 			} else {
-				// End non-looping animation when it reaches its end time and there is no next entry.
-				if (!current.loop && current.lastTime >= current.endTime) clearTrack(i);
+				updateMixingFrom(current, delta, true);
+				// Clear the track when there is no next entry, the track end time is reached, and there is no mixingFrom.
+				if (current.trackLast >= current.trackEnd && current.mixingFrom == null) {
+					tracks[i] = null;
+					queue.end(current);
+					disposeNext(current);
+					continue;
+				}
 			}
-		}
-	}
 
-	public function apply (skeleton:Skeleton) : void {
-		for (var i:int = 0; i < _tracks.length; i++) {
-			var current:TrackEntry = _tracks[i];
-			if (!current) continue;
+			current.trackTime += currentDelta;
+		}
 
-			_events.length = 0;
+		queue.drain();
+	}
+	
+	private function updateMixingFrom (entry:TrackEntry, delta:Number, canEnd:Boolean):void {
+		var from:TrackEntry = entry.mixingFrom;
+		if (from == null) return;
+
+		if (canEnd && entry.mixTime >= entry.mixDuration && entry.mixTime > 0) {
+			queue.end(from);
+			var newFrom:TrackEntry = from.mixingFrom;
+			entry.mixingFrom = newFrom;
+			if (newFrom == null) return;
+			entry.mixTime = from.mixTime;
+			entry.mixDuration = from.mixDuration;
+			from = newFrom;
+		}
 
-			var time:Number = current.time;
-			var lastTime:Number = current.lastTime;
-			var endTime:Number = current.endTime;
-			var loop:Boolean = current.loop;
-			if (!loop && time > endTime) time = endTime;
+		from.animationLast = from.nextAnimationLast;
+		from.trackLast = from.nextTrackLast;
+		var mixingFromDelta:Number = delta * from.timeScale;
+		from.trackTime += mixingFromDelta;
+		entry.mixTime += mixingFromDelta;
 
-			var previous:TrackEntry = current.previous;
-			if (!previous) {
-				if (current.mix == 1)
-					current.animation.apply(skeleton, current.lastTime, time, loop, _events);
-				else
-					current.animation.mix(skeleton, current.lastTime, time, loop, _events, current.mix);
+		updateMixingFrom(from, delta, canEnd && from.alpha == 1);
+	}
+	
+	public function apply (skeleton:Skeleton):void {
+		if (skeleton == null) throw new ArgumentError("skeleton cannot be null.");
+		if (animationsChanged) _animationsChanged();
+
+		var events:Vector.<Event> = this.events;
+
+		for (var i:int = 0, n:int = tracks.length; i < n; i++) {
+			var current:TrackEntry = tracks[i];
+			if (current == null || current.delay > 0) continue;
+
+			// Apply mixing from entries first.
+			var mix:Number = current.alpha;
+			if (current.mixingFrom != null) mix *= applyMixingFrom(current, skeleton);
+
+			// Apply current entry.
+			var animationLast:Number = current.animationLast, animationTime:Number = current.getAnimationTime();
+			var timelineCount:int = current.animation.timelines.length;
+			var timelines:Vector.<Timeline> = current.animation.timelines;
+			var ii:int = 0;
+			if (mix == 1) {
+				for (ii = 0; ii < timelineCount; ii++)
+					Timeline(timelines[ii]).apply(skeleton, animationLast, animationTime, events, 1, true, false);
 			} else {
-				var previousTime:Number = previous.time;
-				if (!previous.loop && previousTime > previous.endTime) previousTime = previous.endTime;
-				previous.animation.apply(skeleton, previousTime, previousTime, previous.loop, null);
-
-				var alpha:Number = current.mixTime / current.mixDuration * current.mix;
-				if (alpha >= 1) {
-					alpha = 1;
-					current.previous = null;
+				var firstFrame:Boolean = current.timelinesRotation.length == 0;
+				if (firstFrame) current.timelinesRotation.length = timelineCount << 1;
+				var timelinesRotation:Vector.<Number> = current.timelinesRotation;
+
+				var timelinesFirst:Vector.<Boolean> = current.timelinesFirst;
+				for (ii = 0; ii < timelineCount; ii++) {
+					var timeline:Timeline = timelines[ii];
+					if (timeline is RotateTimeline) {
+						applyRotateTimeline(timeline, skeleton, animationTime, mix, timelinesFirst[ii], timelinesRotation, ii << 1,
+							firstFrame);
+					} else
+						timeline.apply(skeleton, animationLast, animationTime, events, mix, timelinesFirst[ii], false);
 				}
-				current.animation.mix(skeleton, current.lastTime, time, loop, _events, alpha);
 			}
+			queueEvents(current, animationTime);
+			current.nextAnimationLast = animationTime;
+			current.nextTrackLast = current.trackTime;
+		}
 
-			for each (var event:Event in _events) {
-				if (current.onEvent != null) current.onEvent(i, event);
-				onEvent.invoke(i, event);
-			}
+		queue.drain();
+	}
+	
+	private function applyMixingFrom (entry:TrackEntry, skeleton:Skeleton):Number {
+		var from:TrackEntry = entry.mixingFrom;
+		if (from.mixingFrom != null) applyMixingFrom(from, skeleton);
+
+		var mix:Number = 0;
+		if (entry.mixDuration == 0) // Single frame mix to undo mixingFrom changes.
+			mix = 1;
+		else {
+			mix = entry.mixTime / entry.mixDuration;
+			if (mix > 1) mix = 1;			
+		}
 
-			// Check if completed the animation or a loop iteration.
-			if (loop ? (lastTime % endTime > time % endTime) : (lastTime < endTime && time >= endTime)) {
-				var count:int = (int)(time / endTime);
-				if (current.onComplete != null) current.onComplete(i, count);
-				onComplete.invoke(i, count);
+		var events:Vector.<Event> = mix < from.eventThreshold ? this.events : null;
+		var attachments:Boolean = mix < from.attachmentThreshold, drawOrder:Boolean = mix < from.drawOrderThreshold;
+		var animationLast:Number = from.animationLast, animationTime:Number = from.getAnimationTime();
+		var timelineCount:int = from.animation.timelines.length;
+		var timelines:Vector.<Timeline> = from.animation.timelines;
+		var timelinesFirst:Vector.<Boolean> = from.timelinesFirst;
+		var alpha:Number = from.alpha * entry.mixAlpha * (1 - mix);
+
+		var firstFrame:Boolean = from.timelinesRotation.length == 0;
+		if (firstFrame) from.timelinesRotation.length = timelineCount << 1;
+		var timelinesRotation:Vector.<Number> = from.timelinesRotation;
+
+		for (var i:int = 0; i < timelineCount; i++) {
+			var timeline:Timeline = timelines[i];
+			var setupPose:Boolean = timelinesFirst[i];
+			if (timeline is RotateTimeline)
+				applyRotateTimeline(timeline, skeleton, animationTime, alpha, setupPose, timelinesRotation, i << 1, firstFrame);
+			else {
+				if (!setupPose) {
+					if (!attachments && timeline is AttachmentTimeline) continue;
+					if (!drawOrder && timeline is DrawOrderTimeline) continue;
+				}
+				timeline.apply(skeleton, animationLast, animationTime, events, alpha, setupPose, true);
 			}
-
-			current.lastTime = current.time;
 		}
-	}
 
-	public function clearTracks () : void {
-		for (var i:int = 0, n:int = _tracks.length; i < n; i++)
-			clearTrack(i);
-		_tracks.length = 0;
+		queueEvents(from, animationTime);
+		from.nextAnimationLast = animationTime;
+		from.nextTrackLast = from.trackTime;
+
+		return mix;
 	}
+	
+	private function applyRotateTimeline (timeline:Timeline, skeleton:Skeleton, time:Number, alpha:Number, setupPose:Boolean,
+		timelinesRotation:Vector.<Number>, i:int, firstFrame:Boolean):void {
+		if (alpha == 1) {
+			timeline.apply(skeleton, 0, time, null, 1, setupPose, false);
+			return;
+		}
 
-	public function clearTrack (trackIndex:int) : void {
-		if (trackIndex >= _tracks.length) return;
-		var current:TrackEntry = _tracks[trackIndex];
-		if (!current) return;
+		var rotateTimeline:RotateTimeline = RotateTimeline(timeline);
+		var frames:Vector.<Number> = rotateTimeline.frames;		
+		var bone:Bone = skeleton.bones[rotateTimeline.boneIndex];
+		if (time < frames[0]) {
+			if (setupPose) bone.rotation = bone.data.rotation;
+			return;
+		}
 
-		if (current.onEnd != null) current.onEnd(trackIndex);
-		onEnd.invoke(trackIndex);
+		var r2:Number;
+		if (time >= frames[frames.length - RotateTimeline.ENTRIES]) // Time is after last frame.
+			r2 = bone.data.rotation + frames[frames.length + RotateTimeline.PREV_ROTATION];
+		else {
+			// Interpolate between the previous frame and the current frame.
+			var frame:int = Animation.binarySearch(frames, time, RotateTimeline.ENTRIES);
+			var prevRotation:Number = frames[frame + RotateTimeline.PREV_ROTATION];
+			var frameTime:Number = frames[frame];
+			var percent:Number = rotateTimeline.getCurvePercent((frame >> 1) - 1,
+				1 - (time - frameTime) / (frames[frame + RotateTimeline.PREV_TIME] - frameTime));
+
+			r2 = frames[frame + RotateTimeline.ROTATION] - prevRotation;
+			r2 -= (16384 - int((16384.499999999996 - r2 / 360))) * 360;
+			r2 = prevRotation + r2 * percent + bone.data.rotation;
+			r2 -= (16384 - int((16384.499999999996 - r2 / 360))) * 360;
+		}
 
-		_tracks[trackIndex] = null;
+		// Mix between rotations using the direction of the shortest route on the first frame while detecting crosses.
+		var r1:Number = setupPose ? bone.data.rotation : bone.rotation;
+		var total:Number, diff:Number = r2 - r1;
+		if (diff == 0) {
+			if (firstFrame) {
+				timelinesRotation[i] = 0;
+				total = 0;
+			} else
+				total = timelinesRotation[i];
+		} else {
+			diff -= (16384 - int((16384.499999999996 - diff / 360))) * 360;
+			var lastTotal:Number, lastDiff:Number;
+			if (firstFrame) {
+				lastTotal = 0;
+				lastDiff = diff;
+			} else {
+				lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops.
+				lastDiff = timelinesRotation[i + 1]; // Difference between bones.
+			}
+			var current:Boolean = diff > 0, dir:Boolean = lastTotal >= 0;
+			// Detect cross at 0 (not 180).			
+			if (MathUtils.signum(lastDiff) != MathUtils.signum(diff) && Math.abs(lastDiff) <= 90) {
+				// A cross after a 360 rotation is a loop.
+				if (Math.abs(lastTotal) > 180) lastTotal += 360 * MathUtils.signum(lastTotal);
+				dir = current;
+			}
+			total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal.
+			if (dir != current) total += 360 * MathUtils.signum(lastTotal);
+			timelinesRotation[i] = total;
+		}
+		timelinesRotation[i + 1] = diff;
+		r1 += total * alpha;
+		bone.rotation = r1 - (16384 - int((16384.499999999996 - r1 / 360))) * 360;
 	}
+	
+	private function queueEvents (entry:TrackEntry, animationTime:Number):void {
+		var animationStart:Number = entry.animationStart, animationEnd:Number = entry.animationEnd;
+		var duration:Number = animationEnd - animationStart;
+		var trackLastWrapped:Number = entry.trackLast % duration;
+
+		// Queue events before complete.
+		var events:Vector.<Event> = this.events;
+		var event:Event;
+		var i:int = 0, n:int = events.length;
+		for (; i < n; i++) {
+			event = events[i];
+			if (event.time < trackLastWrapped) break;
+			if (event.time > animationEnd) continue; // Discard events outside animation start/end.
+			queue.event(entry, event);
+		}
 
-	private function expandToIndex (index:int) : TrackEntry {
-		if (index < _tracks.length) return _tracks[index];
-		while (index >= _tracks.length)
-			_tracks[_tracks.length] = null;
-		return null;
-	}
+		// Queue complete if completed a loop iteration or the animation.
+		if (entry.loop ? (trackLastWrapped > entry.trackTime % duration)
+			: (animationTime >= animationEnd && entry.animationLast < animationEnd)) {
+			queue.complete(entry);
+		}
 
-	private function setCurrent (index:int, entry:TrackEntry) : void {
-		var current:TrackEntry = expandToIndex(index);
-		if (current) {
-			var previous:TrackEntry = current.previous;
-			current.previous = null;
-
-			if (current.onEnd != null) current.onEnd(index);
-			onEnd.invoke(index);
-
-			entry.mixDuration = _data.getMix(current.animation, entry.animation);
-			if (entry.mixDuration > 0) {
-				entry.mixTime = 0;
-				// If a mix is in progress, mix from the closest animation.
-				if (previous != null && current.mixTime / current.mixDuration < 0.5) {
-					entry.previous = previous;
-					previous = current;
-				} else
-					entry.previous = current;
-			}
+		// Queue events after complete.
+		for (; i < n; i++) {
+			event = events[i];
+			if (event.time < animationStart) continue; // Discard events outside animation start/end.
+			queue.event(entry, events[i]);
+		}
+		events.length = 0;
+	}
+	
+	public function clearTracks ():void {
+		queue.drainDisabled = true;
+		for (var i:int = 0, n:int = tracks.length; i < n; i++)
+			clearTrack(i);
+		tracks.length = 0;
+		queue.drainDisabled = false;
+		queue.drain();
+	}
+	
+	public function clearTrack (trackIndex:int):void {
+		if (trackIndex >= tracks.length) return;
+		var current:TrackEntry = tracks[trackIndex];
+		if (current == null) return;
+
+		queue.end(current);
+
+		disposeNext(current);
+
+		var entry:TrackEntry = current;
+		while (true) {
+			var from:TrackEntry = entry.mixingFrom;
+			if (from == null) break;
+			queue.end(from);
+			entry.mixingFrom = null;
+			entry = from;
 		}
 
-		_tracks[index] = entry;
+		tracks[current.trackIndex] = null;
 
-		if (entry.onStart != null) entry.onStart(index);
-		onStart.invoke(index);
+		queue.drain();
 	}
+	
+	
+	private function setCurrent (index:int, current:TrackEntry):void {
+		var from:TrackEntry = expandToIndex(index);
+		tracks[index] = current;
+
+		if (from != null) {
+			queue.interrupt(from);
+			current.mixingFrom = from;
+			current.mixTime = 0;
+
+			from.timelinesRotation.length = 0;
+
+			// If not completely mixed in, set mixAlpha so mixing out happens from current mix to zero.
+			if (from.mixingFrom != null) current.mixAlpha *= Math.min(from.mixTime / from.mixDuration, 1);
+		}
 
-	public function setAnimationByName (trackIndex:int, animationName:String, loop:Boolean) : TrackEntry {
-		var animation:Animation = _data._skeletonData.findAnimation(animationName);
-		if (!animation) throw new ArgumentError("Animation not found: " + animationName);
+		queue.start(current);
+	}
+	
+	public function setAnimationByName (trackIndex:int, animationName:String, loop:Boolean):TrackEntry {
+		var animation:Animation = data.skeletonData.findAnimation(animationName);
+		if (animation == null) throw new ArgumentError("Animation not found: " + animationName);
 		return setAnimation(trackIndex, animation, loop);
 	}
-
-	/** Set the current animation. Any queued animations are cleared. */
-	public function setAnimation (trackIndex:int, animation:Animation, loop:Boolean) : TrackEntry {
-		var entry:TrackEntry = new TrackEntry();
-		entry.animation = animation;
-		entry.loop = loop;
-		entry.endTime = animation.duration;
+	
+	public function setAnimation (trackIndex:int, animation:Animation, loop:Boolean):TrackEntry {
+		if (animation == null) throw new ArgumentError("animation cannot be null.");
+		var current:TrackEntry = expandToIndex(trackIndex);
+		if (current != null) {
+			if (current.nextTrackLast == -1) {
+				// Don't mix from an entry that was never applied.
+				tracks[trackIndex] = null;
+				queue.interrupt(current);
+				queue.end(current);
+				disposeNext(current);
+				current = null;
+			} else
+				disposeNext(current);
+		}
+		var entry:TrackEntry = trackEntry(trackIndex, animation, loop, current);
 		setCurrent(trackIndex, entry);
+		queue.drain();
 		return entry;
 	}
-
-	public function addAnimationByName (trackIndex:int, animationName:String, loop:Boolean, delay:Number) : TrackEntry {
-		var animation:Animation = _data._skeletonData.findAnimation(animationName);
-		if (!animation) throw new ArgumentError("Animation not found: " + animationName);
+	
+	public function addAnimationByName (trackIndex:int, animationName:String, loop:Boolean, delay:Number):TrackEntry {
+		var animation:Animation = data.skeletonData.findAnimation(animationName);
+		if (animation == null) throw new ArgumentError("Animation not found: " + animationName);
 		return addAnimation(trackIndex, animation, loop, delay);
 	}
-
-	/** Adds an animation to be played delay seconds after the current or last queued animation.
-	 * @param delay May be <= 0 to use duration of previous animation minus any mix duration plus the negative delay. */
-	public function addAnimation (trackIndex:int, animation:Animation, loop:Boolean, delay:Number) : TrackEntry {
-		var entry:TrackEntry = new TrackEntry();
-		entry.animation = animation;
-		entry.loop = loop;
-		entry.endTime = animation.duration;
+	
+	public function addAnimation (trackIndex:int, animation:Animation, loop:Boolean, delay:Number):TrackEntry {
+		if (animation == null) throw new ArgumentError("animation cannot be null.");
 
 		var last:TrackEntry = expandToIndex(trackIndex);
-		if (last) {
-			while (last.next)
+		if (last != null) {
+			while (last.next != null)
 				last = last.next;
-			last.next = entry;
-		} else
-			_tracks[trackIndex] = entry;
+		}
 
-		if (delay <= 0) {
-			if (last)
-				delay += last.endTime - _data.getMix(last.animation, animation);
-			else
-				delay = 0;
+		var entry:TrackEntry = trackEntry(trackIndex, animation, loop, last);
+
+		if (last == null) {
+			setCurrent(trackIndex, entry);
+			queue.drain();
+		} else {
+			last.next = entry;
+			if (delay <= 0) {
+				var duration:Number = last.animationEnd - last.animationStart;
+				if (duration != 0)
+					delay += duration * (1 + (int)(last.trackTime / duration)) - data.getMix(last.animation, animation);
+				else
+					delay = 0;
+			}
 		}
-		entry.delay = delay;
 
+		entry.delay = delay;
 		return entry;
 	}
+	
+	public function setEmptyAnimation (trackIndex:int, mixDuration:Number):TrackEntry {
+		var entry:TrackEntry = setAnimation(trackIndex, emptyAnimation, false);
+		entry.mixDuration = mixDuration;
+		entry.trackEnd = mixDuration;
+		return entry;
+	}
+	
+	public function addEmptyAnimation (trackIndex:int, mixDuration:Number, delay:Number):TrackEntry {
+		if (delay <= 0) delay -= mixDuration;
+		var entry:TrackEntry = addAnimation(trackIndex, emptyAnimation, false, delay);
+		entry.mixDuration = mixDuration;
+		entry.trackEnd = mixDuration;
+		return entry;
+	}
+	
+	public function setEmptyAnimations (mixDuration:Number):void {
+		queue.drainDisabled = true;
+		for (var i:int = 0, n:int = tracks.length; i < n; i++) {
+			var current:TrackEntry = tracks[i];
+			if (current != null) setEmptyAnimation(current.trackIndex, mixDuration);
+		}
+		queue.drainDisabled = false;
+		queue.drain();
+	}
+	
+	private function expandToIndex (index:int):TrackEntry {
+		if (index < tracks.length) return tracks[index];		
+		tracks.length = index + 1;
+		return null;
+	}
+	
+	private function trackEntry (trackIndex:int, animation:Animation, loop:Boolean, last:TrackEntry):TrackEntry {
+		var entry:TrackEntry = TrackEntry(trackEntryPool.obtain());
+		entry.trackIndex = trackIndex;
+		entry.animation = animation;
+		entry.loop = loop;
 
-	/** May be null. */
-	public function getCurrent (trackIndex:int) : TrackEntry {
-		if (trackIndex >= _tracks.length) return null;
-		return _tracks[trackIndex];
+		entry.eventThreshold = 0;
+		entry.attachmentThreshold = 0;
+		entry.drawOrderThreshold = 0;
+
+		entry.animationStart = 0;
+		entry.animationEnd = animation.duration;
+		entry.animationLast = -1;
+		entry.nextAnimationLast = -1;
+
+		entry.delay = 0;
+		entry.trackTime = 0;
+		entry.trackLast = -1;
+		entry.nextTrackLast = -1;
+		entry.trackEnd = loop ? int.MAX_VALUE : entry.animationEnd;
+		entry.timeScale = 1;
+
+		entry.alpha = 1;
+		entry.mixAlpha = 1;
+		entry.mixTime = 0;
+		entry.mixDuration = last == null ? 0 : data.getMix(last.animation, animation);
+		return entry;
 	}
+	
+	private function disposeNext (entry:TrackEntry):void{
+		var next:TrackEntry = entry.next;
+		while (next != null) {
+			queue.dispose(next);
+			next = next.next;
+		}
+		entry.next = null;
+	}
+	
+	private function _animationsChanged ():void {
+		animationsChanged = false;
+
+		var propertyIDs:Dictionary = this.propertyIDs = new Dictionary();
 
-	public function toString () : String {
-		var buffer:String = "";
-		for each (var entry:TrackEntry in _tracks) {
-			if (!entry) continue;
-			if (buffer.length > 0) buffer += ", ";
-			buffer += entry.toString();
+		// Compute timelinesFirst from lowest to highest track entries.
+		var i:int = 0, n:int = tracks.length;
+		for (var key:String in propertyIDs) {
+    		delete propertyIDs[key];
+		}
+		var entry:TrackEntry;
+		for (; i < n; i++) { // Find first non-null entry.
+			entry = tracks[i];
+			if (entry == null) continue;
+			setTimelinesFirst(entry);
+			i++;
+			break;
+		}
+		for (; i < n; i++) { // Rest of entries.
+			entry = tracks[i];
+			if (entry != null) checkTimelinesFirst(entry);
 		}
-		if (buffer.length == 0) return "<none>";
-		return buffer;
+	}
+	
+	private function setTimelinesFirst (entry:TrackEntry):void {
+		if (entry.mixingFrom != null) {
+			setTimelinesFirst(entry.mixingFrom);
+			checkTimelinesUsage(entry, entry.timelinesFirst);
+			return;
+		}
+		var propertyIDs:Dictionary = this.propertyIDs;
+		var timelines:Vector.<Timeline> = entry.animation.timelines;
+		var n:int = timelines.length;
+		var usage:Vector.<Boolean> = entry.timelinesFirst;
+		usage.length = n;
+		for (var i:int = 0; i < n; i++) {
+			var id:String = timelines[i].getPropertyId().toString();
+			propertyIDs[id] = id;
+			usage[i] = true;
+		}
+	}
+	
+	private function checkTimelinesFirst (entry:TrackEntry):void {
+		if (entry.mixingFrom != null) checkTimelinesFirst(entry.mixingFrom);
+		checkTimelinesUsage(entry, entry.timelinesFirst);
+	}
+	
+	private function checkTimelinesUsage (entry:TrackEntry, usageArray:Vector.<Boolean>):void {
+		var propertyIDs:Dictionary = this.propertyIDs;
+		var timelines:Vector.<Timeline> = entry.animation.timelines;
+		var n:int = timelines.length;
+		var usage:Vector.<Boolean> = usageArray;
+		usageArray.length = n;
+		for (var i:int = 0; i < n; i++) {
+			var id:String = timelines[i].getPropertyId().toString();
+			usage[i] = !propertyIDs.hasOwnProperty(id);			
+			propertyIDs[id] = id;
+		}
+	}
+	
+	public function getCurrent (trackIndex:int):TrackEntry {
+		if (trackIndex >= tracks.length) return null;
+		return tracks[trackIndex];
+	}
+	
+	public function clearListeners ():void {
+		onStart.listeners.length = 0;
+		onInterrupt.listeners.length = 0;
+		onEnd.listeners.length = 0;
+		onDispose.listeners.length = 0;
+		onComplete.listeners.length = 0;
+		onEvent.listeners.length = 0;	
+	}
+	
+	public function clearListenerNotifications ():void {
+		queue.clear();
 	}
 }
-
 }

+ 21 - 3
spine-as3/spine-as3/src/spine/animation/AttachmentTimeline.as

@@ -29,6 +29,7 @@
  *****************************************************************************/
 
 package spine.animation {
+import spine.Slot;
 import spine.Event;
 import spine.Skeleton;
 
@@ -45,6 +46,10 @@ public class AttachmentTimeline implements Timeline {
 	public function get frameCount () : int {
 		return frames.length;
 	}
+	
+	public function getPropertyId () : int {
+		return (TimelineType.attachment.ordinal << 24) + slotIndex;
+	}
 
 	/** Sets the time and value of the specified keyframe. */
 	public function setFrame (frameIndex:int, time:Number, attachmentName:String) : void {
@@ -52,9 +57,22 @@ public class AttachmentTimeline implements Timeline {
 		attachmentNames[frameIndex] = attachmentName;
 	}
 
-	public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number) : void {
+	public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void {
+		var attachmentName:String;
+		var slot:Slot = skeleton.slots[slotIndex];
+		if (mixingOut && setupPose) {			
+			attachmentName = slot.data.attachmentName;
+			slot.attachment = attachmentName == null ? null : skeleton.getAttachmentForSlotIndex(slotIndex, attachmentName);
+			return;
+		}
 		var frames:Vector.<Number> = this.frames;
-		if (time < frames[0]) return; // Time is before first frame.
+		if (time < frames[0]) {
+			if (setupPose) {
+				attachmentName = slot.data.attachmentName;
+				slot.attachment = attachmentName == null ? null : skeleton.getAttachmentForSlotIndex(slotIndex, attachmentName);
+			}
+			return;
+		}
 
 		var frameIndex:int;
 		if (time >= frames[frames.length - 1]) // Time is after last frame.
@@ -62,7 +80,7 @@ public class AttachmentTimeline implements Timeline {
 		else
 			frameIndex = Animation.binarySearch(frames, time, 1) - 1;
 
-		var attachmentName:String = attachmentNames[frameIndex];
+		attachmentName = attachmentNames[frameIndex];
 		skeleton.slots[slotIndex].attachment = attachmentName == null ? null : skeleton.getAttachmentForSlotIndex(slotIndex, attachmentName);
 	}
 }

+ 40 - 22
spine-as3/spine-as3/src/spine/animation/ColorTimeline.as

@@ -45,6 +45,10 @@ public class ColorTimeline extends CurveTimeline {
 		super(frameCount);
 		frames = new Vector.<Number>(frameCount * 5, true);
 	}
+	
+	override public function getPropertyId () : int {
+		return (TimelineType.color.ordinal << 24) + slotIndex;
+	}
 
 	/** Sets the time and value of the specified keyframe. */
 	public function setFrame (frameIndex:int, time:Number, r:Number, g:Number, b:Number, a:Number) : void {
@@ -56,45 +60,59 @@ public class ColorTimeline extends CurveTimeline {
 		frames[int(frameIndex + A)] = a;
 	}
 
-	override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number) : void {
-		if (time < frames[0])
-			return; // Time is before first frame.
+	override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void {
+		var frames:Vector.<Number> = this.frames;
+		var slot:Slot = skeleton.slots[slotIndex];
+		
+		if (time < frames[0]) {
+			if (setupPose) {
+				slot.r = slot.data.r;
+				slot.g = slot.data.g;
+				slot.b = slot.data.b;
+				slot.a = slot.data.a;	
+			}
+			return;
+		}
 
 		var r:Number, g:Number, b:Number, a:Number;
-		if (time >= frames[int(frames.length - ENTRIES)]) {
-			// Time is after last frame.
+		if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame.
 			var i:int = frames.length;
-			r = frames[int(i + PREV_R)];
-			g = frames[int(i + PREV_G)];
-			b = frames[int(i + PREV_B)];
-			a = frames[int(i + PREV_A)];
+			r = frames[i + PREV_R];
+			g = frames[i + PREV_G];
+			b = frames[i + PREV_B];
+			a = frames[i + PREV_A];
 		} else {
 			// Interpolate between the previous frame and the current frame.
 			var frame:int = Animation.binarySearch(frames, time, ENTRIES);
-			r = frames[int(frame + PREV_R)];
-			g = frames[int(frame + PREV_G)];
-			b = frames[int(frame + PREV_B)];
-			a = frames[int(frame + PREV_A)];
+			r = frames[frame + PREV_R];
+			g = frames[frame + PREV_G];
+			b = frames[frame + PREV_B];
+			a = frames[frame + PREV_A];
 			var frameTime:Number = frames[frame];
 			var percent:Number = getCurvePercent(frame / ENTRIES - 1,
-					1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
 
 			r += (frames[frame + R] - r) * percent;
 			g += (frames[frame + G] - g) * percent;
 			b += (frames[frame + B] - b) * percent;
 			a += (frames[frame + A] - a) * percent;
-		}
-		var slot:Slot = skeleton.slots[slotIndex];
-		if (alpha < 1) {
-			slot.r += (r - slot.r) * alpha;
-			slot.g += (g - slot.g) * alpha;
-			slot.b += (b - slot.b) * alpha;
-			slot.a += (a - slot.a) * alpha;
-		} else {
+		}		
+		if (alpha == 1) {
 			slot.r = r;
 			slot.g = g;
 			slot.b = b;
 			slot.a = a;
+		} else {			
+			if (setupPose) {
+				slot.r = slot.data.r;
+				slot.g = slot.data.g;
+				slot.b = slot.data.b;
+				slot.a = slot.data.a;				 
+			}
+			slot.r += (r - slot.r) * alpha;
+			slot.g += (g - slot.g) * alpha;
+			slot.b += (b - slot.b) * alpha;
+			slot.a += (a - slot.a) * alpha;			
 		}
 	}
 }

+ 5 - 1
spine-as3/spine-as3/src/spine/animation/CurveTimeline.as

@@ -46,7 +46,11 @@ public class CurveTimeline implements Timeline {
 		curves = new Vector.<Number>((frameCount - 1) * BEZIER_SIZE, true);
 	}
 
-	public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number) : void {
+	public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void {
+	}
+	
+	public function getPropertyId () : int {
+		return 0;
 	}
 
 	public function get frameCount () : int {

+ 65 - 19
spine-as3/spine-as3/src/spine/animation/DeformTimeline.as

@@ -29,6 +29,7 @@
  *****************************************************************************/
 
 package spine.animation {
+import spine.attachments.Attachment;
 import spine.attachments.VertexAttachment;
 import spine.Event;
 import spine.Skeleton;
@@ -45,6 +46,10 @@ public class DeformTimeline extends CurveTimeline {
 		frames = new Vector.<Number>(frameCount, true);
 		frameVertices = new Vector.<Vector.<Number>>(frameCount, true);
 	}
+	
+	override public function getPropertyId () : int {
+		return (TimelineType.deform.ordinal << 24) + slotIndex;
+	}
 
 	/** Sets the time and value of the specified keyframe. */
 	public function setFrame (frameIndex:int, time:Number, vertices:Vector.<Number>) : void {
@@ -52,51 +57,92 @@ public class DeformTimeline extends CurveTimeline {
 		frameVertices[frameIndex] = vertices;
 	}
 
-	override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number) : void {
+	override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void {
 		var slot:Slot = skeleton.slots[slotIndex];
-		var slotAttachment:VertexAttachment = slot.attachment as VertexAttachment;
-		if (!slotAttachment || !slotAttachment.applyDeform(attachment)) return;
+		var slotAttachment:Attachment = slot.attachment;
+		if (!(slotAttachment is VertexAttachment) || !(VertexAttachment(slotAttachment)).applyDeform(attachment)) return;
 
 		var frames:Vector.<Number> = this.frames;
-		if (time < frames[0]) return; // Time is before first frame.
+		var verticesArray:Vector.<Number> = slot.attachmentVertices;
+		if (time < frames[0]) {
+			if (setupPose) verticesArray.length = 0;
+			return;
+		}
 
 		var frameVertices:Vector.<Vector.<Number>> = this.frameVertices;
 		var vertexCount:int = frameVertices[0].length;
+		
+		if (verticesArray.length != vertexCount) alpha = 1; // Don't mix from uninitialized slot vertices.
+		verticesArray.length = vertexCount;
+		var vertices:Vector.<Number> = verticesArray;
 
-		var vertices:Vector.<Number> = slot.attachmentVertices;
-		if (vertices.length != vertexCount) alpha = 1; // Don't mix from uninitialized slot vertices.
-		vertices.length = vertexCount;
-
-		var i:int;
+		var i:int, n:int;
+		var vertexAttachment:VertexAttachment;
+		var setupVertices:Vector.<Number>;
+		var setup:Number, prev:Number;
 		if (time >= frames[frames.length - 1]) { // Time is after last frame.
-			var lastVertices:Vector.<Number> = frameVertices[int(frames.length - 1)];
-			if (alpha < 1) {
-				for (i = 0; i < vertexCount; i++)
-					vertices[i] += (lastVertices[i] - vertices[i]) * alpha;
+			var lastVertices:Vector.<Number> = frameVertices[frames.length - 1];
+			if (alpha == 1) {
+				// Vertex positions or deform offsets, no alpha.
+				for (i = 0, n = vertexCount; i < n; i++)
+					vertices[i] = lastVertices[i];
+			} else if (setupPose) {
+				vertexAttachment = VertexAttachment(slotAttachment);
+				if (vertexAttachment.bones == null) {
+					// Unweighted vertex positions, with alpha.
+					setupVertices = vertexAttachment.vertices;
+					for (i = 0; i < vertexCount; i++) {
+						setup = setupVertices[i];
+						vertices[i] = setup + (lastVertices[i] - setup) * alpha;
+					}
+				} else {
+					// Weighted deform offsets, with alpha.
+					for (i = 0; i < vertexCount; i++)
+						vertices[i] = lastVertices[i] * alpha;
+				}
 			} else {
+				// Vertex positions or deform offsets, with alpha.
 				for (i = 0; i < vertexCount; i++)
-					vertices[i] = lastVertices[i];
+					vertices[i] += (lastVertices[i] - vertices[i]) * alpha;
 			}
 			return;
 		}
 
 		// Interpolate between the previous frame and the current frame.
 		var frame:int = Animation.binarySearch1(frames, time);
-		var prevVertices:Vector.<Number> = frameVertices[int(frame - 1)];
+		var prevVertices:Vector.<Number> = frameVertices[frame - 1];
 		var nextVertices:Vector.<Number> = frameVertices[frame];
 		var frameTime:Number = frames[frame];
 		var percent:Number = getCurvePercent(frame - 1, 1 - (time - frameTime) / (frames[frame - 1] - frameTime));
 
-		var prev:Number;
-		if (alpha < 1) {
+		if (alpha == 1) {
+			// Vertex positions or deform offsets, no alpha.
 			for (i = 0; i < vertexCount; i++) {
 				prev = prevVertices[i];
-				vertices[i] += (prev + (nextVertices[i] - prev) * percent - vertices[i]) * alpha;
+				vertices[i] = prev + (nextVertices[i] - prev) * percent;
+			}
+		} else if (setupPose) {
+			vertexAttachment = VertexAttachment(slotAttachment);
+			if (vertexAttachment.bones == null) {
+				// Unweighted vertex positions, with alpha.
+				setupVertices = vertexAttachment.vertices;
+				for (i = 0; i < vertexCount; i++) {
+					prev = prevVertices[i];
+					setup = setupVertices[i];
+					vertices[i] = setup + (prev + (nextVertices[i] - prev) * percent - setup) * alpha;
+				}
+			} else {
+				// Weighted deform offsets, with alpha.
+				for (i = 0; i < vertexCount; i++) {
+					prev = prevVertices[i];
+					vertices[i] = (prev + (nextVertices[i] - prev) * percent) * alpha;
+				}
 			}
 		} else {
+			// Vertex positions or deform offsets, with alpha.
 			for (i = 0; i < vertexCount; i++) {
 				prev = prevVertices[i];
-				vertices[i] = prev + (nextVertices[i] - prev) * percent;
+				vertices[i] += (prev + (nextVertices[i] - prev) * percent - vertices[i]) * alpha;
 			}
 		}
 	}

+ 24 - 7
spine-as3/spine-as3/src/spine/animation/DrawOrderTimeline.as

@@ -45,6 +45,10 @@ public class DrawOrderTimeline implements Timeline {
 	public function get frameCount () : int {
 		return frames.length;
 	}
+	
+	public function getPropertyId () : int {
+		return TimelineType.drawOrder.ordinal << 24;
+	}
 
 	/** Sets the time and value of the specified keyframe. */
 	public function setFrame (frameIndex:int, time:Number, drawOrder:Vector.<int>) : void {
@@ -52,9 +56,24 @@ public class DrawOrderTimeline implements Timeline {
 		drawOrders[frameIndex] = drawOrder;
 	}
 
-	public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number) : void {
-		if (time < frames[0])
-			return; // Time is before first frame.
+	public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void {
+		if (mixingOut && setupPose) {
+			for (var ii:int = 0, n:int = skeleton.slots.length; ii < n; ii++)
+				skeleton.drawOrder[ii] = skeleton.slots[ii];
+			return;
+		}
+
+		var drawOrder:Vector.<Slot> = skeleton.drawOrder;
+		var slots:Vector.<Slot> = skeleton.slots;
+		var slot:Slot;
+		var i:int = 0;
+		if (time < frames[0]) {
+			if (setupPose) {
+				for each (slot in slots)
+					drawOrder[i++] = slot;			
+			}
+			return;
+		}
 
 		var frameIndex:int;
 		if (time >= frames[int(frames.length - 1)]) // Time is after last frame.
@@ -62,12 +81,10 @@ public class DrawOrderTimeline implements Timeline {
 		else
 			frameIndex = Animation.binarySearch1(frames, time) - 1;
 
-		var drawOrder:Vector.<Slot> = skeleton.drawOrder;
-		var slots:Vector.<Slot> = skeleton.slots;
 		var drawOrderToSetupIndex:Vector.<int> = drawOrders[frameIndex];
-		var i:int = 0;
+		i = 0;		
 		if (!drawOrderToSetupIndex) {
-			for each (var slot:Slot in slots)
+			for each (slot in slots)
 				drawOrder[i++] = slot;
 		} else {
 			for each (var setupIndex:int in drawOrderToSetupIndex)

+ 122 - 0
spine-as3/spine-as3/src/spine/animation/EventQueue.as

@@ -0,0 +1,122 @@
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, Esoteric Software
+ * All rights reserved.
+ *
+ * You are granted a perpetual, non-exclusive, non-sublicensable, and
+ * non-transferable license to use, install, execute, and perform the Spine
+ * Runtimes software and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes 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 SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) 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.
+ *****************************************************************************/
+
+package spine.animation {
+import spine.Event;
+
+public class EventQueue {
+	internal var objects:Vector.<Object> = new Vector.<Object>();
+	internal var animationState:AnimationState;
+	public var drainDisabled:Boolean;
+
+	public function EventQueue(animationState:AnimationState) {
+		this.animationState = animationState;
+	}
+
+	public function start (entry:TrackEntry):void {
+		objects.push(EventType.start);
+		objects.push(entry);
+		animationState.animationsChanged = true;
+	}
+
+	public function interrupt (entry:TrackEntry):void {
+		objects.push(EventType.interrupt);
+		objects.push(entry);
+	}
+
+	public function end (entry:TrackEntry):void {
+		objects.push(EventType.end);
+		objects.push(entry);
+		animationState.animationsChanged = true;
+	}
+
+	public function dispose (entry:TrackEntry):void {
+		objects.push(EventType.dispose);
+		objects.push(entry);
+	}
+
+	public function complete (entry:TrackEntry):void {
+		objects.push(EventType.complete);
+		objects.push(entry);
+	}
+
+	public function event (entry:TrackEntry, event:Event):void {
+		objects.push(EventType.event);
+		objects.push(entry);
+		objects.push(event);
+	}
+
+	public function drain ():void {
+		if (drainDisabled) return; // Not reentrant.
+		drainDisabled = true;
+
+		var objects:Vector.<Object> = this.objects;		
+		for (var i:int = 0; i < objects.length; i += 2) {
+			var type:EventType = EventType(objects[i]);
+			var entry:TrackEntry = TrackEntry(objects[i + 1]);			
+			switch (type) {
+			case EventType.start:
+				entry.onStart.invoke(entry);
+				animationState.onStart.invoke(entry);				
+				break;
+			case EventType.interrupt:
+				entry.onInterrupt.invoke(entry);
+				animationState.onInterrupt.invoke(entry);
+				break;
+			case EventType.end:
+				entry.onEnd.invoke(entry);
+				animationState.onEnd.invoke(entry);
+				// Fall through.
+			case EventType.dispose:
+				entry.onDispose.invoke(entry);
+				animationState.onDispose.invoke(entry);
+				animationState.trackEntryPool.free(entry);
+				break;
+			case EventType.complete:
+				entry.onComplete.invoke(entry);
+				animationState.onComplete.invoke(entry);
+				break;
+			case EventType.event:
+				var event:Event = Event(objects[i++ + 2]);
+				entry.onEvent.invoke(entry, event);
+				animationState.onEvent.invoke(entry, event);
+				break;
+			}
+		}
+		clear();
+
+		drainDisabled = false;
+	}
+
+	public function clear ():void {
+		objects.length = 0;
+	}
+}
+}

+ 6 - 2
spine-as3/spine-as3/src/spine/animation/EventTimeline.as

@@ -44,6 +44,10 @@ public class EventTimeline implements Timeline {
 	public function get frameCount () : int {
 		return frames.length;
 	}
+	
+	public function getPropertyId () : int {
+		return TimelineType.event.ordinal << 24;
+	}
 
 	/** Sets the time and value of the specified keyframe. */
 	public function setFrame (frameIndex:int, event:Event) : void {
@@ -52,11 +56,11 @@ public class EventTimeline implements Timeline {
 	}
 
 	/** Fires events for frames > lastTime and <= time. */
-	public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number) : void {
+	public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void {
 		if (!firedEvents) return;
 
 		if (lastTime > time) { // Fire events after last time for looped animations.
-			apply(skeleton, lastTime, int.MAX_VALUE, firedEvents, alpha);
+			apply(skeleton, lastTime, int.MAX_VALUE, firedEvents, alpha, setupPose, mixingOut);
 			lastTime = -1;
 		} else if (lastTime >= frames[int(frameCount - 1)]) // Last time is after last frame.
 			return;

+ 41 - 0
spine-as3/spine-as3/src/spine/animation/EventType.as

@@ -0,0 +1,41 @@
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, Esoteric Software
+ * All rights reserved.
+ *
+ * You are granted a perpetual, non-exclusive, non-sublicensable, and
+ * non-transferable license to use, install, execute, and perform the Spine
+ * Runtimes software and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes 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 SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) 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.
+ *****************************************************************************/
+
+package spine.animation {
+
+public class EventType {
+	public static const start:EventType = new EventType();
+	public static const interrupt:EventType = new EventType();
+	public static const end:EventType = new EventType();
+	public static const dispose:EventType = new EventType();
+	public static const complete:EventType = new EventType();
+	public static const event:EventType = new EventType();
+}
+}

+ 27 - 7
spine-as3/spine-as3/src/spine/animation/IkConstraintTimeline.as

@@ -45,6 +45,10 @@ public class IkConstraintTimeline extends CurveTimeline {
 		super(frameCount);
 		frames = new Vector.<Number>(frameCount * ENTRIES, true);
 	}
+	
+	override public function getPropertyId () : int {
+		return (TimelineType.ikConstraint.ordinal << 24) + ikConstraintIndex;
+	}
 
 	/** Sets the time, mix and bend direction of the specified keyframe. */
 	public function setFrame (frameIndex:int, time:Number, mix:Number, bendDirection:int) : void {
@@ -54,14 +58,25 @@ public class IkConstraintTimeline extends CurveTimeline {
 		frames[int(frameIndex + BEND_DIRECTION)] = bendDirection;
 	}
 
-	override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number) : void {
-		if (time < frames[0]) return; // Time is before first frame.
-
+	override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void {
 		var constraint:IkConstraint = skeleton.ikConstraints[ikConstraintIndex];
+		if (time < frames[0]) {
+			if (setupPose) {
+				constraint.mix = constraint.data.mix;
+				constraint.bendDirection = constraint.data.bendDirection;
+			}
+			return;
+		}
 
 		if (time >= frames[int(frames.length - ENTRIES)]) { // Time is after last frame.
-			constraint.mix += (frames[int(frames.length + PREV_MIX)] - constraint.mix) * alpha;
-			constraint.bendDirection = int(frames[int(frames.length + PREV_BEND_DIRECTION)]);
+			if (setupPose) {
+				constraint.mix = constraint.data.mix + (frames[frames.length + PREV_MIX] - constraint.data.mix) * alpha;
+				constraint.bendDirection = mixingOut ? constraint.data.bendDirection
+					: int(frames[frames.length + PREV_BEND_DIRECTION]);
+			} else {
+				constraint.mix += (frames[frames.length + PREV_MIX] - constraint.mix) * alpha;
+				if (!mixingOut) constraint.bendDirection = int(frames[frames.length + PREV_BEND_DIRECTION]);
+			}
 			return;
 		}
 
@@ -71,8 +86,13 @@ public class IkConstraintTimeline extends CurveTimeline {
 		var frameTime:Number = frames[frame];
 		var percent:Number = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
 
-		constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha;
-		constraint.bendDirection = int(frames[frame + PREV_BEND_DIRECTION]);
+		if (setupPose) {
+			constraint.mix = constraint.data.mix + (mix + (frames[frame + MIX] - mix) * percent - constraint.data.mix) * alpha;
+			constraint.bendDirection = mixingOut ? constraint.data.bendDirection : int(frames[frame + PREV_BEND_DIRECTION]);
+		} else {
+			constraint.mix += (mix + (frames[frame + MIX] - mix) * percent - constraint.mix) * alpha;
+			if (!mixingOut) constraint.bendDirection = int(frames[frame + PREV_BEND_DIRECTION]);
+		}
 	}
 }
 

+ 33 - 17
spine-as3/spine-as3/src/spine/animation/PathConstraintMixTimeline.as

@@ -46,6 +46,10 @@ public class PathConstraintMixTimeline extends CurveTimeline {
 		super(frameCount);
 		frames = new Vector.<Number>(frameCount * ENTRIES, true);
 	}
+	
+	override public function getPropertyId () : int {
+		return (TimelineType.pathConstraintMix.ordinal << 24) + pathConstraintIndex;
+	}
 
 	/** Sets the time and mixes of the specified keyframe. */
 	public function setFrame (frameIndex:int, time:Number, rotateMix:Number, translateMix:Number) : void {
@@ -55,28 +59,40 @@ public class PathConstraintMixTimeline extends CurveTimeline {
 		frames[frameIndex + TRANSLATE] = translateMix;
 	}
 
-	override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number) : void {
-		if (time < frames[0]) return; // Time is before first frame.
-
+	override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void {		
 		var constraint:PathConstraint = skeleton.pathConstraints[pathConstraintIndex];
-
-		if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame.
-			var i:int = frames.length;
-			constraint.rotateMix += (frames[i + PREV_ROTATE] - constraint.rotateMix) * alpha;
-			constraint.translateMix += (frames[i + PREV_TRANSLATE] - constraint.translateMix) * alpha;
+		if (time < frames[0]) {
+			if (setupPose) {
+				constraint.rotateMix = constraint.data.rotateMix;
+				constraint.translateMix = constraint.data.translateMix;
+			}
 			return;
 		}
 
-		// Interpolate between the previous frame and the current frame.
-		var frame:int = Animation.binarySearch(frames, time, ENTRIES);
-		var rotate:Number = frames[frame + PREV_ROTATE];
-		var translate:Number = frames[frame + PREV_TRANSLATE];
-		var frameTime:Number = frames[frame];
-		var percent:Number = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+		var rotate:Number, translate:Number;
+		if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame.
+			rotate = frames[frames.length + PREV_ROTATE];
+			translate = frames[frames.length + PREV_TRANSLATE];
+		} else {
+			// Interpolate between the previous frame and the current frame.
+			var frame:int = Animation.binarySearch(frames, time, ENTRIES);
+			rotate = frames[frame + PREV_ROTATE];
+			translate = frames[frame + PREV_TRANSLATE];
+			var frameTime:Number = frames[frame];
+			var percent:Number = getCurvePercent(frame / ENTRIES - 1,
+				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+
+			rotate += (frames[frame + ROTATE] - rotate) * percent;
+			translate += (frames[frame + TRANSLATE] - translate) * percent;
+		}
 
-		constraint.rotateMix += (rotate + (frames[frame + ROTATE] - rotate) * percent - constraint.rotateMix) * alpha;
-		constraint.translateMix += (translate + (frames[frame + TRANSLATE] - translate) * percent - constraint.translateMix)
-			* alpha;
+		if (setupPose) {
+			constraint.rotateMix = constraint.data.rotateMix + (rotate - constraint.data.rotateMix) * alpha;
+			constraint.translateMix = constraint.data.translateMix + (translate - constraint.data.translateMix) * alpha;
+		} else {
+			constraint.rotateMix += (rotate - constraint.rotateMix) * alpha;
+			constraint.translateMix += (translate - constraint.translateMix) * alpha;
+		}
 	}
 }
 }

+ 23 - 13
spine-as3/spine-as3/src/spine/animation/PathConstraintPositionTimeline.as

@@ -46,6 +46,10 @@ public class PathConstraintPositionTimeline extends CurveTimeline {
 		super(frameCount);
 		frames = new Vector.<Number>(frameCount * ENTRIES, true);
 	}
+	
+	override public function getPropertyId () : int {
+		return (TimelineType.pathConstraintPosition.ordinal << 24) + pathConstraintIndex;
+	}
 
 	/** Sets the time and value of the specified keyframe. */
 	public function setFrame (frameIndex:int, time:Number, value:Number) : void {
@@ -54,24 +58,30 @@ public class PathConstraintPositionTimeline extends CurveTimeline {
 		frames[frameIndex + VALUE] = value;
 	}
 
-	override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number) : void {
-		if (time < frames[0]) return; // Time is before first frame.
-
+	override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void {		
 		var constraint:PathConstraint = skeleton.pathConstraints[pathConstraintIndex];
-
-		if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame.
-			var i:int = frames.length;
-			constraint.position += (frames[i + PREV_VALUE] - constraint.position) * alpha;
+		if (time < frames[0]) {
+			if (setupPose) constraint.position = constraint.data.position;
 			return;
 		}
 
-		// Interpolate between the previous frame and the current frame.
-		var frame:int = Animation.binarySearch(frames, time, ENTRIES);
-		var position:Number = frames[frame + PREV_VALUE];
-		var frameTime:Number = frames[frame];
-		var percent:Number = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+		var position:Number;
+		if (time >= frames[frames.length - ENTRIES]) // Time is after last frame.
+			position = frames[frames.length + PREV_VALUE];
+		else {
+			// Interpolate between the previous frame and the current frame.
+			var frame:int = Animation.binarySearch(frames, time, ENTRIES);
+			position = frames[frame + PREV_VALUE];
+			var frameTime:Number = frames[frame];
+			var percent:Number = getCurvePercent(frame / ENTRIES - 1,
+				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
 
-		constraint.position += (position + (frames[frame + VALUE] - position) * percent - constraint.position) * alpha;
+			position += (frames[frame + VALUE] - position) * percent;
+		}
+		if (setupPose)
+			constraint.position = constraint.data.position + (position - constraint.data.position) * alpha;
+		else
+			constraint.position += (position - constraint.position) * alpha;
 	}
 }
 }

+ 24 - 13
spine-as3/spine-as3/src/spine/animation/PathConstraintSpacingTimeline.as

@@ -37,25 +37,36 @@ public class PathConstraintSpacingTimeline extends PathConstraintPositionTimelin
 	public function PathConstraintSpacingTimeline (frameCount:int) {
 		super(frameCount);
 	}
+	
+	override public function getPropertyId () : int {
+		return (TimelineType.pathConstraintSpacing.ordinal << 24) + pathConstraintIndex;
+	}
 
-	override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number) : void {
-		if (time < frames[0]) return; // Time is before first frame.
-
+	override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void {
 		var constraint:PathConstraint = skeleton.pathConstraints[pathConstraintIndex];
-
-		if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame.
-			var i:int = frames.length;
-			constraint.spacing += (frames[i + PREV_VALUE] - constraint.spacing) * alpha;
+		if (time < frames[0]) {
+			if (setupPose) constraint.spacing = constraint.data.spacing;
 			return;
 		}
 
-		// Interpolate between the previous frame and the current frame.
-		var frame:int = Animation.binarySearch(frames, time, ENTRIES);
-		var spacing:Number = frames[frame + PREV_VALUE];
-		var frameTime:Number = frames[frame];
-		var percent:Number = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+		var spacing:Number;
+		if (time >= frames[frames.length - ENTRIES]) // Time is after last frame.
+			spacing = frames[frames.length + PREV_VALUE];
+		else {
+			// Interpolate between the previous frame and the current frame.
+			var frame:int = Animation.binarySearch(frames, time, ENTRIES);
+			spacing = frames[frame + PREV_VALUE];
+			var frameTime:Number = frames[frame];
+			var percent:Number = getCurvePercent(frame / ENTRIES - 1,
+				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+
+			spacing += (frames[frame + VALUE] - spacing) * percent;
+		}
 
-		constraint.spacing += (spacing + (frames[frame + VALUE] - spacing) * percent - constraint.spacing) * alpha;
+		if (setupPose)
+			constraint.spacing = constraint.data.spacing + (spacing - constraint.data.spacing) * alpha;
+		else
+			constraint.spacing += (spacing - constraint.spacing) * alpha;
 	}
 }
 }

+ 34 - 25
spine-as3/spine-as3/src/spine/animation/RotateTimeline.as

@@ -35,8 +35,8 @@ import spine.Skeleton;
 
 public class RotateTimeline extends CurveTimeline {
 	static public const ENTRIES:int = 2;
-	static private const PREV_TIME:int = -2, PREV_ROTATION:int = -1;
-	static private const ROTATION:int = 1;
+	static public const PREV_TIME:int = -2, PREV_ROTATION:int = -1;
+	static public const ROTATION:int = 1;
 
 	public var boneIndex:int;
 	public var frames:Vector.<Number>; // time, value, ...
@@ -45,6 +45,10 @@ public class RotateTimeline extends CurveTimeline {
 		super(frameCount);
 		frames = new Vector.<Number>(frameCount * 2, true);
 	}
+	
+	override public function getPropertyId () : int {
+		return (TimelineType.rotate.ordinal << 24) + boneIndex;
+	}
 
 	/** Sets the time and angle of the specified keyframe. */
 	public function setFrame (frameIndex:int, time:Number, degrees:Number) : void {
@@ -52,40 +56,45 @@ public class RotateTimeline extends CurveTimeline {
 		frames[frameIndex] = time;
 		frames[int(frameIndex + ROTATION)] = degrees;
 	}
-
-	override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number) : void {
-		if (time < frames[0])
-			return; // Time is before first frame.
+                    
+	override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void {			
+		var frames:Vector.<Number> = this.frames;		
 
 		var bone:Bone = skeleton.bones[boneIndex];
+		var r:Number;
+		if (time < frames[0]) {
+			if (setupPose) bone.rotation = bone.data.rotation;
+			return;
+		}
 
-		if (time >= frames[int(frames.length - 2)]) { // Time is after last frame.
-			var amount:Number = bone.data.rotation + frames[int(frames.length + PREV_ROTATION)] - bone.rotation;
-			while (amount > 180)
-				amount -= 360;
-			while (amount < -180)
-				amount += 360;
-			bone.rotation += amount * alpha;
+		if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame.
+			if (setupPose)
+				bone.rotation = bone.data.rotation + frames[frames.length + PREV_ROTATION] * alpha;
+			else {
+				r = bone.data.rotation + frames[frames.length + PREV_ROTATION] - bone.rotation;
+				r -= (16384 - int((16384.499999999996 - r / 360))) * 360; // Wrap within -180 and 180.
+				bone.rotation += r * alpha;
+			}
 			return;
 		}
 
 		// Interpolate between the previous frame and the current frame.
 		var frame:int = Animation.binarySearch(frames, time, ENTRIES);
-		var prevRotation:Number = frames[int(frame + PREV_ROTATION)];
+		var prevRotation:Number = frames[frame + PREV_ROTATION];
 		var frameTime:Number = frames[frame];
 		var percent:Number = getCurvePercent((frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
 
-		amount = frames[int(frame + ROTATION)] - prevRotation;
-		while (amount > 180)
-			amount -= 360;
-		while (amount < -180)
-			amount += 360;
-		amount = bone.data.rotation + (prevRotation + amount * percent) - bone.rotation;
-		while (amount > 180)
-			amount -= 360;
-		while (amount < -180)
-			amount += 360;
-		bone.rotation += amount * alpha;
+		r = frames[frame + ROTATION] - prevRotation;
+		r -= (16384 - int((16384.499999999996 - r / 360))) * 360;
+		r = prevRotation + r * percent;
+		if (setupPose) {
+			r -= (16384 - int((16384.499999999996 - r / 360))) * 360;
+			bone.rotation = bone.data.rotation + r * alpha;
+		} else {
+			r = bone.data.rotation + r - bone.rotation;
+			r -= (16384 - int((16384.499999999996 - r / 360))) * 360;
+			bone.rotation += r * alpha;
+		}
 	}
 }
 

+ 51 - 15
spine-as3/spine-as3/src/spine/animation/ScaleTimeline.as

@@ -29,6 +29,7 @@
  *****************************************************************************/
 
 package spine.animation {
+import spine.MathUtils;
 import spine.Bone;
 import spine.Event;
 import spine.Skeleton;
@@ -37,27 +38,62 @@ public class ScaleTimeline extends TranslateTimeline {
 	public function ScaleTimeline (frameCount:int) {
 		super(frameCount);
 	}
+	
+	override public function getPropertyId () : int {
+		return (TimelineType.scale.ordinal << 24) + boneIndex;
+	}
 
-	override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number) : void {
-		if (time < frames[0])
-			return; // Time is before first frame.
-
+	override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void {
+		var frames:Vector.<Number> = this.frames;
 		var bone:Bone = skeleton.bones[boneIndex];
-		if (time >= frames[int(frames.length - ENTRIES)]) { // Time is after last frame.
-			bone.scaleX += (bone.data.scaleX * frames[int(frames.length + PREV_X)] - bone.scaleX) * alpha;
-			bone.scaleY += (bone.data.scaleY * frames[int(frames.length + PREV_Y)] - bone.scaleY) * alpha;
+		
+		if (time < frames[0]) {
+			if (setupPose) {
+				bone.scaleX = bone.data.scaleX;
+				bone.scaleY = bone.data.scaleY;	
+			}
 			return;
 		}
 
-		// Interpolate between the previous frame and the current frame.
-		var frame:int = Animation.binarySearch(frames, time, ENTRIES);
-		var prevX:Number = frames[frame + PREV_X];
-		var prevY:Number = frames[frame + PREV_Y];
-		var frameTime:Number = frames[frame];
-		var percent:Number = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+		var x:Number, y:Number;
+		if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame.
+			x = frames[frames.length + PREV_X] * bone.data.scaleX;
+			y = frames[frames.length + PREV_Y] * bone.data.scaleY;
+		} else {
+			// Interpolate between the previous frame and the current frame.
+			var frame:int = Animation.binarySearch(frames, time, ENTRIES);
+			x = frames[frame + PREV_X];
+			y = frames[frame + PREV_Y];
+			var frameTime:Number = frames[frame];
+			var percent:Number = getCurvePercent(frame / ENTRIES - 1,
+				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
 
-		bone.scaleX += (bone.data.scaleX * (prevX + (frames[frame + X] - prevX) * percent) - bone.scaleX) * alpha;
-		bone.scaleY += (bone.data.scaleY * (prevY + (frames[frame + Y] - prevY) * percent) - bone.scaleY) * alpha;
+			x = (x + (frames[frame + X] - x) * percent) * bone.data.scaleX;
+			y = (y + (frames[frame + Y] - y) * percent) * bone.data.scaleY;
+		}
+		if (alpha == 1) {
+			bone.scaleX = x;
+			bone.scaleY = y;
+		} else {
+			var bx:Number, by:Number;
+			if (setupPose) {
+				bx = bone.data.scaleX;
+				by = bone.data.scaleY;
+			} else {
+				bx = bone.scaleX;
+				by = bone.scaleY;
+			}
+			// Mixing out uses sign of setup or current pose, else use sign of key.
+			if (mixingOut) {
+				x = Math.abs(x) * MathUtils.signum(bx);
+				y = Math.abs(y) * MathUtils.signum(by);
+			} else {
+				bx = Math.abs(bx) * MathUtils.signum(x);
+				by = Math.abs(by) * MathUtils.signum(y);
+			}
+			bone.scaleX = bx + (x - bx) * alpha;
+			bone.scaleY = by + (y - by) * alpha;
+		}
 	}
 }
 

+ 37 - 18
spine-as3/spine-as3/src/spine/animation/ShearTimeline.as

@@ -29,35 +29,54 @@
  *****************************************************************************/
 
 package spine.animation {
-	import spine.Event;
-	import spine.Skeleton;
-	import spine.Bone;
+import spine.Event;
+import spine.Skeleton;
+import spine.Bone;
 
 public class ShearTimeline extends TranslateTimeline {
 	public function ShearTimeline (frameCount:int) {
 		super(frameCount);
 	}
+	
+	override public function getPropertyId () : int {
+		return (TimelineType.shear.ordinal << 24) + boneIndex;
+	}
 
-	override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number) : void {
-		var frames:Vector.<Number> = this.frames;
-		if (time < frames[0]) return; // Time is before first frame.
-
+	override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void {
+		var frames:Vector.<Number> = this.frames;		
 		var bone:Bone = skeleton.bones[boneIndex];
-		if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame.
-			bone.shearX += (bone.data.shearX + frames[frames.length + PREV_X] - bone.shearX) * alpha;
-			bone.shearY += (bone.data.shearY + frames[frames.length + PREV_Y] - bone.shearY) * alpha;
+		
+		if (time < frames[0]) {
+			if (setupPose) {
+				bone.shearX = bone.data.shearX;
+				bone.shearY = bone.data.shearY;
+			}
 			return;
 		}
 
-		// Interpolate between the previous frame and the current frame.
-		var frame:int = Animation.binarySearch(frames, time, ENTRIES);
-		var prevX:Number = frames[frame + PREV_X];
-		var prevY:Number = frames[frame + PREV_Y];
-		var frameTime:Number = frames[frame];
-		var percent:Number = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+		var x:Number, y:Number;
+		if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame.
+			x = frames[frames.length + PREV_X];
+			y = frames[frames.length + PREV_Y];
+		} else {
+			// Interpolate between the previous frame and the current frame.
+			var frame:int = Animation.binarySearch(frames, time, ENTRIES);
+			x = frames[frame + PREV_X];
+			y = frames[frame + PREV_Y];
+			var frameTime:Number = frames[frame];
+			var percent:Number = getCurvePercent(frame / ENTRIES - 1,
+				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
 
-		bone.shearX += (bone.data.shearX + (prevX + (frames[frame + X] - prevX) * percent) - bone.shearX) * alpha;
-		bone.shearY += (bone.data.shearY + (prevY + (frames[frame + Y] - prevY) * percent) - bone.shearY) * alpha;
+			x = x + (frames[frame + X] - x) * percent;
+			y = y + (frames[frame + Y] - y) * percent;
+		}
+		if (setupPose) {
+			bone.shearX = bone.data.shearX + x * alpha;
+			bone.shearY = bone.data.shearY + y * alpha;
+		} else {
+			bone.shearX += (bone.data.shearX + x - bone.shearX) * alpha;
+			bone.shearY += (bone.data.shearY + y - bone.shearY) * alpha;
+		}
 	}
 }
 

+ 3 - 1
spine-as3/spine-as3/src/spine/animation/Timeline.as

@@ -34,7 +34,9 @@ import spine.Skeleton;
 
 public interface Timeline {
 	/** Sets the value(s) for the specified time. */
-	function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number) : void;
+	function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void;
+	
+	function getPropertyId() : int;
 }
 
 }

+ 56 - 0
spine-as3/spine-as3/src/spine/animation/TimelineType.as

@@ -0,0 +1,56 @@
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, Esoteric Software
+ * All rights reserved.
+ *
+ * You are granted a perpetual, non-exclusive, non-sublicensable, and
+ * non-transferable license to use, install, execute, and perform the Spine
+ * Runtimes software and derivative works solely for personal or internal
+ * use. Without the written permission of Esoteric Software (see Section 2 of
+ * the Spine Software License Agreement), you may not (a) modify, translate,
+ * adapt, or develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes 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 SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) 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.
+ *****************************************************************************/
+
+package spine.animation {
+
+public class TimelineType {
+	public var ordinal:int;
+	
+	public function TimelineType(order:int) {
+		this.ordinal = order;
+	}
+	
+	public static const rotate:TimelineType = new TimelineType(0);
+	public static const translate:TimelineType = new TimelineType(1);
+	public static const scale:TimelineType = new TimelineType(2);
+	public static const shear:TimelineType = new TimelineType(3);
+	public static const attachment:TimelineType = new TimelineType(4);
+	public static const color:TimelineType = new TimelineType(5);
+	public static const deform:TimelineType = new TimelineType(6);
+	public static const event:TimelineType = new TimelineType(7);
+	public static const drawOrder:TimelineType = new TimelineType(8);
+	public static const ikConstraint:TimelineType = new TimelineType(9);
+	public static const transformConstraint:TimelineType = new TimelineType(10);
+	public static const pathConstraintPosition:TimelineType = new TimelineType(11);
+	public static const pathConstraintSpacing:TimelineType = new TimelineType(12);
+	public static const pathConstraintMix:TimelineType = new TimelineType(13);
+}
+
+}

+ 44 - 12
spine-as3/spine-as3/src/spine/animation/TrackEntry.as

@@ -29,22 +29,54 @@
  *****************************************************************************/
 
 package spine.animation {
+import spine.Poolable;
 
-public class TrackEntry {
-	public var next:TrackEntry;
-	internal var previous:TrackEntry;
+public class TrackEntry implements Poolable {
 	public var animation:Animation;
+	public var next:TrackEntry, mixingFrom:TrackEntry;
+	public var onStart:Listeners = new Listeners();
+	public var onInterrupt:Listeners = new Listeners();
+	public var onEnd:Listeners = new Listeners();
+	public var onDispose:Listeners = new Listeners();
+	public var onComplete:Listeners = new Listeners();
+	public var onEvent:Listeners = new Listeners();
+	public var trackIndex:int;
 	public var loop:Boolean;
-	public var delay:Number, time:Number = 0, lastTime:Number = -1, endTime:Number, timeScale:Number = 1;
-	internal var mixTime:Number, mixDuration:Number, mix:Number = 1;
-	public var onStart:Function, onEnd:Function, onComplete:Function, onEvent:Function;
-
-	public function TrackEntry () {
+	public var eventThreshold:Number, attachmentThreshold:Number, drawOrderThreshold:Number;
+	public var animationStart:Number, animationEnd:Number, animationLast:Number, nextAnimationLast:Number;
+	public var delay:Number, trackTime:Number, trackLast:Number, nextTrackLast:Number, trackEnd:Number, timeScale:Number;
+	public var alpha:Number, mixTime:Number, mixDuration:Number, mixAlpha:Number;
+	public var timelinesFirst:Vector.<Boolean> = new Vector.<Boolean>();
+	public var timelinesRotation:Vector.<Number> = new Vector.<Number>();
+	
+	public function TrackEntry () {		
 	}
-
-	public function toString () : String {
-		return animation == null ? "<none>" : animation.name;
+	
+	public function getAnimationTime():Number {
+		if (loop) {
+			var duration:Number = animationEnd - animationStart;
+			if (duration == 0) return animationStart;
+			return (trackTime % duration) + animationStart;
+		}
+		return Math.min(trackTime + animationStart, animationEnd);
+	}
+	
+	public function reset ():void {
+		next = null;
+		mixingFrom = null;
+		animation = null;
+		onStart.listeners.length = 0;
+		onInterrupt.listeners.length = 0;
+		onEnd.listeners.length = 0;
+		onDispose.listeners.length = 0;
+		onComplete.listeners.length = 0;
+		onEvent.listeners.length = 0;
+		timelinesFirst.length = 0;
+		timelinesRotation.length = 0;
+	}
+	
+	public function resetRotationDirection ():void {
+		timelinesRotation.length = 0;
 	}
 }
-
 }

+ 51 - 23
spine-as3/spine-as3/src/spine/animation/TransformConstraintTimeline.as

@@ -29,6 +29,7 @@
  *****************************************************************************/
 
 package spine.animation {
+import spine.TransformConstraintData;
 import spine.Event;
 import spine.Skeleton;
 import spine.TransformConstraint;
@@ -45,6 +46,10 @@ public class TransformConstraintTimeline extends CurveTimeline {
 		super(frameCount);
 		frames = new Vector.<Number>(frameCount * ENTRIES, true);
 	}
+	
+	override public function getPropertyId () : int {
+		return (TimelineType.transformConstraint.ordinal << 24) + transformConstraintIndex;
+	}
 
 	/** Sets the time and mixes of the specified keyframe. */
 	public function setFrame (frameIndex:int, time:Number, rotateMix:Number, translateMix:Number, scaleMix:Number, shearMix:Number) : void {
@@ -56,34 +61,57 @@ public class TransformConstraintTimeline extends CurveTimeline {
 		frames[frameIndex + SHEAR] = shearMix;
 	}
 
-	override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number) : void {
-		if (time < frames[0]) return; // Time is before first frame.
-
-		var constraint:TransformConstraint = skeleton.transformConstraints[transformConstraintIndex];
+	override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void {
+		var frames:Vector.<Number> = this.frames;		
 
-		if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame.
-			var i:int = frames.length;
-			constraint.rotateMix += (frames[i + PREV_ROTATE] - constraint.rotateMix) * alpha;
-			constraint.translateMix += (frames[i + PREV_TRANSLATE] - constraint.translateMix) * alpha;
-			constraint.scaleMix += (frames[i + PREV_SCALE] - constraint.scaleMix) * alpha;
-			constraint.shearMix += (frames[i + PREV_SHEAR] - constraint.shearMix) * alpha;
+		var constraint:TransformConstraint  = skeleton.transformConstraints[transformConstraintIndex];
+		var data:TransformConstraintData;
+		if (time < frames[0]) {
+			if (setupPose) {
+				data = constraint.data;
+				constraint.rotateMix = constraint.data.rotateMix;
+				constraint.translateMix = constraint.data.translateMix;
+				constraint.scaleMix = constraint.data.scaleMix;
+				constraint.shearMix = constraint.data.shearMix;
+			}
 			return;
 		}
 
-		// Interpolate between the previous frame and the current frame.
-		var frame:int = Animation.binarySearch(frames, time, ENTRIES);
-		var frameTime:Number = frames[frame];
-		var percent:Number = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+		var rotate:Number, translate:Number, scale:Number, shear:Number;
+		if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame.
+			var i:int = frames.length;
+			rotate = frames[i + PREV_ROTATE];
+			translate = frames[i + PREV_TRANSLATE];
+			scale = frames[i + PREV_SCALE];
+			shear = frames[i + PREV_SHEAR];
+		} else {
+			// Interpolate between the previous frame and the current frame.
+			var frame:int = Animation.binarySearch(frames, time, ENTRIES);
+			rotate = frames[frame + PREV_ROTATE];
+			translate = frames[frame + PREV_TRANSLATE];
+			scale = frames[frame + PREV_SCALE];
+			shear = frames[frame + PREV_SHEAR];
+			var frameTime:Number = frames[frame];
+			var percent:Number = getCurvePercent(frame / ENTRIES - 1,
+				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
 
-		var rotate:Number = frames[frame + PREV_ROTATE];
-		var translate:Number = frames[frame + PREV_TRANSLATE];
-		var scale:Number = frames[frame + PREV_SCALE];
-		var shear:Number = frames[frame + PREV_SHEAR];
-		constraint.rotateMix += (rotate + (frames[frame + ROTATE] - rotate) * percent - constraint.rotateMix) * alpha;
-		constraint.translateMix += (translate + (frames[frame + TRANSLATE] - translate) * percent - constraint.translateMix)
-			* alpha;
-		constraint.scaleMix += (scale + (frames[frame + SCALE] - scale) * percent - constraint.scaleMix) * alpha;
-		constraint.shearMix += (shear + (frames[frame + SHEAR] - shear) * percent - constraint.shearMix) * alpha;
+			rotate += (frames[frame + ROTATE] - rotate) * percent;
+			translate += (frames[frame + TRANSLATE] - translate) * percent;
+			scale += (frames[frame + SCALE] - scale) * percent;
+			shear += (frames[frame + SHEAR] - shear) * percent;
+		}
+		if (setupPose) {
+			data = constraint.data;
+			constraint.rotateMix = data.rotateMix + (rotate - data.rotateMix) * alpha;
+			constraint.translateMix = data.translateMix + (translate - data.translateMix) * alpha;
+			constraint.scaleMix = data.scaleMix + (scale - data.scaleMix) * alpha;
+			constraint.shearMix = data.shearMix + (shear - data.shearMix) * alpha;
+		} else {
+			constraint.rotateMix += (rotate - constraint.rotateMix) * alpha;
+			constraint.translateMix += (translate - constraint.translateMix) * alpha;
+			constraint.scaleMix += (scale - constraint.scaleMix) * alpha;
+			constraint.shearMix += (shear - constraint.shearMix) * alpha;
+		}
 	}
 }
 }

+ 33 - 15
spine-as3/spine-as3/src/spine/animation/TranslateTimeline.as

@@ -45,6 +45,10 @@ public class TranslateTimeline extends CurveTimeline {
 		super(frameCount);
 		frames = new Vector.<Number>(frameCount * ENTRIES, true);
 	}
+	
+	override public function getPropertyId () : int {
+		return (TimelineType.translate.ordinal << 24) + boneIndex;
+	}
 
 	/** Sets the time and value of the specified keyframe. */
 	public function setFrame (frameIndex:int, time:Number, x:Number, y:Number) : void {
@@ -54,27 +58,41 @@ public class TranslateTimeline extends CurveTimeline {
 		frames[int(frameIndex + Y)] = y;
 	}
 
-	override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number) : void {
-		if (time < frames[0])
-			return; // Time is before first frame.
+	override public function apply (skeleton:Skeleton, lastTime:Number, time:Number, firedEvents:Vector.<Event>, alpha:Number, setupPose:Boolean, mixingOut:Boolean) : void {
+		var frames:Vector.<Number> = this.frames;		
 
 		var bone:Bone = skeleton.bones[boneIndex];
-
-		if (time >= frames[int(frames.length - ENTRIES)]) { // Time is after last frame.
-			bone.x += (bone.data.x + frames[int(frames.length + PREV_X)] - bone.x) * alpha;
-			bone.y += (bone.data.y + frames[int(frames.length + PREV_Y)] - bone.y) * alpha;
+		if (time < frames[0]) {
+			if (setupPose) {
+				bone.x = bone.data.x;
+				bone.y = bone.data.y;
+			}
 			return;
 		}
 
-		// Interpolate between the previous frame and the current frame.
-		var frame:int = Animation.binarySearch(frames, time, ENTRIES);
-		var prevX:Number = frames[frame + PREV_X];
-		var prevY:Number = frames[frame + PREV_Y];
-		var frameTime:Number = frames[frame];
-		var percent:Number = getCurvePercent(frame / ENTRIES - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
+		var x:Number, y:Number;
+		if (time >= frames[frames.length - ENTRIES]) { // Time is after last frame.
+			x = frames[frames.length + PREV_X];
+			y = frames[frames.length + PREV_Y];
+		} else {
+			// Interpolate between the previous frame and the current frame.
+			var frame:int = Animation.binarySearch(frames, time, ENTRIES);
+			x = frames[frame + PREV_X];
+			y = frames[frame + PREV_Y];
+			var frameTime:Number = frames[frame];
+			var percent:Number = getCurvePercent(frame / ENTRIES - 1,
+				1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));
 
-		bone.x += (bone.data.x + prevX + (frames[frame + X] - prevX) * percent - bone.x) * alpha;
-		bone.y += (bone.data.y + prevY + (frames[frame + Y] - prevY) * percent - bone.y) * alpha;
+			x += (frames[frame + X] - x) * percent;
+			y += (frames[frame + Y] - y) * percent;
+		}
+		if (setupPose) {
+			bone.x = bone.data.x + x * alpha;
+			bone.y = bone.data.y + y * alpha;
+		} else {
+			bone.x += (bone.data.x + x - bone.x) * alpha;
+			bone.y += (bone.data.y + y - bone.y) * alpha;
+		}
 	}
 }
 

+ 5 - 6
spine-as3/spine-as3/src/spine/attachments/VertexAttachment.as

@@ -53,8 +53,7 @@ public dynamic class VertexAttachment extends Attachment {
 	 * @param offset The worldVertices index to begin writing values. */
 	public function computeWorldVertices2 (slot:Slot, start:int, count:int, worldVertices:Vector.<Number>, offset:int): void {
 		count += offset;
-		var skeleton:Skeleton = slot.skeleton;
-		var x:Number = skeleton.x, y:Number = skeleton.y;
+		var skeleton:Skeleton = slot.skeleton;		
 		var deformArray:Vector.<Number> = slot.attachmentVertices;
 		var vertices:Vector.<Number> = this.vertices;
 		var bones:Vector.<int> = this.bones;
@@ -68,8 +67,8 @@ public dynamic class VertexAttachment extends Attachment {
 		if (bones == null) {
 			if (deformArray.length > 0) vertices = deformArray;
 			bone = slot.bone;
-			x += bone.worldX;
-			y += bone.worldY;
+			var x:Number = bone.worldX;
+			var y:Number = bone.worldY;
 			var a:Number = bone.a, bb:Number = bone.b, c:Number = bone.c, d:Number = bone.d;
 			for (v = start, w = offset; w < count; v += 2, w += 2) {
 				vx = vertices[v], vy = vertices[v + 1];
@@ -87,7 +86,7 @@ public dynamic class VertexAttachment extends Attachment {
 		var skeletonBones:Vector.<Bone> = skeleton.bones;
 		if (deformArray.length == 0) {
 			for (w = offset, b = skip * 3; w < count; w += 2) {
-				wx = x, wy = y;
+				wx = 0, wy = 0;
 				n = bones[v++];
 				n += v;
 				for (; v < n; v++, b += 3) {
@@ -102,7 +101,7 @@ public dynamic class VertexAttachment extends Attachment {
 		} else {
 			deform = deformArray;
 			for (w = offset, b = skip * 3, f = skip << 1; w < count; w += 2) {
-				wx = x; wy = y;
+				wx = 0; wy = 0;
 				n = bones[v++];
 				n += v;
 				for (; v < n; v++, b += 3, f += 2) {

+ 8 - 13
spine-c/include/spine/Animation.h

@@ -65,35 +65,27 @@ void spAnimation_dispose (spAnimation* self);
  * @param lastTime The last time the animation was applied.
  * @param events Any triggered events are added. May be null.*/
 void spAnimation_apply (const spAnimation* self, struct spSkeleton* skeleton, float lastTime, float time, int loop,
-		spEvent** events, int* eventsCount);
-
-/** Poses the skeleton at the specified time for this animation mixed with the current pose.
- * @param lastTime The last time the animation was applied.
- * @param events Any triggered events are added. May be null.
- * @param alpha The amount of this animation that affects the current pose. */
-void spAnimation_mix (const spAnimation* self, struct spSkeleton* skeleton, float lastTime, float time, int loop,
-		spEvent** events, int* eventsCount, float alpha);
+		spEvent** events, int* eventsCount, float alpha, int /*boolean*/ setupPose, int /*boolean*/ mixingOut);
 
 #ifdef SPINE_SHORT_NAMES
 typedef spAnimation Animation;
 #define Animation_create(...) spAnimation_create(__VA_ARGS__)
 #define Animation_dispose(...) spAnimation_dispose(__VA_ARGS__)
 #define Animation_apply(...) spAnimation_apply(__VA_ARGS__)
-#define Animation_mix(...) spAnimation_mix(__VA_ARGS__)
 #endif
 
 /**/
 
 typedef enum {
-	SP_TIMELINE_SCALE,
 	SP_TIMELINE_ROTATE,
 	SP_TIMELINE_TRANSLATE,
+	SP_TIMELINE_SCALE,
 	SP_TIMELINE_SHEAR,
-	SP_TIMELINE_COLOR,
 	SP_TIMELINE_ATTACHMENT,
+	SP_TIMELINE_COLOR,
+	SP_TIMELINE_DEFORM,
 	SP_TIMELINE_EVENT,
 	SP_TIMELINE_DRAWORDER,
-	SP_TIMELINE_DEFORM,
 	SP_TIMELINE_IKCONSTRAINT,
 	SP_TIMELINE_TRANSFORMCONSTRAINT,
 	SP_TIMELINE_PATHCONSTRAINTPOSITION,
@@ -115,7 +107,8 @@ struct spTimeline {
 
 void spTimeline_dispose (spTimeline* self);
 void spTimeline_apply (const spTimeline* self, struct spSkeleton* skeleton, float lastTime, float time, spEvent** firedEvents,
-		int* eventsCount, float alpha);
+		int* eventsCount, float alpha, int /*boolean*/ setupPose, int /*boolean*/ mixingOut);
+int spTimeline_getPropertyId (const spTimeline* self);
 
 #ifdef SPINE_SHORT_NAMES
 typedef spTimeline Timeline;
@@ -181,6 +174,8 @@ typedef struct spBaseTimeline {
 
 /**/
 
+static const int ROTATE_PREV_TIME = -2, ROTATE_PREV_ROTATION = -1;
+static const int ROTATE_ROTATION = 1;
 static const int ROTATE_ENTRIES = 2;
 
 typedef struct spBaseTimeline spRotateTimeline;

+ 45 - 23
spine-c/include/spine/AnimationState.h

@@ -40,60 +40,69 @@ extern "C" {
 #endif
 
 typedef enum {
-	SP_ANIMATION_START, SP_ANIMATION_END, SP_ANIMATION_COMPLETE, SP_ANIMATION_EVENT
+	SP_ANIMATION_START, SP_ANIMATION_INTERRUPT, SP_ANIMATION_END, SP_ANIMATION_COMPLETE, SP_ANIMATION_DISPOSE, SP_ANIMATION_EVENT
 } spEventType;
 
 typedef struct spAnimationState spAnimationState;
+typedef struct spTrackEntry spTrackEntry;
 
-typedef void (*spAnimationStateListener) (spAnimationState* state, int trackIndex, spEventType type, spEvent* event,
-		int loopCount);
+typedef void (*spAnimationStateListener) (spAnimationState* state, spEventType type, spTrackEntry* entry, spEvent* event);
 
-typedef struct spTrackEntry spTrackEntry;
 struct spTrackEntry {
-	spAnimationState* const state;
-	spTrackEntry* next;
-	spTrackEntry* previous;
 	spAnimation* animation;
-	int/*bool*/loop;
-	float delay, time, lastTime, endTime, timeScale;
+	spTrackEntry* next;
+	spTrackEntry* mixingFrom;
 	spAnimationStateListener listener;
-	float mixTime, mixDuration, mix;
-
+	int trackIndex;
+	int /*boolean*/ loop;
+	float eventThreshold, attachmentThreshold, drawOrderThreshold;
+	float animationStart, animationEnd, animationLast, nextAnimationLast;
+	float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale;
+	float alpha, mixTime, mixDuration, mixAlpha;
+	int* /*boolean*/ timelinesFirst;
+	int timelinesFirstCount;
+	float* timelinesRotation;
+	int timelinesRotationCount;
 	void* rendererObject;
 
 #ifdef __cplusplus
 	spTrackEntry() :
-		state(0),
-		next(0),
-		previous(0),
 		animation(0),
-		loop(0),
-		delay(0), time(0), lastTime(0), endTime(0), timeScale(0),
+		next(0), mixingFrom(0),
 		listener(0),
-		mixTime(0), mixDuration(0), mix(0),
-		rendererObject(0) {
+		trackIndex(0),
+		loop(0),
+		eventThreshold(0), attachmentThreshold(0), drawOrderThreshold(0),
+		animationStart(0), animationEnd(0), animationLast(0), nextAnimationLast(0),
+		delay(0), trackTime(0), trackLast(0), nextTrackLast(0), trackEnd(0), timeScale(0),
+		alpha(0), mixTime(0), mixDuration(0), mixAlpha(0),
+		timelinesFirst(0),
+		timelinesFirstCount(0),
+		timelinesRotation(0),
+		timelinesRotationCount(0) {
 	}
 #endif
 };
 
 struct spAnimationState {
 	spAnimationStateData* const data;
-	float timeScale;
-	spAnimationStateListener listener;
 
 	int tracksCount;
 	spTrackEntry** tracks;
 
+	spAnimationStateListener listener;
+
+	float timeScale;
+
 	void* rendererObject;
 
 #ifdef __cplusplus
 	spAnimationState() :
 		data(0),
-		timeScale(0),
-		listener(0),
 		tracksCount(0),
 		tracks(0),
-		rendererObject(0) {
+		listener(0),
+		timeScale(0) {
 	}
 #endif
 };
@@ -119,14 +128,23 @@ spTrackEntry* spAnimationState_addAnimationByName (spAnimationState* self, int t
 		int/*bool*/loop, float delay);
 spTrackEntry* spAnimationState_addAnimation (spAnimationState* self, int trackIndex, spAnimation* animation, int/*bool*/loop,
 		float delay);
+spTrackEntry* spAnimationState_setEmptyAnimation(spAnimationState* self, int trackIndex, float mixDuration);
+spTrackEntry* spAnimationState_addEmptyAnimation(spAnimationState* self, int trackIndex, float mixDuration, float delay);
+void spAnimationState_setEmptyAnimations(spAnimationState* self, float mixDuration);
 
 spTrackEntry* spAnimationState_getCurrent (spAnimationState* self, int trackIndex);
 
+void spAnimationState_clearListenerNotifications(spAnimationState* self);
+
+float spTrackEntry_getAnimationTime (spTrackEntry* entry);
+
 #ifdef SPINE_SHORT_NAMES
 typedef spEventType EventType;
 #define ANIMATION_START SP_ANIMATION_START
+#define ANIMATION_INTERRUPT SP_ANIMATION_INTERRUPT
 #define ANIMATION_END SP_ANIMATION_END
 #define ANIMATION_COMPLETE SP_ANIMATION_COMPLETE
+#define ANIMATION_DISPOSE SP_ANIMATION_DISPOSE
 #define ANIMATION_EVENT SP_ANIMATION_EVENT
 typedef spAnimationStateListener AnimationStateListener;
 typedef spTrackEntry TrackEntry;
@@ -141,7 +159,11 @@ typedef spAnimationState AnimationState;
 #define AnimationState_setAnimation(...) spAnimationState_setAnimation(__VA_ARGS__)
 #define AnimationState_addAnimationByName(...) spAnimationState_addAnimationByName(__VA_ARGS__)
 #define AnimationState_addAnimation(...) spAnimationState_addAnimation(__VA_ARGS__)
+#define AnimationState_setEmptyAnimation(...) spAnimatinState_setEmptyAnimation(__VA_ARGS__)
+#define AnimationState_addEmptyAnimation(...) spAnimatinState_addEmptyAnimation(__VA_ARGS__)
+#define AnimationState_setEmptyAnimations(...) spAnimatinState_setEmptyAnimations(__VA_ARGS__)
 #define AnimationState_getCurrent(...) spAnimationState_getCurrent(__VA_ARGS__)
+#define AnimationState_clearListenerNotifications(...) spAnimatinState_clearListenerNotifications(__VA_ARGS__)
 #endif
 
 #ifdef __cplusplus

+ 6 - 6
spine-c/include/spine/Bone.h

@@ -47,11 +47,11 @@ struct spBone {
 	int childrenCount;
 	spBone** const children;
 	float x, y, rotation, scaleX, scaleY, shearX, shearY;
-	float appliedRotation;
+	float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY;
+	int /*bool*/ appliedValid;
 
 	float const a, b, worldX;
 	float const c, d, worldY;
-	float const worldSignX, worldSignY;
 
 	int/*bool*/ sorted;
 
@@ -62,11 +62,11 @@ struct spBone {
 		parent(0),
 		childrenCount(0), children(0),
 		x(0), y(0), rotation(0), scaleX(0), scaleY(0),
-		appliedRotation(0),
+		ax(0), ay(0), arotation(0), ascaleX(0), ascaleY(0), ashearX(0), ashearY(0),
+		appliedValid(0),
 
 		a(0), b(0), worldX(0),
 		c(0), d(0), worldY(0),
-		worldSignX(0), worldSignY(0),
 
 		sorted(0) {
 	}
@@ -93,7 +93,7 @@ float spBone_getWorldScaleY (spBone* self);
 float spBone_worldToLocalRotationX (spBone* self);
 float spBone_worldToLocalRotationY (spBone* self);
 void spBone_rotateWorld (spBone* self, float degrees);
-void spBone_updateLocalTransform (spBone* self);
+void spBone_updateAppliedTransform (spBone* self);
 
 void spBone_worldToLocal (spBone* self, float worldX, float worldY, float* localX, float* localY);
 void spBone_localToWorld (spBone* self, float localX, float localY, float* worldX, float* worldY);
@@ -114,7 +114,7 @@ typedef spBone Bone;
 #define Bone_worldToLocalRotationX(...) spBone_worldToLocalRotationX(__VA_ARGS__)
 #define Bone_worldToLocalRotationY(...) spBone_worldToLocalRotationY(__VA_ARGS__)
 #define Bone_rotateWorld(...) spBone_rotateWorld(__VA_ARGS__)
-#define Bone_updateLocalTransform(...) spBone_updateLocalTransform(__VA_ARGS__)
+#define Bone_updateAppliedTransform(...) spBone_updateAppliedTransform(__VA_ARGS__)
 #define Bone_worldToLocal(...) spBone_worldToLocal(__VA_ARGS__)
 #define Bone_localToWorld(...) spBone_localToWorld(__VA_ARGS__)
 #endif

+ 10 - 2
spine-c/include/spine/BoneData.h

@@ -35,6 +35,14 @@
 extern "C" {
 #endif
 
+typedef enum {
+	SP_TRANSFORMMODE_NORMAL,
+	SP_TRANSFORMMODE_ONLYTRANSLATION,
+	SP_TRANSFORMMODE_NOROTATIONORREFLECTION,
+	SP_TRANSFORMMODE_NOSCALE,
+	SP_TRANSFORMMODE_NOSCALEORREFLECTION
+} spTransformMode;
+
 typedef struct spBoneData spBoneData;
 struct spBoneData {
 	const int index;
@@ -42,7 +50,7 @@ struct spBoneData {
 	spBoneData* const parent;
 	float length;
 	float x, y, rotation, scaleX, scaleY, shearX, shearY;
-	int/*bool*/inheritRotation, inheritScale;
+	spTransformMode transformMode;
 
 #ifdef __cplusplus
 	spBoneData() :
@@ -54,7 +62,7 @@ struct spBoneData {
 		rotation(0),
 		scaleX(0), scaleY(0),
 		shearX(0), shearY(0),
-		inheritRotation(0), inheritScale(0) {
+		transformMode(SP_TRANSFORMMODE_NORMAL) {
 	}
 #endif
 };

+ 1 - 4
spine-c/include/spine/IkConstraint.h

@@ -50,8 +50,6 @@ typedef struct spIkConstraint {
 	int bendDirection;
 	float mix;
 
-	int level;
-
 #ifdef __cplusplus
 	spIkConstraint() :
 		data(0),
@@ -59,8 +57,7 @@ typedef struct spIkConstraint {
 		bones(0),
 		target(0),
 		bendDirection(0),
-		mix(0),
-		level(0) {
+		mix(0) {
 	}
 #endif
 } spIkConstraint;

+ 1 - 1
spine-c/include/spine/IkConstraintData.h

@@ -39,7 +39,7 @@ extern "C" {
 
 typedef struct spIkConstraintData {
 	const char* const name;
-
+	int order;
 	int bonesCount;
 	spBoneData** bones;
 

+ 1 - 0
spine-c/include/spine/PathConstraintData.h

@@ -52,6 +52,7 @@ typedef enum {
 
 typedef struct spPathConstraintData {
 	const char* const name;
+	int order;
 	int bonesCount;
 	spBoneData** const bones;
 	spSlotData* target;

+ 0 - 2
spine-c/include/spine/Skeleton.h

@@ -55,7 +55,6 @@ typedef struct spSkeleton {
 
 	int ikConstraintsCount;
 	spIkConstraint** ikConstraints;
-	spIkConstraint** ikConstraintsSorted;
 
 	int transformConstraintsCount;
 	spTransformConstraint** transformConstraints;
@@ -81,7 +80,6 @@ typedef struct spSkeleton {
 
 		ikConstraintsCount(0),
 		ikConstraints(0),
-		ikConstraintsSorted(0),
 
 		transformConstraintsCount(0),
 		transformConstraints(0),

+ 1 - 0
spine-c/include/spine/TransformConstraintData.h

@@ -39,6 +39,7 @@ extern "C" {
 
 typedef struct spTransformConstraintData {
 	const char* const name;
+	int order;
 	int bonesCount;
 	spBoneData** const bones;
 	spBoneData* target;

+ 50 - 11
spine-c/include/spine/extension.h

@@ -93,6 +93,7 @@
 #define RAD_DEG (180 / PI)
 
 #define ABS(A) ((A) < 0? -(A): (A))
+#define SIGNUM(A) ((A) < 0? -1: (A) > 0 ? 1 : 0)
 
 #ifdef __STDC_VERSION__
 #define FMOD(A,B) fmodf(A, B)
@@ -170,25 +171,60 @@ char* _readFile (const char* path, int* length);
 
 /**/
 
-typedef struct _spAnimationState {
+typedef union _spEventQueueItem {
+	int type;
+	spTrackEntry* entry;
+	spEvent* event;
+} _spEventQueueItem;
+
+typedef struct _spAnimationState _spAnimationState;
+
+typedef struct _spEventQueue {
+	_spAnimationState* state;
+	_spEventQueueItem* objects;
+	int objectsCount;
+	int objectsCapacity;
+	int /*boolean*/ drainDisabled;
+
+#ifdef __cplusplus
+	_spEventQueue() :
+		state(0),
+		objects(0),
+		objectsCount(0),
+		objectsCapacity(0),
+		drainDisabled(0) {
+	}
+#endif
+} _spEventQueue;
+
+struct _spAnimationState {
 	spAnimationState super;
+
+	int eventsCount;
 	spEvent** events;
 
-	spTrackEntry* (*createTrackEntry) (spAnimationState* self);
-	void (*disposeTrackEntry) (spTrackEntry* entry);
+	_spEventQueue* queue;
+
+	int* propertyIDs;
+	int propertyIDsCount;
+	int propertyIDsCapacity;
+
+	int /*boolean*/ animationsChanged;
 
 #ifdef __cplusplus
 	_spAnimationState() :
 		super(),
+		eventsCount(0),
 		events(0),
-		createTrackEntry(0),
-		disposeTrackEntry(0) {
+		queue(0),
+		propertyIDs(0),
+		propertyIDsCount(0),
+		propertyIDsCapacity(0),
+		animationsChanged(0) {
 	}
 #endif
-} _spAnimationState;
+};
 
-spTrackEntry* _spTrackEntry_create (spAnimationState* self);
-void _spTrackEntry_dispose (spTrackEntry* self);
 
 /**/
 
@@ -230,7 +266,8 @@ void _spVertexAttachment_deinit (spVertexAttachment* self);
 void _spTimeline_init (spTimeline* self, spTimelineType type,
 	void (*dispose) (spTimeline* self),
 	void (*apply) (const spTimeline* self, spSkeleton* skeleton, float lastTime, float time, spEvent** firedEvents,
-		int* eventsCount, float alpha));
+		int* eventsCount, float alpha, int setupPose, int mixingOut),
+	int (*getPropertyId) (const spTimeline* self));
 void _spTimeline_deinit (spTimeline* self);
 
 #ifdef SPINE_SHORT_NAMES
@@ -242,13 +279,15 @@ void _spTimeline_deinit (spTimeline* self);
 
 void _spCurveTimeline_init (spCurveTimeline* self, spTimelineType type, int framesCount,
 	void (*dispose) (spTimeline* self),
-	void (*apply) (const spTimeline* self, spSkeleton* skeleton, float lastTime, float time, spEvent** firedEvents,
-		int* eventsCount, float alpha));
+	void (*apply) (const spTimeline* self, spSkeleton* skeleton, float lastTime, float time, spEvent** firedEvents, int* eventsCount, float alpha, int setupPose, int mixingOut),
+	int (*getPropertyId) (const spTimeline* self));
 void _spCurveTimeline_deinit (spCurveTimeline* self);
+int _spCurveTimeline_binarySearch (float *values, int valuesLength, float target, int step);
 
 #ifdef SPINE_SHORT_NAMES
 #define _CurveTimeline_init(...) _spCurveTimeline_init(__VA_ARGS__)
 #define _CurveTimeline_deinit(...) _spCurveTimeline_deinit(__VA_ARGS__)
+#define _CurveTimeline_binarySearch(...) _spCurveTimeline_binarySearch(__VA_ARGS__)
 #endif
 
 #ifdef __cplusplus

Datei-Diff unterdrückt, da er zu groß ist
+ 501 - 266
spine-c/src/spine/Animation.c


+ 706 - 181
spine-c/src/spine/AnimationState.c

@@ -30,293 +30,818 @@
 
 #include <spine/AnimationState.h>
 #include <spine/extension.h>
-#include <string.h>
-
-spTrackEntry* _spTrackEntry_create (spAnimationState* state) {
-	spTrackEntry* self = NEW(spTrackEntry);
-	CONST_CAST(spAnimationState*, self->state) = state;
-	self->timeScale = 1;
-	self->lastTime = -1;
-	self->mix = 1;
+#include <limits.h>
+
+static spAnimation* SP_EMPTY_ANIMATION = 0;
+
+/* Forward declaration of some "private" functions so we can keep
+   the same function order in C as we have method order in Java */
+void _spAnimationState_disposeTrackEntry (spTrackEntry* entry);
+void _spAnimationState_disposeTrackEntries (spAnimationState* state, spTrackEntry* entry);
+void _spAnimationState_updateMixingFrom (spAnimationState* self, spTrackEntry* entry, float delta, int /*boolean*/ canEnd);
+float _spAnimationState_applyMixingFrom (spAnimationState* self, spTrackEntry* entry, spSkeleton* skeleton);
+void _spAnimationState_applyRotateTimeline (spAnimationState* self, spTimeline* timeline, spSkeleton* skeleton, float time, float alpha, int /*boolean*/ setupPose, float* timelinesRotation, int i, int /*boolean*/ firstFrame);
+void _spAnimationState_queueEvents (spAnimationState* self, spTrackEntry* entry, float animationTime);
+void _spAnimationState_setCurrent (spAnimationState* self, int index, spTrackEntry* current);
+spTrackEntry* _spAnimationState_expandToIndex (spAnimationState* self, int index);
+spTrackEntry* _spAnimationState_trackEntry (spAnimationState* self, int trackIndex, spAnimation* animation, int /*boolean*/ loop, spTrackEntry* last);
+void _spAnimationState_disposeNext (spAnimationState* self, spTrackEntry* entry);
+void _spAnimationState_animationsChanged (spAnimationState* self);
+float* _spAnimationState_resizeTimelinesRotation(spTrackEntry* entry, int newSize);
+int* _spAnimationState_resizeTimelinesFirst(spTrackEntry* entry, int newSize);
+void _spAnimationState_ensureCapacityPropertyIDs(spAnimationState* self, int capacity);
+int _spAnimationState_addPropertyID(spAnimationState* self, int id);
+void _spAnimationState_setTimelinesFirst (spAnimationState* self, spTrackEntry* entry);
+void _spAnimationState_checkTimelinesFirst (spAnimationState* self, spTrackEntry* entry);
+void _spAnimationState_checkTimelinesUsage (spAnimationState* self, spTrackEntry* entry);
+
+_spEventQueue* _spEventQueue_create (_spAnimationState* state) {
+	_spEventQueue *self = CALLOC(_spEventQueue, 1);
+	self->state = state;
+	self->objectsCount = 0;
+	self->objectsCapacity = 16;
+	self->objects = CALLOC(_spEventQueueItem, self->objectsCapacity);
+	self->drainDisabled = 0;
 	return self;
 }
 
-void _spTrackEntry_dispose (spTrackEntry* self) {
-	if (self->previous) SUB_CAST(_spAnimationState, self->state)->disposeTrackEntry(self->previous);
-	FREE(self);
+void _spEventQueue_free (_spEventQueue* self) {
+	FREE(self->objects);
 }
 
-/**/
+void _spEventQueue_ensureCapacity (_spEventQueue* self, int newElements) {
+	if (self->objectsCount + newElements > self->objectsCapacity) {
+		_spEventQueueItem* newObjects;
+		self->objectsCapacity <<= 1;
+		newObjects = CALLOC(_spEventQueueItem, self->objectsCapacity);
+		memcpy(newObjects, self->objects, self->objectsCount);
+		FREE(self->objects);
+		self->objects = newObjects;
+	}
+}
 
-spTrackEntry* _spAnimationState_createTrackEntry (spAnimationState* self) {
-	return _spTrackEntry_create(self);
+void _spEventQueue_addType (_spEventQueue* self, spEventType type) {
+	_spEventQueue_ensureCapacity(self, 1);
+	self->objects[self->objectsCount++].type = type;
 }
 
-void _spAnimationState_disposeTrackEntry (spTrackEntry* entry) {
-	_spTrackEntry_dispose(entry);
+void _spEventQueue_addEntry (_spEventQueue* self, spTrackEntry* entry) {
+	_spEventQueue_ensureCapacity(self, 1);
+	self->objects[self->objectsCount++].entry = entry;
 }
 
-spAnimationState* spAnimationState_create (spAnimationStateData* data) {
-	_spAnimationState* internal = NEW(_spAnimationState);
-	spAnimationState* self = SUPER(internal);
-	internal->events = MALLOC(spEvent*, 256);
-	self->timeScale = 1;
-	CONST_CAST(spAnimationStateData*, self->data) = data;
-	internal->createTrackEntry = _spAnimationState_createTrackEntry;
-	internal->disposeTrackEntry = _spAnimationState_disposeTrackEntry;
-	return self;
+void _spEventQueue_addEvent (_spEventQueue* self, spEvent* event) {
+	_spEventQueue_ensureCapacity(self, 1);
+	self->objects[self->objectsCount++].event = event;
 }
 
-void _spAnimationState_disposeAllEntries (spAnimationState* self, spTrackEntry* entry) {
-	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+void _spEventQueue_start (_spEventQueue* self, spTrackEntry* entry) {
+	_spEventQueue_addType(self, SP_ANIMATION_START);
+	_spEventQueue_addEntry(self, entry);
+	self->state->animationsChanged = 1;
+}
+
+void _spEventQueue_interrupt (_spEventQueue* self, spTrackEntry* entry) {
+	_spEventQueue_addType(self, SP_ANIMATION_INTERRUPT);
+	_spEventQueue_addEntry(self, entry);
+}
+
+void _spEventQueue_end (_spEventQueue* self, spTrackEntry* entry) {
+	_spEventQueue_addType(self, SP_ANIMATION_END);
+	_spEventQueue_addEntry(self, entry);
+	self->state->animationsChanged = 1;
+}
+
+void _spEventQueue_dispose (_spEventQueue* self, spTrackEntry* entry) {
+	_spEventQueue_addType(self, SP_ANIMATION_DISPOSE);
+	_spEventQueue_addEntry(self, entry);
+}
+
+void _spEventQueue_complete (_spEventQueue* self, spTrackEntry* entry) {
+	_spEventQueue_addType(self, SP_ANIMATION_COMPLETE);
+	_spEventQueue_addEntry(self, entry);
+}
+
+void _spEventQueue_event (_spEventQueue* self, spTrackEntry* entry, spEvent* event) {
+	_spEventQueue_addType(self, SP_ANIMATION_EVENT);
+	_spEventQueue_addEntry(self, entry);
+	_spEventQueue_addEvent(self, event);
+}
+
+void _spEventQueue_clear (_spEventQueue* self) {
+	self->objectsCount = 0;
+}
+
+void _spEventQueue_drain (_spEventQueue* self) {
+	int i;
+	if (self->drainDisabled) return;
+	self->drainDisabled = 1;
+	for (i = 0; i < self->objectsCount; i += 2) {
+		spEventType type = self->objects[i].type;
+		spTrackEntry* entry = self->objects[i+1].entry;
+		spEvent* event;
+		switch (type) {
+			case SP_ANIMATION_START:
+			case SP_ANIMATION_INTERRUPT:
+			case SP_ANIMATION_COMPLETE:
+				if (entry->listener) entry->listener(SUPER(self->state), type, entry, 0);
+				if (self->state->super.listener) self->state->super.listener(SUPER(self->state), type, entry, 0);
+				break;
+			case SP_ANIMATION_END:
+				if (entry->listener) entry->listener(SUPER(self->state), type, entry, 0);
+				if (self->state->super.listener) self->state->super.listener(SUPER(self->state), type, entry, 0);
+				/* Fall through. */
+			case SP_ANIMATION_DISPOSE:
+				if (entry->listener) entry->listener(SUPER(self->state), type, entry, 0);
+				if (self->state->super.listener) self->state->super.listener(SUPER(self->state), type, entry, 0);
+				_spAnimationState_disposeTrackEntry(entry);
+				break;
+			case SP_ANIMATION_EVENT:
+				event = self->objects[i+2].event;
+				if (entry->listener) entry->listener(SUPER(self->state), type, entry, event);
+				if (self->state->super.listener) self->state->super.listener(SUPER(self->state), type, entry, event);
+				i++;
+				break;
+		}
+	}
+	_spEventQueue_clear(self);
+
+	self->drainDisabled = 0;
+}
+
+void _spAnimationState_disposeTrackEntry (spTrackEntry* entry) {
+	FREE(entry);
+}
+
+void _spAnimationState_disposeTrackEntries (spAnimationState* state, spTrackEntry* entry) {
 	while (entry) {
 		spTrackEntry* next = entry->next;
-		internal->disposeTrackEntry(entry);
+		_spAnimationState_disposeTrackEntry(entry);
 		entry = next;
 	}
 }
 
+spAnimationState* spAnimationState_create (spAnimationStateData* data) {
+	_spAnimationState* internal;
+	spAnimationState* self;
+
+	if (!SP_EMPTY_ANIMATION) {
+		SP_EMPTY_ANIMATION = (spAnimation*)1; /* dirty trick so we can recursively call spAnimation_create */
+		SP_EMPTY_ANIMATION = spAnimation_create("<empty>", 0);
+	}
+
+	internal = NEW(_spAnimationState);
+	self = SUPER(internal);
+
+	CONST_CAST(spAnimationStateData*, self->data) = data;
+	self->timeScale = 1;
+
+	internal->queue = _spEventQueue_create(internal);
+	internal->events = CALLOC(spEvent*, 128);
+
+	internal->propertyIDs = CALLOC(int, 128);
+	internal->propertyIDsCapacity = 128;
+
+	return self;
+}
+
 void spAnimationState_dispose (spAnimationState* self) {
 	int i;
 	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
-	FREE(internal->events);
-	for (i = 0; i < self->tracksCount; ++i)
-		_spAnimationState_disposeAllEntries(self, self->tracks[i]);
+	for (i = 0; i < self->tracksCount; i++)
+		_spAnimationState_disposeTrackEntries(self, self->tracks[i]);
 	FREE(self->tracks);
-	FREE(self);
+	_spEventQueue_free(internal->queue);
+	FREE(internal->events);
+	FREE(internal->propertyIDs);
 }
 
-void _spAnimationState_setCurrent (spAnimationState* self, int index, spTrackEntry* entry);
-
 void spAnimationState_update (spAnimationState* self, float delta) {
-	int i;
-	float previousDelta;
+	int i, n;
+	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
 	delta *= self->timeScale;
-	for (i = 0; i < self->tracksCount; ++i) {
+	for (i = 0, n = self->tracksCount; i < n; i++) {
+		float currentDelta;
 		spTrackEntry* current = self->tracks[i];
+		spTrackEntry* next;
 		if (!current) continue;
 
-		current->time += delta * current->timeScale;
-		if (current->previous) {
-			previousDelta = delta * current->previous->timeScale;
-			current->previous->time += previousDelta;
-			current->mixTime += previousDelta;
+		current->animationLast = current->nextAnimationLast;
+		current->trackLast = current->nextTrackLast;
+
+		currentDelta = delta * current->timeScale;
+
+		if (current->delay > 0) {
+			current->delay -= currentDelta;
+			if (current->delay > 0) continue;
+			currentDelta = -current->delay;
+			current->delay = 0;
 		}
 
-		if (current->next) {
-			current->next->time = current->lastTime - current->next->delay;
-			if (current->next->time >= 0) _spAnimationState_setCurrent(self, i, current->next);
+		next = current->next;
+		if (next) {
+			/* When the next entry's delay is passed, change to the next entry, preserving leftover time. */
+			float nextTime = current->trackLast - next->delay;
+			if (nextTime >= 0) {
+				next->delay = 0;
+				next->trackTime = nextTime + delta * next->timeScale;
+				current->trackTime += currentDelta;
+				_spAnimationState_setCurrent(self, i, next);
+				while (next->mixingFrom) {
+					next->mixTime += currentDelta;
+					next = next->mixingFrom;
+				}
+				continue;
+			}
+			_spAnimationState_updateMixingFrom(self, current, delta, 1);
 		} else {
-			/* End non-looping animation when it reaches its end time and there is no next entry. */
-			if (!current->loop && current->lastTime >= current->endTime) spAnimationState_clearTrack(self, i);
+			_spAnimationState_updateMixingFrom(self, current, delta, 1);
+			/* Clear the track when there is no next entry, the track end time is reached, and there is no mixingFrom. */
+			if (current->trackLast >= current->trackEnd && current->mixingFrom == 0) {
+				self->tracks[i] = 0;
+				_spEventQueue_end(internal->queue, current);
+				_spAnimationState_disposeNext(self, current);
+				continue;
+			}
 		}
+
+		current->trackTime += currentDelta;
+	}
+
+	_spEventQueue_drain(internal->queue);
+}
+
+void _spAnimationState_updateMixingFrom (spAnimationState* self, spTrackEntry* entry, float delta, int /*boolean*/ canEnd) {
+	spTrackEntry* from = entry->mixingFrom;
+	spTrackEntry* newFrom;
+	float mixingFromDelta;
+	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+	if (!from) return;
+
+	if (canEnd && entry->mixTime >= entry->mixDuration && entry->mixTime > 0) {
+		_spEventQueue_end(internal->queue, from);
+		newFrom = from->mixingFrom;
+		entry->mixingFrom = newFrom;
+		if (!newFrom) return;
+		entry->mixTime = from->mixTime;
+		entry->mixDuration = from->mixDuration;
+		from = newFrom;
 	}
+
+	from->animationLast = from->nextAnimationLast;
+	from->trackLast = from->nextTrackLast;
+	mixingFromDelta = delta * from->timeScale;
+	from->trackTime += mixingFromDelta;
+	entry->mixTime += mixingFromDelta;
+
+	_spAnimationState_updateMixingFrom(self, from, delta, canEnd && from->alpha == 1);
 }
 
 void spAnimationState_apply (spAnimationState* self, spSkeleton* skeleton) {
 	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+	spTrackEntry* current;
+	int i, ii, n;
+	float animationLast, animationTime;
+	int timelineCount;
+	spTimeline** timelines;
+	int /*boolean*/ firstFrame;
+	float* timelinesRotation;
+	int* timelinesFirst;
+	spTimeline* timeline;
+
+	if (internal->animationsChanged) _spAnimationState_animationsChanged(self);
+
+	for (i = 0, n = self->tracksCount; i < n; i++) {
+		float mix;
+		current = self->tracks[i];
+		if (!current || current->delay > 0) continue;
+
+		/* Apply mixing from entries first. */
+		mix = current->alpha;
+		if (current->mixingFrom) mix *= _spAnimationState_applyMixingFrom(self, current, skeleton);
+
+		/* Apply current entry. */
+		animationLast = current->animationLast; animationTime = spTrackEntry_getAnimationTime(current);
+		timelineCount = current->animation->timelinesCount;
+		timelines = current->animation->timelines;
+		if (mix == 1) {
+			for (ii = 0; ii < timelineCount; ii++)
+				spTimeline_apply(timelines[ii], skeleton, animationLast, animationTime, internal->events, &internal->eventsCount, 1, 1, 0);
+		} else {
+			firstFrame = current->timelinesRotationCount == 0;
+			if (firstFrame) _spAnimationState_resizeTimelinesRotation(current, timelineCount << 1);
+			timelinesRotation = current->timelinesRotation;
+
+			timelinesFirst = current->timelinesFirst;
+			for (ii = 0; ii < timelineCount; ii++) {
+				timeline = timelines[ii];
+				if (timeline->type == SP_TIMELINE_ROTATE)
+					_spAnimationState_applyRotateTimeline(self, timeline, skeleton, animationTime, mix, timelinesFirst[ii], timelinesRotation, ii << 1, firstFrame);
+				else
+					spTimeline_apply(timeline, skeleton, animationLast, animationTime, internal->events, &internal->eventsCount, mix, timelinesFirst[ii], 0);
+			}
+		}
+		_spAnimationState_queueEvents(self, current, animationTime);
+		current->nextAnimationLast = animationTime;
+		current->nextTrackLast = current->trackTime;
+	}
 
-	int i, ii;
-	int eventsCount;
-	int entryChanged;
-	float time;
-	spTrackEntry* previous;
-	for (i = 0; i < self->tracksCount; ++i) {
-		spTrackEntry* current = self->tracks[i];
-		if (!current) continue;
+	_spEventQueue_drain(internal->queue);
+}
 
-		eventsCount = 0;
+float _spAnimationState_applyMixingFrom (spAnimationState* self, spTrackEntry* entry, spSkeleton* skeleton) {
+	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+	float mix;
+	spEvent** events;
+	int /*boolean*/ attachments;
+	int /*boolean*/ drawOrder;
+	float animationLast;
+	float animationTime;
+	int timelineCount;
+	spTimeline** timelines;
+	int* timelinesFirst;
+	float alpha;
+	int /*boolean*/ firstFrame;
+	float* timelinesRotation;
+	spTimeline* timeline;
+	int /*boolean*/ setupPose;
+	int i;
 
-		time = current->time;
-		if (!current->loop && time > current->endTime) time = current->endTime;
+	spTrackEntry* from = entry->mixingFrom;
+	if (from->mixingFrom) _spAnimationState_applyMixingFrom(self, from, skeleton);
 
-		previous = current->previous;
-		if (!previous) {
-			if (current->mix == 1) {
-				spAnimation_apply(current->animation, skeleton, current->lastTime, time,
-					current->loop, internal->events, &eventsCount);
-			} else {
-				spAnimation_mix(current->animation, skeleton, current->lastTime, time,
-					current->loop, internal->events, &eventsCount, current->mix);
+	if (entry->mixDuration == 0) /* Single frame mix to undo mixingFrom changes. */
+		mix = 1;
+	else {
+		mix = entry->mixTime / entry->mixDuration;
+		if (mix > 1) mix = 1;
+	}
+
+	events = mix < from->eventThreshold ? internal->events : 0;
+	attachments = mix < from->attachmentThreshold;
+	drawOrder = mix < from->drawOrderThreshold;
+	animationLast = from->animationLast;
+	animationTime = spTrackEntry_getAnimationTime(from);
+	timelineCount = from->animation->timelinesCount;
+	timelines = from->animation->timelines;
+	timelinesFirst = from->timelinesFirst;
+	alpha = from->alpha * entry->mixAlpha * (1 - mix);
+
+	firstFrame = from->timelinesRotationCount == 0;
+	if (firstFrame) _spAnimationState_resizeTimelinesRotation(from, timelineCount << 1);
+	timelinesRotation = from->timelinesRotation;
+
+	for (i = 0; i < timelineCount; i++) {
+		timeline = timelines[i];
+		setupPose = timelinesFirst[i];
+		if (timeline->type == SP_TIMELINE_ROTATE)
+			_spAnimationState_applyRotateTimeline(self, timeline, skeleton, animationTime, alpha, setupPose, timelinesRotation, i << 1, firstFrame);
+		else {
+			if (!setupPose) {
+				if (!attachments && timeline->type == SP_TIMELINE_ATTACHMENT) continue;
+				if (!drawOrder && timeline->type == SP_TIMELINE_DRAWORDER) continue;
 			}
-		} else {
-			float alpha = current->mixTime / current->mixDuration * current->mix;
+			spTimeline_apply(timeline, skeleton, animationLast, animationTime, events, &internal->eventsCount, alpha, setupPose, 1);
+		}
+	}
 
-			float previousTime = previous->time;
-			if (!previous->loop && previousTime > previous->endTime) previousTime = previous->endTime;
-			spAnimation_apply(previous->animation, skeleton, previousTime, previousTime, previous->loop, 0, 0);
+	_spAnimationState_queueEvents(self, from, animationTime);
+	from->nextAnimationLast = animationTime;
+	from->nextTrackLast = from->trackTime;
 
-			if (alpha >= 1) {
-				alpha = 1;
-				internal->disposeTrackEntry(current->previous);
-				current->previous = 0;
-			}
-			spAnimation_mix(current->animation, skeleton, current->lastTime, time,
-				current->loop, internal->events, &eventsCount, alpha);
+	return mix;
+}
+
+void _spAnimationState_applyRotateTimeline (spAnimationState* self, spTimeline* timeline, spSkeleton* skeleton, float time, float alpha, int /*boolean*/ setupPose, float* timelinesRotation, int i, int /*boolean*/ firstFrame) {
+	spRotateTimeline *rotateTimeline;
+	float *frames;
+	spBone* bone;
+	float r1, r2;
+	int frame;
+	float prevRotation;
+	float frameTime;
+	float percent;
+	float total, diff;
+	int /*boolean*/ current, dir;
+
+	if (alpha == 1) {
+		spTimeline_apply(timeline, skeleton, 0, time, 0, 0, 1, setupPose, 0);
+		return;
+	}
+
+	rotateTimeline = SUB_CAST(spRotateTimeline, timeline);
+	frames = rotateTimeline->frames;
+	bone = skeleton->bones[rotateTimeline->boneIndex];
+	if (time < frames[0]) {
+		if (setupPose) {
+			bone->rotation = bone->data->rotation;
 		}
+		return; /* Time is before first frame. */
+	}
 
-		entryChanged = 0;
-		for (ii = 0; ii < eventsCount; ++ii) {
-			spEvent* event = internal->events[ii];
-			if (current->listener) {
-				current->listener(self, i, SP_ANIMATION_EVENT, event, 0);
-				if (self->tracks[i] != current) {
-					entryChanged = 1;
-					break;
-				}
-			}
-			if (self->listener) {
-				self->listener(self, i, SP_ANIMATION_EVENT, event, 0);
-				if (self->tracks[i] != current) {
-					entryChanged = 1;
-					break;
-				}
-			}
+	if (time >= frames[rotateTimeline->framesCount - ROTATE_ENTRIES]) /* Time is after last frame. */
+		r2 = bone->data->rotation + frames[rotateTimeline->framesCount + ROTATE_PREV_ROTATION];
+	else {
+		/* Interpolate between the previous frame and the current frame. */
+		frame = _spCurveTimeline_binarySearch(frames, rotateTimeline->framesCount, time, ROTATE_ENTRIES);
+		prevRotation = frames[frame + ROTATE_PREV_ROTATION];
+		frameTime = frames[frame];
+		percent = spCurveTimeline_getCurvePercent(SUPER(rotateTimeline), (frame >> 1) - 1,
+													   1 - (time - frameTime) / (frames[frame + ROTATE_PREV_TIME] - frameTime));
+
+		r2 = frames[frame + ROTATE_ROTATION] - prevRotation;
+		r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360;
+		r2 = prevRotation + r2 * percent + bone->data->rotation;
+		r2 -= (16384 - (int)(16384.499999999996 - r2 / 360)) * 360;
+	}
+
+	/* Mix between rotations using the direction of the shortest route on the first frame while detecting crosses. */
+	r1 = setupPose ? bone->data->rotation : bone->rotation;
+	diff = r2 - r1;
+	if (diff == 0) {
+		if (firstFrame) {
+			timelinesRotation[i] = 0;
+			total = 0;
+		} else
+			total = timelinesRotation[i];
+	} else {
+		float lastTotal, lastDiff;
+		diff -= (16384 - (int)(16384.499999999996 - diff / 360)) * 360;
+		if (firstFrame) {
+			lastTotal = 0;
+			lastDiff = diff;
+		} else {
+			lastTotal = timelinesRotation[i]; /* Angle and direction of mix, including loops. */
+			lastDiff = timelinesRotation[i + 1]; /* Difference between bones. */
 		}
-		if (entryChanged) continue;
-
-		/* Check if completed the animation or a loop iteration. */
-		if (current->loop ? (FMOD(current->lastTime, current->endTime) > FMOD(time, current->endTime))
-				: (current->lastTime < current->endTime && time >= current->endTime)) {
-			int count = (int)(time / current->endTime);
-			if (current->listener) {
-				current->listener(self, i, SP_ANIMATION_COMPLETE, 0, count);
-				if (self->tracks[i] != current) continue;
-			}
-			if (self->listener) {
-				self->listener(self, i, SP_ANIMATION_COMPLETE, 0, count);
-				if (self->tracks[i] != current) continue;
-			}
+		current = diff > 0;
+		dir = lastTotal >= 0;
+		/* Detect cross at 0 (not 180). */
+		if (SIGNUM(lastDiff) != SIGNUM(diff) && ABS(lastDiff) <= 90) {
+			/* A cross after a 360 rotation is a loop. */
+			if (ABS(lastTotal) > 180) lastTotal += 360 * SIGNUM(lastTotal);
+			dir = current;
 		}
+		total = diff + lastTotal - FMOD(lastTotal, 360); /* Store loops as part of lastTotal. */
+		if (dir != current) total += 360 * SIGNUM(lastTotal);
+		timelinesRotation[i] = total;
+	}
+	timelinesRotation[i + 1] = diff;
+	r1 += total * alpha;
+	bone->rotation = r1 - (16384 - (int)(16384.499999999996 - r1 / 360)) * 360;
+}
+
+void _spAnimationState_queueEvents (spAnimationState* self, spTrackEntry* entry, float animationTime) {
+	spEvent** events;
+	spEvent* event;
+	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+	int i, n;
+	float animationStart = entry->animationStart, animationEnd = entry->animationEnd;
+	float duration = animationEnd - animationStart;
+	float trackLastWrapped = FMOD(entry->trackLast, duration);
+
+	/* Queue events before complete. */
+	events = internal->events;
+	for (i = 0, n = internal->eventsCount; i < n; i++) {
+		event = events[i];
+		if (event->time < trackLastWrapped) break;
+		if (event->time > animationEnd) continue; /* Discard events outside animation start/end. */
+		_spEventQueue_event(internal->queue, entry, event);
+	}
+
+	/* Queue complete if completed a loop iteration or the animation. */
+	if (entry->loop ? (trackLastWrapped > FMOD(entry->trackTime, duration))
+				   : (animationTime >= animationEnd && entry->animationLast < animationEnd)) {
+		_spEventQueue_complete(internal->queue, entry);
+	}
 
-		current->lastTime = current->time;
+	/* Queue events after complete. */
+	for (; i < n; i++) {
+		event = events[i];
+		if (event->time < animationStart) continue; /* Discard events outside animation start/end. */
+		_spEventQueue_event(internal->queue, entry, event);
 	}
+	internal->eventsCount = 0;
 }
 
 void spAnimationState_clearTracks (spAnimationState* self) {
-	int i;
-	for (i = 0; i < self->tracksCount; ++i)
+	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+	int i, n;
+	internal->queue->drainDisabled = 1;
+	for (i = 0, n = self->tracksCount; i < n; i++)
 		spAnimationState_clearTrack(self, i);
 	self->tracksCount = 0;
+	internal->queue->drainDisabled = 0;
+	_spEventQueue_drain(internal->queue);
 }
 
 void spAnimationState_clearTrack (spAnimationState* self, int trackIndex) {
 	spTrackEntry* current;
+	spTrackEntry* entry;
+	spTrackEntry* from;
+	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+
 	if (trackIndex >= self->tracksCount) return;
 	current = self->tracks[trackIndex];
 	if (!current) return;
 
-	if (current->listener) current->listener(self, trackIndex, SP_ANIMATION_END, 0, 0);
-	if (self->listener) self->listener(self, trackIndex, SP_ANIMATION_END, 0, 0);
+	_spEventQueue_end(internal->queue, current);
 
-	self->tracks[trackIndex] = 0;
+	_spAnimationState_disposeNext(self, current);
 
-	_spAnimationState_disposeAllEntries(self, current);
-}
+	entry = current;
+	while (1) {
+		from = entry->mixingFrom;
+		if (!from) break;
+		_spEventQueue_end(internal->queue, from);
+		entry->mixingFrom = 0;
+		entry = from;
+	}
 
-spTrackEntry* _spAnimationState_expandToIndex (spAnimationState* self, int index) {
-	spTrackEntry** newTracks;
-	if (index < self->tracksCount) return self->tracks[index];
-	newTracks = CALLOC(spTrackEntry*, index + 1);
-	memcpy(newTracks, self->tracks, self->tracksCount * sizeof(spTrackEntry*));
-	FREE(self->tracks);
-	self->tracks = newTracks;
-	self->tracksCount = index + 1;
-	return 0;
+	self->tracks[current->trackIndex] = 0;
+	_spEventQueue_drain(internal->queue);
 }
 
-void _spAnimationState_setCurrent (spAnimationState* self, int index, spTrackEntry* entry) {
+void _spAnimationState_setCurrent (spAnimationState* self, int index, spTrackEntry* current) {
 	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+	spTrackEntry* from = _spAnimationState_expandToIndex(self, index);
+	self->tracks[index] = current;
 
-	spTrackEntry* current = _spAnimationState_expandToIndex(self, index);
-	if (current) {
-		spTrackEntry* previous = current->previous;
-		current->previous = 0;
-
-		if (current->listener) current->listener(self, index, SP_ANIMATION_END, 0, 0);
-		if (self->listener) self->listener(self, index, SP_ANIMATION_END, 0, 0);
-
-		entry->mixDuration = spAnimationStateData_getMix(self->data, current->animation, entry->animation);
-		if (entry->mixDuration > 0) {
-			entry->mixTime = 0;
-			/* If a mix is in progress, mix from the closest animation. */
-			if (previous && current->mixTime / current->mixDuration < 0.5f) {
-				entry->previous = previous;
-				previous = current;
-			} else
-				entry->previous = current;
-		} else
-			internal->disposeTrackEntry(current);
+	if (from) {
+		_spEventQueue_interrupt(internal->queue, from);
+		current->mixingFrom = from;
+		current->mixTime = 0;
 
-		if (previous) internal->disposeTrackEntry(previous);
+		/* If not completely mixed in, set mixAlpha so mixing out happens from current mix to zero. */
+		if (from->mixingFrom) current->mixAlpha *= MIN(from->mixTime / from->mixDuration, 1);
 	}
 
-	self->tracks[index] = entry;
-
-	if (entry->listener) {
-		entry->listener(self, index, SP_ANIMATION_START, 0, 0);
-		if (self->tracks[index] != entry) return;
-	}
-	if (self->listener) self->listener(self, index, SP_ANIMATION_START, 0, 0);
+	_spEventQueue_start(internal->queue, current);
 }
 
+/** Set the current animation. Any queued animations are cleared. */
 spTrackEntry* spAnimationState_setAnimationByName (spAnimationState* self, int trackIndex, const char* animationName,
-		int/*bool*/loop) {
+												   int/*bool*/loop) {
 	spAnimation* animation = spSkeletonData_findAnimation(self->data->skeletonData, animationName);
 	return spAnimationState_setAnimation(self, trackIndex, animation, loop);
 }
 
 spTrackEntry* spAnimationState_setAnimation (spAnimationState* self, int trackIndex, spAnimation* animation, int/*bool*/loop) {
-	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
-
 	spTrackEntry* entry;
+	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
 	spTrackEntry* current = _spAnimationState_expandToIndex(self, trackIndex);
-	if (current) _spAnimationState_disposeAllEntries(self, current->next);
-
-	entry = internal->createTrackEntry(self);
-	entry->animation = animation;
-	entry->loop = loop;
-	entry->endTime = animation->duration;
+	if (current) {
+		if (current->nextTrackLast == -1) {
+			/* Don't mix from an entry that was never applied. */
+			self->tracks[trackIndex] = 0;
+			_spEventQueue_interrupt(internal->queue, current);
+			_spEventQueue_end(internal->queue, current);
+			_spAnimationState_disposeNext(self, current);
+			current = 0;
+		} else
+			_spAnimationState_disposeNext(self, current);
+	}
+	entry = _spAnimationState_trackEntry(self, trackIndex, animation, loop, current);
 	_spAnimationState_setCurrent(self, trackIndex, entry);
+	_spEventQueue_drain(internal->queue);
 	return entry;
 }
 
+/** Adds an animation to be played delay seconds after the current or last queued animation, taking into account any mix
+ * duration. */
 spTrackEntry* spAnimationState_addAnimationByName (spAnimationState* self, int trackIndex, const char* animationName,
-		int/*bool*/loop, float delay) {
+												   int/*bool*/loop, float delay) {
 	spAnimation* animation = spSkeletonData_findAnimation(self->data->skeletonData, animationName);
 	return spAnimationState_addAnimation(self, trackIndex, animation, loop, delay);
 }
 
 spTrackEntry* spAnimationState_addAnimation (spAnimationState* self, int trackIndex, spAnimation* animation, int/*bool*/loop,
-		float delay) {
+											 float delay) {
+	spTrackEntry* entry;
 	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
-	spTrackEntry* last;
-
-	spTrackEntry* entry = internal->createTrackEntry(self);
-	entry->animation = animation;
-	entry->loop = loop;
-	entry->endTime = animation->duration;
-
-	last = _spAnimationState_expandToIndex(self, trackIndex);
+	spTrackEntry* last = _spAnimationState_expandToIndex(self, trackIndex);
 	if (last) {
 		while (last->next)
 			last = last->next;
-		last->next = entry;
-	} else
-		self->tracks[trackIndex] = entry;
+	}
+
+	entry = _spAnimationState_trackEntry(self, trackIndex, animation, loop, last);
 
-	if (delay <= 0) {
-		if (last)
-			delay += last->endTime - spAnimationStateData_getMix(self->data, last->animation, animation);
-		else
-			delay = 0;
+	if (!last) {
+		_spAnimationState_setCurrent(self, trackIndex, entry);
+		_spEventQueue_drain(internal->queue);
+	} else {
+		last->next = entry;
+		if (delay <= 0) {
+			float duration = last->animationEnd - last->animationStart;
+			if (duration != 0)
+				delay += duration * (1 + (int)(last->trackTime / duration)) - spAnimationStateData_getMix(self->data, last->animation, animation);
+			else
+				delay = 0;
+		}
 	}
+
 	entry->delay = delay;
+	return entry;
+}
 
+spTrackEntry* spAnimationState_setEmptyAnimation(spAnimationState* self, int trackIndex, float mixDuration) {
+	spTrackEntry* entry = spAnimationState_setAnimation(self, trackIndex, SP_EMPTY_ANIMATION, 0);
+	entry->mixDuration = mixDuration;
+	entry->trackEnd = mixDuration;
 	return entry;
 }
 
+spTrackEntry* spAnimationState_addEmptyAnimation(spAnimationState* self, int trackIndex, float mixDuration, float delay) {
+	spTrackEntry* entry;
+	if (delay <= 0) delay -= mixDuration;
+	entry = spAnimationState_addAnimation(self, trackIndex, SP_EMPTY_ANIMATION, 0, delay);
+	entry->mixDuration = mixDuration;
+	entry->trackEnd = mixDuration;
+	return entry;
+}
+
+void spAnimationState_setEmptyAnimations(spAnimationState* self, float mixDuration) {
+	int i, n;
+	spTrackEntry* current;
+	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+	internal->queue->drainDisabled = 1;
+	for (i = 0, n = self->tracksCount; i < n; i++) {
+		current = self->tracks[i];
+		if (current) spAnimationState_setEmptyAnimation(self, current->trackIndex, mixDuration);
+	}
+	internal->queue->drainDisabled = 0;
+	_spEventQueue_drain(internal->queue);
+}
+
+spTrackEntry* _spAnimationState_expandToIndex (spAnimationState* self, int index) {
+	spTrackEntry** newTracks;
+	if (index < self->tracksCount) return self->tracks[index];
+	newTracks = CALLOC(spTrackEntry*, index + 1);
+	memcpy(newTracks, self->tracks, self->tracksCount * sizeof(spTrackEntry*));
+	FREE(self->tracks);
+	self->tracks = newTracks;
+	self->tracksCount = index + 1;
+	return 0;
+}
+
+spTrackEntry* _spAnimationState_trackEntry (spAnimationState* self, int trackIndex, spAnimation* animation, int /*boolean*/ loop, spTrackEntry* last) {
+	spTrackEntry* entry = NEW(spTrackEntry);
+	entry->trackIndex = trackIndex;
+	entry->animation = animation;
+	entry->loop = loop;
+
+	entry->eventThreshold = 0;
+	entry->attachmentThreshold = 0;
+	entry->drawOrderThreshold = 0;
+
+	entry->animationStart = 0;
+	entry->animationEnd = animation->duration;
+	entry->animationLast = -1;
+	entry->nextAnimationLast = -1;
+
+	entry->delay = 0;
+	entry->trackTime = 0;
+	entry->trackLast = -1;
+	entry->nextTrackLast = -1;
+	entry->trackEnd = loop ? (float)INT_MAX : entry->animationEnd;
+	entry->timeScale = 1;
+
+	entry->alpha = 1;
+	entry->mixAlpha = 1;
+	entry->mixTime = 0;
+	entry->mixDuration = !last ? 0 : spAnimationStateData_getMix(self->data, last->animation, animation);
+	return entry;
+}
+
+void _spAnimationState_disposeNext (spAnimationState* self, spTrackEntry* entry) {
+	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+	spTrackEntry* next = entry->next;
+	while (next) {
+		_spEventQueue_dispose(internal->queue, next);
+		next = next->next;
+	}
+	entry->next = 0;
+}
+
+void _spAnimationState_animationsChanged (spAnimationState* self) {
+	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+	int i, n;
+	spTrackEntry* entry;
+	internal->animationsChanged = 0;
+
+	i = 0; n = self->tracksCount;
+	internal->propertyIDsCount = 0;
+
+	for (; i < n; i++) {
+		entry = self->tracks[i];
+		if (!entry) continue;
+		_spAnimationState_setTimelinesFirst(self, entry);
+		i++;
+		break;
+	}
+	for (; i < n; i++) {
+		entry = self->tracks[i];
+		if (entry) _spAnimationState_checkTimelinesFirst(self, entry);
+	}
+}
+
+float* _spAnimationState_resizeTimelinesRotation(spTrackEntry* entry, int newSize) {
+	if (entry->timelinesRotationCount != newSize) {
+		float* newTimelinesRotation = CALLOC(float, newSize);
+		FREE(entry->timelinesRotation);
+		entry->timelinesRotation = newTimelinesRotation;
+		entry->timelinesRotationCount = newSize;
+	}
+	return entry->timelinesRotation;
+}
+
+int* _spAnimationState_resizeTimelinesFirst(spTrackEntry* entry, int newSize) {
+	if (entry->timelinesFirstCount != newSize) {
+		int* newTimelinesFirst = CALLOC(int, newSize);
+		FREE(entry->timelinesFirst);
+		entry->timelinesFirst = newTimelinesFirst;
+		entry->timelinesFirstCount = newSize;
+	}
+
+	return entry->timelinesFirst;
+}
+
+void _spAnimationState_ensureCapacityPropertyIDs(spAnimationState* self, int capacity) {
+	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+	if (internal->propertyIDsCapacity < capacity) {
+		int *newPropertyIDs = CALLOC(int, capacity << 1);
+		memcpy(newPropertyIDs, internal->propertyIDs, internal->propertyIDsCount);
+		FREE(internal->propertyIDs);
+		internal->propertyIDs = newPropertyIDs;
+		internal->propertyIDsCapacity = capacity << 1;
+	}
+}
+
+int _spAnimationState_addPropertyID(spAnimationState* self, int id) {
+	int i, n;
+	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+
+	for (i = 0, n = internal->propertyIDsCount; i < n; i++) {
+		if (internal->propertyIDs[i] == id) return 0;
+	}
+
+	_spAnimationState_ensureCapacityPropertyIDs(self, internal->propertyIDsCount + 1);
+	internal->propertyIDs[internal->propertyIDsCount] = id;
+	return 1;
+}
+
+void _spAnimationState_setTimelinesFirst (spAnimationState* self, spTrackEntry* entry) {
+	int i, n;
+	int* usage;
+	spTimeline** timelines;
+
+	if (entry->mixingFrom) {
+		_spAnimationState_setTimelinesFirst(self, entry->mixingFrom);
+		_spAnimationState_checkTimelinesUsage(self, entry);
+		return;
+	}
+
+	n = entry->animation->timelinesCount;
+	timelines = entry->animation->timelines;
+	usage = _spAnimationState_resizeTimelinesFirst(entry, n);
+	for (i = 0; i < n; i++) {
+		_spAnimationState_addPropertyID(self, spTimeline_getPropertyId(timelines[i]));
+		usage[i] = 1;
+	}
+}
+
+void _spAnimationState_checkTimelinesFirst (spAnimationState* self, spTrackEntry* entry) {
+	if (entry->mixingFrom) _spAnimationState_checkTimelinesFirst(self, entry->mixingFrom);
+	_spAnimationState_checkTimelinesUsage(self, entry);
+}
+
+void _spAnimationState_checkTimelinesUsage (spAnimationState* self, spTrackEntry* entry) {
+	int i, n;
+	int* usage;
+	spTimeline** timelines;
+	n = entry->animation->timelinesCount;
+	timelines = entry->animation->timelines;
+	usage = _spAnimationState_resizeTimelinesFirst(entry, n);
+	for (i = 0; i < n; i++)
+		usage[i] = _spAnimationState_addPropertyID(self, spTimeline_getPropertyId(timelines[i]));
+}
+
 spTrackEntry* spAnimationState_getCurrent (spAnimationState* self, int trackIndex) {
 	if (trackIndex >= self->tracksCount) return 0;
 	return self->tracks[trackIndex];
 }
+
+void spAnimationState_clearListenerNotifications(spAnimationState* self) {
+	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
+	_spEventQueue_clear(internal->queue);
+}
+
+float spTrackEntry_getAnimationTime (spTrackEntry* entry) {
+	if (entry->loop) {
+		float duration = entry->animationEnd - entry->animationStart;
+		if (duration == 0) return entry->animationStart;
+		return FMOD(entry->trackTime, duration) + entry->animationStart;
+	}
+	return MIN(entry->trackTime + entry->animationStart, entry->animationEnd);
+}

+ 121 - 107
spine-c/src/spine/Bone.c

@@ -61,15 +61,24 @@ void spBone_updateWorldTransform (spBone* self) {
 
 void spBone_updateWorldTransformWith (spBone* self, float x, float y, float rotation, float scaleX, float scaleY, float shearX, float shearY) {
 	float cosine, sine;
-	float rotationY = rotation + 90 + shearY;
-	float la = COS_DEG(rotation + shearX) * scaleX, lb = COS_DEG(rotationY) * scaleY;
-	float lc = SIN_DEG(rotation + shearX) * scaleX, ld = SIN_DEG(rotationY) * scaleY;
-	float pa, pb, pc, pd, temp;
+	float pa, pb, pc, pd;
 	spBone* parent = self->parent;
 
-	CONST_CAST(float, self->appliedRotation) = rotation;
+	self->ax = x;
+	self->ay = y;
+	self->arotation = rotation;
+	self->ascaleX = scaleX;
+	self->ascaleY = scaleY;
+	self->ashearX = shearX;
+	self->ashearY = shearY;
+	self->appliedValid = 1;
 
 	if (!parent) { /* Root bone. */
+		float rotationY = rotation + 90 + shearY;
+		float la = COS_DEG(rotation + shearX) * scaleX;
+		float lb = COS_DEG(rotationY) * scaleY;
+		float lc = SIN_DEG(rotation + shearX) * scaleX;
+		float ld = SIN_DEG(rotationY) * scaleY;
 		if (self->skeleton->flipX) {
 			x = -x;
 			la = -la;
@@ -84,10 +93,8 @@ void spBone_updateWorldTransformWith (spBone* self, float x, float y, float rota
 		CONST_CAST(float, self->b) = lb;
 		CONST_CAST(float, self->c) = lc;
 		CONST_CAST(float, self->d) = ld;
-		CONST_CAST(float, self->worldX) = x;
-		CONST_CAST(float, self->worldY) = y;
-		CONST_CAST(float, self->worldSignX) = scaleX > 0 ? 1.0f : -1.0f;
-		CONST_CAST(float, self->worldSignY) = scaleY > 0 ? 1.0f : -1.0f;
+		CONST_CAST(float, self->worldX) = x + self->skeleton->x;
+		CONST_CAST(float, self->worldY) = y + self->skeleton->y;
 		return;
 	}
 
@@ -98,84 +105,91 @@ void spBone_updateWorldTransformWith (spBone* self, float x, float y, float rota
 
 	CONST_CAST(float, self->worldX) = pa * x + pb * y + parent->worldX;
 	CONST_CAST(float, self->worldY) = pc * x + pd * y + parent->worldY;
-	CONST_CAST(float, self->worldSignX) = parent->worldSignX * (scaleX > 0 ? 1 : -1);
-	CONST_CAST(float, self->worldSignY) = parent->worldSignY * (scaleY > 0 ? 1 : -1);
 
-	if (self->data->inheritRotation && self->data->inheritScale) {
-		CONST_CAST(float, self->a) = pa * la + pb * lc;
-		CONST_CAST(float, self->b) = pa * lb + pb * ld;
-		CONST_CAST(float, self->c) = pc * la + pd * lc;
-		CONST_CAST(float, self->d) = pc * lb + pd * ld;
-	} else {
-		if (self->data->inheritRotation) { /* No scale inheritance. */
-			pa = 1;
-			pb = 0;
-			pc = 0;
-			pd = 1;
-			do {
-				cosine = COS_DEG(parent->appliedRotation); sine = SIN_DEG(parent->appliedRotation);
-				temp = pa * cosine + pb * sine;
-				pb = pb * cosine - pa * sine;
-				pa = temp;
-				temp = pc * cosine + pd * sine;
-				pd = pd * cosine - pc * sine;
-				pc = temp;
-
-				if (!parent->data->inheritRotation) break;
-				parent = parent->parent;
-			} while (parent);
+	switch (self->data->transformMode) {
+		case SP_TRANSFORMMODE_NORMAL: {
+			float rotationY = rotation + 90 + shearY;
+			float la = COS_DEG(rotation + shearX) * scaleX;
+			float lb = COS_DEG(rotationY) * scaleY;
+			float lc = SIN_DEG(rotation + shearX) * scaleX;
+			float ld = SIN_DEG(rotationY) * scaleY;
 			CONST_CAST(float, self->a) = pa * la + pb * lc;
 			CONST_CAST(float, self->b) = pa * lb + pb * ld;
 			CONST_CAST(float, self->c) = pc * la + pd * lc;
 			CONST_CAST(float, self->d) = pc * lb + pd * ld;
-		} else if (self->data->inheritScale) { /* No rotation inheritance. */
-			pa = 1;
-			pb = 0;
-			pc = 0;
-			pd = 1;
-			do {
-				float za, zb, zc, zd;
-				float psx = parent->scaleX, psy = parent->scaleY;
-				cosine = COS_DEG(parent->appliedRotation);
-				sine = SIN_DEG(parent->appliedRotation);
-				za = cosine * psx; zb = sine * psy; zc = sine * psx; zd = cosine * psy;
-				temp = pa * za + pb * zc;
-				pb = pb * zd - pa * zb;
-				pa = temp;
-				temp = pc * za + pd * zc;
-				pd = pd * zd - pc * zb;
-				pc = temp;
-
-				if (psx >= 0) sine = -sine;
-				temp = pa * cosine + pb * sine;
-				pb = pb * cosine - pa * sine;
-				pa = temp;
-				temp = pc * cosine + pd * sine;
-				pd = pd * cosine - pc * sine;
-				pc = temp;
-
-				if (!parent->data->inheritScale) break;
-				parent = parent->parent;
-			} while (parent);
-			CONST_CAST(float, self->a) = pa * la + pb * lc;
-			CONST_CAST(float, self->b) = pa * lb + pb * ld;
+			return;
+		}
+		case SP_TRANSFORMMODE_ONLYTRANSLATION: {
+			float rotationY = rotation + 90 + shearY;
+			CONST_CAST(float, self->a) = COS_DEG(rotation + shearX) * scaleX;
+			CONST_CAST(float, self->b) = COS_DEG(rotationY) * scaleY;
+			CONST_CAST(float, self->c) = SIN_DEG(rotation + shearX) * scaleX;
+			CONST_CAST(float, self->d) = SIN_DEG(rotationY) * scaleY;
+			break;
+		}
+		case SP_TRANSFORMMODE_NOROTATIONORREFLECTION: {
+			float s = pa * pa + pc * pc;
+			float prx, rx, ry, la, lb, lc, ld;
+			if (s > 0.0001f) {
+				s = ABS(pa * pd - pb * pc) / s;
+				pb = pc * s;
+				pd = pa * s;
+				prx = ATAN2(pc, pa) * RAD_DEG;
+			} else {
+				pa = 0;
+				pc = 0;
+				prx = 90 - ATAN2(pd, pb) * RAD_DEG;
+			}
+			rx = rotation + shearX - prx;
+			ry = rotation + shearY - prx + 90;
+			la = COS_DEG(rx) * scaleX;
+			lb = COS_DEG(ry) * scaleY;
+			lc = SIN_DEG(rx) * scaleX;
+			ld = SIN_DEG(ry) * scaleY;
+			CONST_CAST(float, self->a) = pa * la - pb * lc;
+			CONST_CAST(float, self->b) = pa * lb - pb * ld;
 			CONST_CAST(float, self->c) = pc * la + pd * lc;
 			CONST_CAST(float, self->d) = pc * lb + pd * ld;
-		} else {
-			CONST_CAST(float, self->a) = la;
-			CONST_CAST(float, self->b) = lb;
-			CONST_CAST(float, self->c) = lc;
-			CONST_CAST(float, self->d) = ld;
-		}
-		if (self->skeleton->flipX) {
-			CONST_CAST(float, self->a) = -self->a;
-			CONST_CAST(float, self->b) = -self->b;
+			break;
 		}
-		if (self->skeleton->flipY != yDown) {
-			CONST_CAST(float, self->c) = -self->c;
-			CONST_CAST(float, self->d) = -self->d;
+		case SP_TRANSFORMMODE_NOSCALE:
+		case SP_TRANSFORMMODE_NOSCALEORREFLECTION: {
+			float za, zc, s;
+			float r, zb, zd, la, lb, lc, ld;
+			cosine = COS_DEG(rotation); sine = SIN_DEG(rotation);
+			za = pa * cosine + pb * sine;
+			zc = pc * cosine + pd * sine;
+			s = SQRT(za * za + zc * zc);
+			if (s > 0.00001f) s = 1 / s;
+			za *= s;
+			zc *= s;
+			s = SQRT(za * za + zc * zc);
+			r = PI / 2 + atan2(zc, za);
+			zb = COS(r) * s;
+			zd = SIN(r) * s;
+			la = COS_DEG(shearX) * scaleX;
+			lb = COS_DEG(90 + shearY) * scaleY;
+			lc = SIN_DEG(shearX) * scaleX;
+			ld = SIN_DEG(90 + shearY) * scaleY;
+			CONST_CAST(float, self->a) = za * la + zb * lc;
+			CONST_CAST(float, self->b) = za * lb + zb * ld;
+			CONST_CAST(float, self->c) = zc * la + zd * lc;
+			CONST_CAST(float, self->d) = zc * lb + zd * ld;
+			if (self->data->transformMode != SP_TRANSFORMMODE_NOSCALEORREFLECTION ? pa * pd - pb * pc < 0 : self->skeleton->flipX != self->skeleton->flipY) {
+				CONST_CAST(float, self->b) = -self->b;
+				CONST_CAST(float, self->d) = -self->d;
+			}
+			return;
 		}
 	}
+	if (self->skeleton->flipX) {
+		CONST_CAST(float, self->a) = -self->a;
+		CONST_CAST(float, self->b) = -self->b;
+	}
+	if (self->skeleton->flipY != yDown) {
+		CONST_CAST(float, self->c) = -self->c;
+		CONST_CAST(float, self->d) = -self->d;
+	}
 }
 
 void spBone_setToSetupPose (spBone* self) {
@@ -197,22 +211,22 @@ float spBone_getWorldRotationY (spBone* self) {
 }
 
 float spBone_getWorldScaleX (spBone* self) {
-	return SQRT(self->a * self->a + self->b * self->b) * self->worldSignX;
+	return SQRT(self->a * self->a + self->c * self->c);
 }
 
 float spBone_getWorldScaleY (spBone* self) {
-	return SQRT(self->c * self->c + self->d * self->d) * self->worldSignY;
+	return SQRT(self->b * self->b + self->d * self->d);
 }
 
 float spBone_worldToLocalRotationX (spBone* self) {
 	spBone* parent = self->parent;
-	if (!parent) return self->rotation;
+	if (!parent) return self->arotation;
 	return ATAN2(parent->a * self->c - parent->c * self->a, parent->d * self->a - parent->b * self->c) * RAD_DEG;
 }
 
 float spBone_worldToLocalRotationY (spBone* self) {
 	spBone* parent = self->parent;
-	if (!parent) return self->rotation;
+	if (!parent) return self->arotation;
 	return ATAN2(parent->a * self->d - parent->c * self->b, parent->d * self->b - parent->b * self->d) * RAD_DEG;
 }
 
@@ -223,24 +237,25 @@ void spBone_rotateWorld (spBone* self, float degrees) {
 	CONST_CAST(float, self->b) = cosine * b - sine * d;
 	CONST_CAST(float, self->c) = sine * a + cosine * c;
 	CONST_CAST(float, self->d) = sine * b + cosine * d;
+	CONST_CAST(int, self->appliedValid) = 1;
 }
 
-/** Computes the local transform from the world transform. This can be useful to perform processing on the local transform
- * after the world transform has been modified directly (eg, by a constraint).
+
+/** Computes the individual applied transform values from the world transform. This can be useful to perform processing using
+ * the applied transform after the world transform has been modified directly (eg, by a constraint).
  * <p>
- * Some redundant information is lost by the world transform, such as -1,-1 scale versus 180 rotation. The computed local
- * transform values may differ from the original values but are functionally the same. */
-void spBone_updateLocalTransform (spBone* self) {
+ * Some information is ambiguous in the world transform, such as -1,-1 scale versus 180 rotation. */
+void spBone_updateAppliedTransform (spBone* self) {
 	spBone* parent = self->parent;
+	self->appliedValid = 1;
 	if (!parent) {
-		float det = self->a * self->d - self->b * self->c;
-		self->x = self->worldX;
-		self->y = self->worldY;
-		self->rotation = ATAN2(self->c, self->a) * RAD_DEG;
-		self->scaleX = SQRT(self->a * self->a + self->c * self->c);
-		self->scaleY = SQRT(self->b * self->b + self->d * self->d);
-		self->shearX = 0;
-		self->shearY = ATAN2(self->a * self->b + self->c * self->d, det) * RAD_DEG;
+		self->ax = self->worldX;
+		self->ay = self->worldY;
+		self->arotation = ATAN2(self->c, self->a) * RAD_DEG;
+		self->ascaleX = SQRT(self->a * self->a + self->c * self->c);
+		self->ascaleY = SQRT(self->b * self->b + self->d * self->d);
+		self->ashearX = 0;
+		self->ashearY = ATAN2(self->a * self->b + self->c * self->d, self->a * self->d - self->b * self->c) * RAD_DEG;
 	} else {
 		float pa = parent->a, pb = parent->b, pc = parent->c, pd = parent->d;
 		float pid = 1 / (pa * pd - pb * pc);
@@ -253,22 +268,21 @@ void spBone_updateLocalTransform (spBone* self) {
 		float rb = ia * self->b - ib * self->d;
 		float rc = id * self->c - ic * self->a;
 		float rd = id * self->d - ic * self->b;
-		self->x = (dx * pd * pid - dy * pb * pid);
-		self->y = (dy * pa * pid - dx * pc * pid);
-		self->shearX = 0;
-		self->scaleX = SQRT(ra * ra + rc * rc);
-		if (self->scaleX > 0.0001f) {
+		self->ax = (dx * pd * pid - dy * pb * pid);
+		self->ay = (dy * pa * pid - dx * pc * pid);
+		self->ashearX = 0;
+		self->ascaleX = SQRT(ra * ra + rc * rc);
+		if (self->ascaleX > 0.0001f) {
 			float det = ra * rd - rb * rc;
-			self->scaleY = det / self->scaleX;
-			self->shearY = ATAN2(ra * rb + rc * rd, det) * RAD_DEG;
-			self->rotation = ATAN2(rc, ra) * RAD_DEG;
+			self->ascaleY = det / self->ascaleX;
+			self->ashearY = ATAN2(ra * rb + rc * rd, det) * RAD_DEG;
+			self->arotation = ATAN2(rc, ra) * RAD_DEG;
 		} else {
-			self->scaleX = 0;
-			self->scaleY = SQRT(rb * rb + rd * rd);
-			self->shearY = 0;
-			self->rotation = 90 - ATAN2(rd, rb) * RAD_DEG;
+			self->ascaleX = 0;
+			self->ascaleY = SQRT(rb * rb + rd * rd);
+			self->ashearY = 0;
+			self->arotation = 90 - ATAN2(rd, rb) * RAD_DEG;
 		}
-		self->appliedRotation = self->rotation;
 	}
 }
 

+ 1 - 2
spine-c/src/spine/BoneData.c

@@ -38,8 +38,7 @@ spBoneData* spBoneData_create (int index, const char* name, spBoneData* parent)
 	CONST_CAST(spBoneData*, self->parent) = parent;
 	self->scaleX = 1;
 	self->scaleY = 1;
-	self->inheritRotation = 1;
-	self->inheritScale = 1;
+	self->transformMode = SP_TRANSFORMMODE_NORMAL;
 	return self;
 }
 

+ 21 - 15
spine-c/src/spine/IkConstraint.c

@@ -67,21 +67,23 @@ void spIkConstraint_apply(spIkConstraint *self) {
 }
 
 void spIkConstraint_apply1 (spBone* bone, float targetX, float targetY, float alpha) {
-	spBone* pp = bone->parent;
-	float id = 1 / (pp->a * pp->d - pp->b * pp->c);
-	float x = targetX - pp->worldX, y = targetY - pp->worldY;
-	float tx = (x * pp->d - y * pp->b) * id - bone->x, ty = (y * pp->a - x * pp->c) * id - bone->y;
-	float rotationIK = ATAN2(ty, tx) * RAD_DEG - bone->shearX - bone->rotation;
-	if (bone->scaleX < 0) rotationIK += 180;
+	spBone* p = bone->parent;
+	float id, x, y, tx, ty, rotationIK;
+	if (!bone->appliedValid) spBone_updateAppliedTransform(bone);
+	id = 1 / (p->a * p->d - p->b * p->c);
+	x = targetX - p->worldX, y = targetY - p->worldY;
+	tx = (x * p->d - y * p->b) * id - bone->ax; ty = (y * p->a - x * p->c) * id - bone->ay;
+	rotationIK = ATAN2(ty, tx) * RAD_DEG - bone->ashearX - bone->arotation;
+	if (bone->ascaleX < 0) rotationIK += 180;
 	if (rotationIK > 180) rotationIK -= 360;
 	else if (rotationIK < -180) rotationIK += 360;
-	spBone_updateWorldTransformWith(bone, bone->x, bone->y, bone->rotation + rotationIK * alpha, bone->scaleX,
-		bone->scaleY, bone->shearX, bone->shearY);
+	spBone_updateWorldTransformWith(bone, bone->ax, bone->ay, bone->arotation + rotationIK * alpha, bone->ascaleX,
+		bone->ascaleY, bone->ashearX, bone->ashearY);
 }
 
 void spIkConstraint_apply2 (spBone* parent, spBone* child, float targetX, float targetY, int bendDir, float alpha) {
-	float px = parent->x, py = parent->y, psx = parent->scaleX, psy = parent->scaleY;
-	float cx = child->x, cy, csx = child->scaleX, cwx, cwy;
+	float px, py, psx, psy;
+	float cx, cy, csx, cwx, cwy;
 	int o1, o2, s2, u;
 	spBone* pp = parent->parent;
 	float tx, ty, dx, dy, l1, l2, a1, a2, r;
@@ -90,6 +92,9 @@ void spIkConstraint_apply2 (spBone* parent, spBone* child, float targetX, float
 		spBone_updateWorldTransform(child);
 		return;
 	}
+	if (!parent->appliedValid) spBone_updateAppliedTransform(parent);
+	if (!child->appliedValid) spBone_updateAppliedTransform(child);
+	px = parent->ax; py = parent->ay; psx = parent->ascaleX; psy = parent->ascaleY; csx = child->ascaleX;
 	if (psx < 0) {
 		psx = -psx;
 		o1 = 180;
@@ -108,13 +113,14 @@ void spIkConstraint_apply2 (spBone* parent, spBone* child, float targetX, float
 	} else
 		o2 = 0;
 	r = psx - psy;
+	cx = child->ax;
 	u = (r < 0 ? -r : r) <= 0.0001f;
 	if (!u) {
 		cy = 0;
 		cwx = parent->a * cx + parent->worldX;
 		cwy = parent->c * cx + parent->worldY;
 	} else {
-		cy = child->y;
+		cy = child->ay;
 		cwx = parent->a * cx + parent->b * cy + parent->worldX;
 		cwy = parent->c * cx + parent->d * cy + parent->worldY;
 	}
@@ -198,13 +204,13 @@ void spIkConstraint_apply2 (spBone* parent, spBone* child, float targetX, float
 	}
 	outer: {
 		float os = ATAN2(cy, cx) * s2;
-		a1 = (a1 - os) * RAD_DEG + o1 - parent->rotation;
+		a1 = (a1 - os) * RAD_DEG + o1 - parent->arotation;
 		if (a1 > 180) a1 -= 360;
 		else if (a1 < -180) a1 += 360;
-		spBone_updateWorldTransformWith(parent, px, py, parent->rotation + a1 * alpha, parent->scaleX, parent->scaleY, 0, 0);
-		a2 = ((a2 + os) * RAD_DEG - child->shearX) * s2 + o2 - child->rotation;
+		spBone_updateWorldTransformWith(parent, px, py, parent->rotation + a1 * alpha, parent->ascaleX, parent->ascaleY, 0, 0);
+		a2 = ((a2 + os) * RAD_DEG - child->ashearX) * s2 + o2 - child->arotation;
 		if (a2 > 180) a2 -= 360;
 		else if (a2 < -180) a2 += 360;
-		spBone_updateWorldTransformWith(child, cx, cy, child->rotation + a2 * alpha, child->scaleX, child->scaleY, child->shearX, child->shearY);
+		spBone_updateWorldTransformWith(child, cx, cy, child->arotation + a2 * alpha, child->ascaleX, child->ascaleY, child->ashearX, child->ashearY);
 	}
 }

+ 15 - 8
spine-c/src/spine/PathConstraint.c

@@ -77,8 +77,7 @@ void spPathConstraint_apply (spPathConstraint* self) {
 	float length, x, y, dx, dy, s;
 	float* spaces, *lengths, *positions;
 	float spacing;
-	spSkeleton* skeleton;
-	float skeletonX, skeletonY, boneX, boneY, offsetRotation;
+	float boneX, boneY, offsetRotation;
 	int/*bool*/tip;
 	float rotateMix = self->rotateMix, translateMix = self->translateMix;
 	int/*bool*/ translate = translateMix > 0, rotate = rotateMix > 0;
@@ -90,6 +89,7 @@ void spPathConstraint_apply (spPathConstraint* self) {
 	int tangents = rotateMode == SP_ROTATE_MODE_TANGENT, scale = rotateMode == SP_ROTATE_MODE_CHAIN_SCALE;
 	int boneCount = self->bonesCount, spacesCount = tangents ? boneCount : boneCount + 1;
 	spBone** bones = self->bones;
+	spBone* pa;
 
 	if (!translate && !rotate) return;
 	if ((attachment == 0) || (attachment->super.super.type != SP_ATTACHMENT_PATH)) return;
@@ -127,14 +127,19 @@ void spPathConstraint_apply (spPathConstraint* self) {
 
 	positions = spPathConstraint_computeWorldPositions(self, attachment, spacesCount, tangents,
 		data->positionMode == SP_POSITION_MODE_PERCENT, spacingMode == SP_SPACING_MODE_PERCENT);
-	skeleton = self->target->bone->skeleton;
-	skeletonX = skeleton->x, skeletonY = skeleton->y;
 	boneX = positions[0], boneY = positions[1], offsetRotation = self->data->offsetRotation;
-	tip = rotateMode == SP_ROTATE_MODE_CHAIN_SCALE && offsetRotation == 0;
+	tip = 0;
+	if (offsetRotation == 0)
+		tip = rotateMode == SP_ROTATE_MODE_CHAIN;
+	else {
+		tip = 0;
+		pa = self->target->bone;
+		offsetRotation *= pa->a * pa->d - pa->b * pa->c > 0 ? DEG_RAD : -DEG_RAD;
+	}
 	for (i = 0, p = 3; i < boneCount; i++, p += 3) {
 		spBone* bone = bones[i];
-		CONST_CAST(float, bone->worldX) += (boneX - skeletonX - bone->worldX) * translateMix;
-		CONST_CAST(float, bone->worldY) += (boneY - skeletonY - bone->worldY) * translateMix;
+		CONST_CAST(float, bone->worldX) += (boneX - bone->worldX) * translateMix;
+		CONST_CAST(float, bone->worldY) += (boneY - bone->worldY) * translateMix;
 		x = positions[p], y = positions[p + 1], dx = x - boneX, dy = y - boneY;
 		if (scale) {
 			length = lengths[i];
@@ -161,7 +166,8 @@ void spPathConstraint_apply (spPathConstraint* self) {
 				length = bone->data->length;
 				boneX += (length * (cosine * a - sine * c) - dx) * rotateMix;
 				boneY += (length * (sine * a + cosine * c) - dy) * rotateMix;
-			}
+			} else
+				r += offsetRotation;
 			if (r > PI)
 				r -= PI2;
 			else if (r < -PI)
@@ -174,6 +180,7 @@ void spPathConstraint_apply (spPathConstraint* self) {
 			CONST_CAST(float, bone->c) = sine * a + cosine * c;
 			CONST_CAST(float, bone->d) = sine * b + cosine * d;
 		}
+		CONST_CAST(int, bone->appliedValid) = -1;
 	}
 }
 

+ 1 - 1
spine-c/src/spine/RegionAttachment.c

@@ -101,7 +101,7 @@ void spRegionAttachment_updateOffset (spRegionAttachment* self) {
 
 void spRegionAttachment_computeWorldVertices (spRegionAttachment* self, spBone* bone, float* vertices) {
 	const float* offset = self->offset;
-	float x = bone->skeleton->x + bone->worldX, y = bone->skeleton->y + bone->worldY;
+	float x = bone->worldX, y = bone->worldY;
 	vertices[SP_VERTEX_X1] = offset[SP_VERTEX_X1] * bone->a + offset[SP_VERTEX_Y1] * bone->b + x;
 	vertices[SP_VERTEX_Y1] = offset[SP_VERTEX_X1] * bone->c + offset[SP_VERTEX_Y1] * bone->d + y;
 	vertices[SP_VERTEX_X2] = offset[SP_VERTEX_X2] * bone->a + offset[SP_VERTEX_Y2] * bone->b + x;

+ 150 - 96
spine-c/src/spine/Skeleton.c

@@ -32,7 +32,6 @@
 #include <stdlib.h>
 #include <string.h>
 #include <spine/extension.h>
-#include <spine/Skin.h>
 
 typedef enum {
 	SP_UPDATE_BONE, SP_UPDATE_IK_CONSTRAINT, SP_UPDATE_PATH_CONSTRAINT, SP_UPDATE_TRANSFORM_CONSTRAINT
@@ -49,6 +48,10 @@ typedef struct {
 	int updateCacheCount;
 	int updateCacheCapacity;
 	_spUpdate* updateCache;
+
+	int updateCacheResetCount;
+	int updateCacheResetCapacity;
+	spBone** updateCacheReset;
 } _spSkeleton;
 
 spSkeleton* spSkeleton_create (spSkeletonData* data) {
@@ -65,15 +68,15 @@ spSkeleton* spSkeleton_create (spSkeletonData* data) {
 
 	for (i = 0; i < self->bonesCount; ++i) {
 		spBoneData* boneData = self->data->bones[i];
-		spBone* bone;
+		spBone* newBone;
 		if (!boneData->parent)
-			bone = spBone_create(boneData, self, 0);
+			newBone = spBone_create(boneData, self, 0);
 		else {
 			spBone* parent = self->bones[boneData->parent->index];
-			bone = spBone_create(boneData, self, parent);
+			newBone = spBone_create(boneData, self, parent);
 			++childrenCounts[boneData->parent->index];
 		}
-		self->bones[i] = bone;
+		self->bones[i] = newBone;
 	}
 	for (i = 0; i < self->bonesCount; ++i) {
 		spBoneData* boneData = self->data->bones[i];
@@ -101,7 +104,6 @@ spSkeleton* spSkeleton_create (spSkeletonData* data) {
 
 	self->ikConstraintsCount = data->ikConstraintsCount;
 	self->ikConstraints = MALLOC(spIkConstraint*, self->ikConstraintsCount);
-	self->ikConstraintsSorted = MALLOC(spIkConstraint*, self->ikConstraintsCount);
 	for (i = 0; i < self->data->ikConstraintsCount; ++i)
 		self->ikConstraints[i] = spIkConstraint_create(self->data->ikConstraints[i], self);
 
@@ -129,6 +131,7 @@ void spSkeleton_dispose (spSkeleton* self) {
 	_spSkeleton* internal = SUB_CAST(_spSkeleton, self);
 
 	FREE(internal->updateCache);
+	FREE(internal->updateCacheReset);
 
 	for (i = 0; i < self->bonesCount; ++i)
 		spBone_dispose(self->bones[i]);
@@ -141,7 +144,6 @@ void spSkeleton_dispose (spSkeleton* self) {
 	for (i = 0; i < self->ikConstraintsCount; ++i)
 		spIkConstraint_dispose(self->ikConstraints[i]);
 	FREE(self->ikConstraints);
-	FREE(self->ikConstraintsSorted);
 
 	for (i = 0; i < self->transformConstraintsCount; ++i)
 		spTransformConstraint_dispose(self->transformConstraints[i]);
@@ -167,6 +169,15 @@ static void _addToUpdateCache(_spSkeleton* const internal, _spUpdateType type, v
 	++internal->updateCacheCount;
 }
 
+static void _addToUpdateCacheReset(_spSkeleton* const internal, spBone* bone) {
+	if (internal->updateCacheResetCount == internal->updateCacheResetCapacity) {
+		internal->updateCacheResetCapacity *= 2;
+		internal->updateCacheReset = realloc(internal->updateCacheReset, sizeof(spBone*) * internal->updateCacheResetCapacity);
+	}
+	internal->updateCacheReset[internal->updateCacheResetCount] = bone;
+	++internal->updateCacheResetCount;
+}
+
 static void _sortBone(_spSkeleton* const internal, spBone* bone) {
 	if (bone->sorted) return;
 	if (bone->parent) _sortBone(internal, bone->parent);
@@ -185,10 +196,9 @@ static void _sortPathConstraintAttachmentBones(_spSkeleton* const internal, spAt
 		_sortBone(internal, slotBone);
 	else {
 		spBone** bones = internal->super.bones;
-		int i = 0;
+		int i = 0, n;
 		while (i < pathBonesCount) {
 			int boneCount = pathBones[i++];
-			int n;
 			for (n = i + boneCount; i < n; i++)
 				_sortBone(internal, bones[pathBones[i]]);
 		}
@@ -212,113 +222,145 @@ static void _sortReset(spBone** bones, int bonesCount) {
 	}
 }
 
+static void _sortIkConstraint (_spSkeleton* const internal, spIkConstraint* constraint) {
+	int /*bool*/ contains = 0;
+	int i;
+	spBone* target = constraint->target;
+	spBone** constrained;
+	spBone* parent;
+	_sortBone(internal, target);
+
+	constrained = constraint->bones;
+	parent = constrained[0];
+	_sortBone(internal, parent);
+
+	if (constraint->bonesCount > 1) {
+		spBone* child = constrained[constraint->bonesCount - 1];
+		contains = 0;
+		for (i = 0; i < internal->updateCacheCount; i++) {
+			_spUpdate update = internal->updateCache[i];
+			if (update.object == child) {
+				contains = -1;
+				break;
+			}
+		}
+		if (!contains)
+			_addToUpdateCacheReset(internal, child);
+	}
+
+	_addToUpdateCache(internal, SP_UPDATE_IK_CONSTRAINT, constraint);
+
+	_sortReset(parent->children, parent->childrenCount);
+	constrained[constraint->bonesCount-1]->sorted = 1;
+}
+
+static void _sortPathConstraint(_spSkeleton* const internal, spPathConstraint* constraint) {
+	spSlot* slot = constraint->target;
+	int slotIndex = slot->data->index;
+	spBone* slotBone = slot->bone;
+	int ii, nn, boneCount;
+	spAttachment* attachment;
+	spBone** constrained;
+	spSkeleton* skeleton = SUPER_CAST(spSkeleton, internal);
+	if (skeleton->skin) _sortPathConstraintAttachment(internal, skeleton->skin, slotIndex, slotBone);
+	if (skeleton->data->defaultSkin && skeleton->data->defaultSkin != skeleton->skin)
+		_sortPathConstraintAttachment(internal, skeleton->data->defaultSkin, slotIndex, slotBone);
+	for (ii = 0, nn = skeleton->data->skinsCount; ii < nn; ii++)
+		_sortPathConstraintAttachment(internal, skeleton->data->skins[ii], slotIndex, slotBone);
+
+	attachment = slot->attachment;
+	if (attachment->type == SP_ATTACHMENT_PATH) _sortPathConstraintAttachmentBones(internal, attachment, slotBone);
+
+	constrained = constraint->bones;
+	boneCount = constraint->bonesCount;
+	for (ii = 0; ii < boneCount; ii++)
+		_sortBone(internal, constrained[ii]);
+
+	_addToUpdateCache(internal, SP_UPDATE_PATH_CONSTRAINT, constraint);
+
+	for (ii = 0; ii < boneCount; ii++)
+		_sortReset(constrained[ii]->children, constrained[ii]->childrenCount);
+	for (ii = 0; ii < boneCount; ii++)
+		constrained[ii]->sorted = 1;
+}
+
+static void _sortTransformConstraint(_spSkeleton* const internal, spTransformConstraint* constraint) {
+	int ii, boneCount;
+	spBone** constrained;
+	_sortBone(internal, constraint->target);
+
+	constrained = constraint->bones;
+	boneCount = constraint->bonesCount;
+	for (ii = 0; ii < boneCount; ii++)
+		_sortBone(internal, constrained[ii]);
+
+	_addToUpdateCache(internal, SP_UPDATE_TRANSFORM_CONSTRAINT, constraint);
+
+	for (ii = 0; ii < boneCount; ii++)
+		_sortReset(constrained[ii]->children, constrained[ii]->childrenCount);
+	for (ii = 0; ii < boneCount; ii++)
+		constrained[ii]->sorted = 1;
+}
+
 void spSkeleton_updateCache (spSkeleton* self) {
-	int i, ii, n, nn, level;
+	int i, ii;
 	spBone** bones;
 	spIkConstraint** ikConstraints;
 	spPathConstraint** pathConstraints;
 	spTransformConstraint** transformConstraints;
+	int ikCount, transformCount, pathCount, constraintCount;
 	_spSkeleton* internal = SUB_CAST(_spSkeleton, self);
-	internal->updateCacheCapacity = self->bonesCount + self->ikConstraintsCount + self->transformConstraintsCount + self->pathConstraintsCount;
 
+	internal->updateCacheCapacity = self->bonesCount + self->ikConstraintsCount + self->transformConstraintsCount + self->pathConstraintsCount;
 	FREE(internal->updateCache);
 	internal->updateCache = MALLOC(_spUpdate, internal->updateCacheCapacity);
 	internal->updateCacheCount = 0;
 
+	internal->updateCacheResetCapacity = self->bonesCount;
+	FREE(internal->updateCacheReset);
+	internal->updateCacheReset = MALLOC(spBone*, internal->updateCacheResetCapacity);
+	internal->updateCacheResetCount = 0;
+
 	bones = self->bones;
 	for (i = 0; i < self->bonesCount; ++i)
 		bones[i]->sorted = 0;
 
 	/* IK first, lowest hierarchy depth first. */
-	if (self->ikConstraintsSorted) FREE(self->ikConstraintsSorted);
-	self->ikConstraintsSorted = MALLOC(spIkConstraint*, self->ikConstraintsCount);
-	ikConstraints = self->ikConstraintsSorted;
-	for (i = 0; i < self->ikConstraintsCount; ++i)
-		ikConstraints[i] = self->ikConstraints[i];
-	for (i = 0; i < self->ikConstraintsCount; ++i) {
-		spIkConstraint* ik = ikConstraints[i];
-		spBone* bone = ik->bones[0]->parent;
-		for (level = 0; bone; ++level)
-			bone = bone->parent;
-		ik->level = level;
-	}
-	for (i = 1; i < self->ikConstraintsCount; ++i) {
-		spIkConstraint* ik = ikConstraints[i];
-		level = ik->level;
-		for (ii = i - 1; ii >= 0; --ii) {
-			spIkConstraint* other = ikConstraints[ii];
-			if (other->level < level) break;
-			ikConstraints[ii + 1] = other;
-		}
-		ikConstraints[ii + 1] = ik;
-	}
-	for (i = 0; i < self->ikConstraintsCount; ++i) {
-		spBone** constrained;
-		spBone* parent;
-		spIkConstraint* constraint = ikConstraints[i];
-		spBone* target = constraint->target;
-		_sortBone(internal, target);
-
-		constrained = constraint->bones;
-		parent = constrained[0];
-		_sortBone(internal, parent);
-
-		_addToUpdateCache(internal, SP_UPDATE_IK_CONSTRAINT, constraint);
-
-		_sortReset(parent->children, parent->childrenCount);
-		constrained[constraint->bonesCount - 1]->sorted = 1;
-	}
-
-	pathConstraints = self->pathConstraints;
-	for (i = 0, n = self->pathConstraintsCount; i < n; i++) {
-		spAttachment* attachment;
-		spBone** constrained;
-		int boneCount;
-		spPathConstraint* constraint = pathConstraints[i];
-
-		spSlot* slot = constraint->target;
-		int slotIndex = slot->data->index;
-		spBone* slotBone = slot->bone;
-		if (self->skin) _sortPathConstraintAttachment(internal, self->skin, slotIndex, slotBone);
-		if (self->data->defaultSkin && self->data->defaultSkin != self->skin)
-			_sortPathConstraintAttachment(internal, self->data->defaultSkin, slotIndex, slotBone);
-		for (ii = 0, nn = self->data->skinsCount; ii < nn; ii++)
-			_sortPathConstraintAttachment(internal, self->data->skins[ii], slotIndex, slotBone);
-
-		attachment = slot->attachment;
-		if (attachment->type == SP_ATTACHMENT_PATH) _sortPathConstraintAttachmentBones(internal, attachment, slotBone);
-
-		constrained = constraint->bones;
-		boneCount = constraint->bonesCount;
-		for (ii = 0; ii < boneCount; ii++)
-			_sortBone(internal, constrained[ii]);
-
-		_addToUpdateCache(internal, SP_UPDATE_PATH_CONSTRAINT, constraint);
-
-		for (ii = 0; ii < boneCount; ii++)
-			_sortReset(constrained[ii]->children, constrained[ii]->childrenCount);
-		for (ii = 0; ii < boneCount; ii++)
-			constrained[ii]->sorted = 1;
-	}
-
+	ikConstraints = self->ikConstraints;
 	transformConstraints = self->transformConstraints;
-	for (i = 0, n = self->transformConstraintsCount; i < n; ++i) {
-		spTransformConstraint* constraint = transformConstraints[i];
-		spBone** constrained = constraint->bones;
-
-		_sortBone(internal, constraint->target);
-
-		for (ii = 0; ii < constraint->bonesCount; ++ii)
-			_sortBone(internal, constrained[ii]);
+	pathConstraints = self->pathConstraints;
+	ikCount = self->ikConstraintsCount; transformCount = self->transformConstraintsCount; pathCount = self->pathConstraintsCount;
+	constraintCount = ikCount + transformCount + pathCount;
+
+	i = 0;
+	outer:
+	for (; i < constraintCount; i++) {
+		for (ii = 0; ii < ikCount; ii++) {
+			spIkConstraint* ikConstraint = ikConstraints[ii];
+			if (ikConstraint->data->order == i) {
+				_sortIkConstraint(internal, ikConstraint);
+				i++;
+				goto outer;
+			}
+		}
 
-		_addToUpdateCache(internal, SP_UPDATE_TRANSFORM_CONSTRAINT, constraint);
+		for (ii = 0; ii < transformCount; ii++) {
+			spTransformConstraint* transformConstraint = transformConstraints[ii];
+			if (transformConstraint->data->order == i) {
+				_sortTransformConstraint(internal, transformConstraint);
+				i++;
+				goto outer;
+			}
+		}
 
-		for (ii = 0; ii < constraint->bonesCount; ++ii) {
-			spBone* bone = constrained[ii];
-			_sortReset(bone->children, bone->childrenCount);
+		for (ii = 0; ii < pathCount; ii++) {
+			spPathConstraint* pathConstraint = pathConstraints[ii];
+			if (pathConstraint->data->order == i) {
+				_sortPathConstraint(internal, pathConstraint);
+				i++;
+				goto outer;
+			}
 		}
-		for (ii = 0; ii < constraint->bonesCount; ++ii)
-			constrained[ii]->sorted = 1;
 	}
 
 	for (i = 0; i < self->bonesCount; ++i)
@@ -328,6 +370,18 @@ void spSkeleton_updateCache (spSkeleton* self) {
 void spSkeleton_updateWorldTransform (const spSkeleton* self) {
 	int i;
 	_spSkeleton* internal = SUB_CAST(_spSkeleton, self);
+	spBone** updateCacheReset = internal->updateCacheReset;
+	for (i = 0; i < internal->updateCacheResetCount; i++) {
+		spBone* bone = updateCacheReset[i];
+		CONST_CAST(float, bone->ax) = bone->x;
+		CONST_CAST(float, bone->ay) = bone->y;
+		CONST_CAST(float, bone->arotation) = bone->rotation;
+		CONST_CAST(float, bone->ascaleX) = bone->scaleX;
+		CONST_CAST(float, bone->ascaleY) = bone->scaleY;
+		CONST_CAST(float, bone->ashearX) = bone->shearX;
+		CONST_CAST(float, bone->ashearY) = bone->shearY;
+		CONST_CAST(int, bone->appliedValid) = 1;
+	}
 
 	for (i = 0; i < internal->updateCacheCount; ++i) {
 		_spUpdate* update = internal->updateCache + i;

+ 21 - 7
spine-c/src/spine/SkeletonBinary.c

@@ -97,8 +97,8 @@ static unsigned char readByte (_dataInput* input) {
 	return *input->cursor++;
 }
 
-static char readSByte (_dataInput* input) {
-	return (char)readByte(input);
+static signed char readSByte (_dataInput* input) {
+	return (signed char)readByte(input);
 }
 
 static int readBoolean (_dataInput* input) {
@@ -788,8 +788,8 @@ spSkin* spSkeletonBinary_readSkin(spSkeletonBinary* self, _dataInput* input,
 		int slotIndex = readVarint(input, 1);
 		for (ii = 0, nn = readVarint(input, 1); ii < nn; ++ii) {
 			const char* name = readString(input);
-			spSkin_addAttachment(skin, slotIndex, name,
-					spSkeletonBinary_readAttachment(self, input, skin, slotIndex, name, nonessential));
+			spAttachment* attachment = spSkeletonBinary_readAttachment(self, input, skin, slotIndex, name, nonessential);
+			if (attachment) spSkin_addAttachment(skin, slotIndex, name, attachment);
 			FREE(name);
 		}
 	}
@@ -842,13 +842,18 @@ spSkeletonData* spSkeletonBinary_readSkeletonData (spSkeletonBinary* self, const
 
 	nonessential = readBoolean(input);
 
-	if (nonessential) FREE(readString(input)); /* Skip images path. */
+	if (nonessential) {
+		/* Skip images path & fps */
+		readFloat(input);
+		FREE(readString(input));
+	}
 
 	/* Bones. */
 	skeletonData->bonesCount = readVarint(input, 1);
 	skeletonData->bones = MALLOC(spBoneData*, skeletonData->bonesCount);
 	for (i = 0; i < skeletonData->bonesCount; ++i) {
 		spBoneData* data;
+		int mode;
 		const char* name = readString(input);
 		spBoneData* parent = i == 0 ? 0 : skeletonData->bones[readVarint(input, 1)];
 		/* TODO Avoid copying of name */
@@ -862,8 +867,14 @@ spSkeletonData* spSkeletonBinary_readSkeletonData (spSkeletonBinary* self, const
 		data->shearX = readFloat(input);
 		data->shearY = readFloat(input);
 		data->length = readFloat(input) * self->scale;
-		data->inheritRotation = readBoolean(input);
-		data->inheritScale = readBoolean(input);
+		mode = readVarint(input, 1);
+		switch (mode) {
+			case 0: data->transformMode = SP_TRANSFORMMODE_NORMAL; break;
+			case 1: data->transformMode = SP_TRANSFORMMODE_ONLYTRANSLATION; break;
+			case 2: data->transformMode = SP_TRANSFORMMODE_NOROTATIONORREFLECTION; break;
+			case 3: data->transformMode = SP_TRANSFORMMODE_NOSCALE; break;
+			case 4: data->transformMode = SP_TRANSFORMMODE_NOSCALEORREFLECTION; break;
+		}
 		if (nonessential) readInt(input); /* Skip bone color. */
 		skeletonData->bones[i] = data;
 	}
@@ -890,6 +901,7 @@ spSkeletonData* spSkeletonBinary_readSkeletonData (spSkeletonBinary* self, const
 		const char* name = readString(input);
 		/* TODO Avoid copying of name */
 		spIkConstraintData* data = spIkConstraintData_create(name);
+		data->order = readVarint(input, 1);
 		FREE(name);
 		data->bonesCount = readVarint(input, 1);
 		data->bones = MALLOC(spBoneData*, data->bonesCount);
@@ -909,6 +921,7 @@ spSkeletonData* spSkeletonBinary_readSkeletonData (spSkeletonBinary* self, const
 		const char* name = readString(input);
 		/* TODO Avoid copying of name */
 		spTransformConstraintData* data = spTransformConstraintData_create(name);
+		data->order = readVarint(input, 1);
 		FREE(name);
 		data->bonesCount = readVarint(input, 1);
 		CONST_CAST(spBoneData**, data->bones) = MALLOC(spBoneData*, data->bonesCount);
@@ -935,6 +948,7 @@ spSkeletonData* spSkeletonBinary_readSkeletonData (spSkeletonBinary* self, const
 		const char* name = readString(input);
 		/* TODO Avoid copying of name */
 		spPathConstraintData* data = spPathConstraintData_create(name);
+		data->order = readVarint(input, 1);
 		FREE(name);
 		data->bonesCount = readVarint(input, 1);
 		CONST_CAST(spBoneData**, data->bones) = MALLOC(spBoneData*, data->bonesCount);

+ 16 - 2
spine-c/src/spine/SkeletonJson.c

@@ -604,6 +604,7 @@ spSkeletonData* spSkeletonJson_readSkeletonData (spSkeletonJson* self, const cha
 	skeletonData->bones = MALLOC(spBoneData*, bones->size);
 	for (boneMap = bones->child, i = 0; boneMap; boneMap = boneMap->next, ++i) {
 		spBoneData* data;
+		const char* transformMode;
 
 		spBoneData* parent = 0;
 		const char* parentName = Json_getString(boneMap, "parent", 0);
@@ -625,8 +626,18 @@ spSkeletonData* spSkeletonJson_readSkeletonData (spSkeletonJson* self, const cha
 		data->scaleY = Json_getFloat(boneMap, "scaleY", 1);
 		data->shearX = Json_getFloat(boneMap, "shearX", 0);
 		data->shearY = Json_getFloat(boneMap, "shearY", 0);
-		data->inheritRotation = Json_getInt(boneMap, "inheritRotation", 1);
-		data->inheritScale = Json_getInt(boneMap, "inheritScale", 1);
+		transformMode = Json_getString(boneMap, "transform", "normal");
+		data->transformMode = SP_TRANSFORMMODE_NORMAL;
+		if (strcmp(transformMode, "normal") == 0)
+			data->transformMode = SP_TRANSFORMMODE_NORMAL;
+		if (strcmp(transformMode, "onlyTranslation") == 0)
+			data->transformMode = SP_TRANSFORMMODE_ONLYTRANSLATION;
+		if (strcmp(transformMode, "noRotationOrReflection") == 0)
+			data->transformMode = SP_TRANSFORMMODE_NOROTATIONORREFLECTION;
+		if (strcmp(transformMode, "noScale") == 0)
+			data->transformMode = SP_TRANSFORMMODE_NOSCALE;
+		if (strcmp(transformMode, "noScaleOrReflection") == 0)
+			data->transformMode = SP_TRANSFORMMODE_NOSCALEORREFLECTION;
 
 		skeletonData->bones[i] = data;
 		skeletonData->bonesCount++;
@@ -688,6 +699,7 @@ spSkeletonData* spSkeletonJson_readSkeletonData (spSkeletonJson* self, const cha
 			const char* targetName;
 
 			spIkConstraintData* data = spIkConstraintData_create(Json_getString(constraintMap, "name", 0));
+			data->order = Json_getInt(constraintMap, "order", 0);
 
 			boneMap = Json_getItem(constraintMap, "bones");
 			data->bonesCount = boneMap->size;
@@ -726,6 +738,7 @@ spSkeletonData* spSkeletonJson_readSkeletonData (spSkeletonJson* self, const cha
 			const char* name;
 
 			spTransformConstraintData* data = spTransformConstraintData_create(Json_getString(constraintMap, "name", 0));
+			data->order = Json_getInt(constraintMap, "order", 0);
 
 			boneMap = Json_getItem(constraintMap, "bones");
 			data->bonesCount = boneMap->size;
@@ -774,6 +787,7 @@ spSkeletonData* spSkeletonJson_readSkeletonData (spSkeletonJson* self, const cha
 			const char* item;
 
 			spPathConstraintData* data = spPathConstraintData_create(Json_getString(constraintMap, "name", 0));
+			data->order = Json_getInt(constraintMap, "order", 0);
 
 			boneMap = Json_getItem(constraintMap, "bones");
 			data->bonesCount = boneMap->size;

+ 18 - 8
spine-c/src/spine/TransformConstraint.c

@@ -57,13 +57,17 @@ void spTransformConstraint_apply (spTransformConstraint* self) {
 	float rotateMix = self->rotateMix, translateMix = self->translateMix, scaleMix = self->scaleMix, shearMix = self->shearMix;
 	spBone* target = self->target;
 	float ta = target->a, tb = target->b, tc = target->c, td = target->d;
+	float degRadReflect = ta * td - tb * tc > 0 ? DEG_RAD : -DEG_RAD;
+	float offsetRotation = self->data->offsetRotation * degRadReflect, offsetShearY = self->data->offsetShearY * degRadReflect;
+	int /*bool*/ modified;
 	int i;
 	for (i = 0; i < self->bonesCount; ++i) {
 		spBone* bone = self->bones[i];
+		modified = 0;
 
-		if (rotateMix > 0) {
+		if (rotateMix != 0) {
 			float a = bone->a, b = bone->b, c = bone->c, d = bone->d;
-			float r = ATAN2(tc, ta) - ATAN2(c, a) + self->data->offsetRotation * DEG_RAD;
+			float r = ATAN2(tc, ta) - ATAN2(c, a) + offsetRotation;
 			float cosine, sine;
 			if (r > PI) r -= PI2;
 			else if (r < -PI) r += PI2;
@@ -74,26 +78,29 @@ void spTransformConstraint_apply (spTransformConstraint* self) {
 			CONST_CAST(float, bone->b) = cosine * b - sine * d;
 			CONST_CAST(float, bone->c) = sine * a + cosine * c;
 			CONST_CAST(float, bone->d) = sine * b + cosine * d;
+			modified = 1;
 		}
 
-		if (translateMix > 0) {
+		if (translateMix != 0) {
 			float x, y;
 			spBone_localToWorld(target, self->data->offsetX, self->data->offsetY, &x, &y);
 			CONST_CAST(float, bone->worldX) += (x - bone->worldX) * translateMix;
 			CONST_CAST(float, bone->worldY) += (y - bone->worldY) * translateMix;
+			modified = 1;
 		}
 
 		if (scaleMix > 0) {
-			float bs = SQRT(bone->a * bone->a + bone->c * bone->c);
+			float s = SQRT(bone->a * bone->a + bone->c * bone->c);
 			float ts = SQRT(ta * ta + tc * tc);
-			float s = bs > 0.00001f ? (bs + (ts - bs + self->data->offsetScaleX) * scaleMix) / bs : 0;
+			if (s > 0.00001f) s = (s + (ts - s + self->data->offsetScaleX) * scaleMix) / s;
 			CONST_CAST(float, bone->a) *= s;
 			CONST_CAST(float, bone->c) *= s;
-			bs = SQRT(bone->b * bone->b + bone->d * bone->d);
+			s = SQRT(bone->b * bone->b + bone->d * bone->d);
 			ts = SQRT(tb * tb + td * td);
-			s = bs > 0.00001f ? (bs + (ts - bs + self->data->offsetScaleY) * scaleMix) / bs : 0;
+			if (s > 0.00001f) s = (s + (ts - s + self->data->offsetScaleY) * scaleMix) / s;
 			CONST_CAST(float, bone->b) *= s;
 			CONST_CAST(float, bone->d) *= s;
+			modified = 1;
 		}
 
 		if (shearMix > 0) {
@@ -103,9 +110,12 @@ void spTransformConstraint_apply (spTransformConstraint* self) {
 			float s = SQRT(b * b + d * d);
 			if (r > PI) r -= PI2;
 			else if (r < -PI) r += PI2;
-			r = by + (r + self->data->offsetShearY * DEG_RAD) * shearMix;
+			r = by + (r + offsetShearY) * shearMix;
 			CONST_CAST(float, bone->b) = COS(r) * s;
 			CONST_CAST(float, bone->d) = SIN(r) * s;
+			modified = 1;
 		}
+
+		if (modified) CONST_CAST(int, bone->appliedValid) = 0;
 	}
 }

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.