Browse Source

Initial Corona runtime.

NathanSweet 12 years ago
commit
e2120ca07b
43 changed files with 1639 additions and 0 deletions
  1. 2 0
      .gitignore
  2. 22 0
      LICENSE
  3. 12 0
      spine-corona/build.settings
  4. 8 0
      spine-corona/config.lua
  5. BIN
      spine-corona/data/eyes-closed.png
  6. BIN
      spine-corona/data/eyes.png
  7. BIN
      spine-corona/data/head.png
  8. BIN
      spine-corona/data/left-ankle.png
  9. BIN
      spine-corona/data/left-arm.png
  10. BIN
      spine-corona/data/left-foot.png
  11. BIN
      spine-corona/data/left-hand.png
  12. BIN
      spine-corona/data/left-lower-leg.png
  13. BIN
      spine-corona/data/left-pant-bottom.png
  14. BIN
      spine-corona/data/left-shoulder.png
  15. BIN
      spine-corona/data/left-upper-leg.png
  16. BIN
      spine-corona/data/neck.png
  17. BIN
      spine-corona/data/pelvis.png
  18. BIN
      spine-corona/data/right-ankle.png
  19. BIN
      spine-corona/data/right-arm.png
  20. BIN
      spine-corona/data/right-foot-idle.png
  21. BIN
      spine-corona/data/right-foot.png
  22. BIN
      spine-corona/data/right-hand.png
  23. BIN
      spine-corona/data/right-lower-leg.png
  24. BIN
      spine-corona/data/right-pant-bottom.png
  25. BIN
      spine-corona/data/right-shoulder.png
  26. BIN
      spine-corona/data/right-upper-leg.png
  27. 101 0
      spine-corona/data/spineboy-skeleton.json
  28. 278 0
      spine-corona/data/spineboy-walk.json
  29. BIN
      spine-corona/data/torso.png
  30. 36 0
      spine-corona/main.lua
  31. 397 0
      spine-corona/spine/Animation.lua
  32. 30 0
      spine-corona/spine/AttachmentResolver.lua
  33. 54 0
      spine-corona/spine/Bone.lua
  34. 13 0
      spine-corona/spine/BoneData.lua
  35. 12 0
      spine-corona/spine/RegionAttachment.lua
  36. 172 0
      spine-corona/spine/Skeleton.lua
  37. 55 0
      spine-corona/spine/SkeletonData.lua
  38. 255 0
      spine-corona/spine/SkeletonJson.lua
  39. 39 0
      spine-corona/spine/Skin.lua
  40. 54 0
      spine-corona/spine/Slot.lua
  41. 23 0
      spine-corona/spine/SlotData.lua
  42. 17 0
      spine-corona/spine/spine.lua
  43. 59 0
      spine-corona/spine/utils.lua

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+spine-cpp/Debug/*
+spine-libgdx/bin/*

+ 22 - 0
LICENSE

@@ -0,0 +1,22 @@
+Copyright (c) 2013, Esoteric Software
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 12 - 0
spine-corona/build.settings

@@ -0,0 +1,12 @@
+settings = {
+	orientation = {
+		default = "portrait",
+		supported = { "portrait", }
+	},	
+	iphone = {
+		plist = {
+			UIStatusBarHidden = false,
+			UIPrerenderedIcon = true, -- set to false for "shine" overlay
+		}
+	},
+}

+ 8 - 0
spine-corona/config.lua

@@ -0,0 +1,8 @@
+application = {
+	content = {
+		width = 320,
+		height = 480, 
+		scale = "letterBox",
+		fps = 60,
+	},
+}

BIN
spine-corona/data/eyes-closed.png


BIN
spine-corona/data/eyes.png


BIN
spine-corona/data/head.png


BIN
spine-corona/data/left-ankle.png


BIN
spine-corona/data/left-arm.png


BIN
spine-corona/data/left-foot.png


BIN
spine-corona/data/left-hand.png


BIN
spine-corona/data/left-lower-leg.png


BIN
spine-corona/data/left-pant-bottom.png


BIN
spine-corona/data/left-shoulder.png


BIN
spine-corona/data/left-upper-leg.png


BIN
spine-corona/data/neck.png


BIN
spine-corona/data/pelvis.png


BIN
spine-corona/data/right-ankle.png


BIN
spine-corona/data/right-arm.png


BIN
spine-corona/data/right-foot-idle.png


BIN
spine-corona/data/right-foot.png


BIN
spine-corona/data/right-hand.png


BIN
spine-corona/data/right-lower-leg.png


BIN
spine-corona/data/right-pant-bottom.png


BIN
spine-corona/data/right-shoulder.png


BIN
spine-corona/data/right-upper-leg.png


+ 101 - 0
spine-corona/data/spineboy-skeleton.json

@@ -0,0 +1,101 @@
+{
+"bones": [
+	{ "name": "root", "length": 0 },
+	{ "name": "hip", "parent": "root", "length": 0, "x": 0.64, "y": 114.41 },
+	{ "name": "left upper leg", "parent": "hip", "length": 50.39, "x": 14.45, "y": 2.81, "rotation": -89.09 },
+	{ "name": "left lower leg", "parent": "left upper leg", "length": 56.45, "x": 51.78, "y": 3.46, "rotation": -16.65 },
+	{ "name": "left foot", "parent": "left lower leg", "length": 46.5, "x": 64.02, "y": -8.67, "rotation": 102.43 },
+	{ "name": "right upper leg", "parent": "hip", "length": 45.76, "x": -18.27, "rotation": -101.13 },
+	{ "name": "right lower leg", "parent": "right upper leg", "length": 58.52, "x": 50.21, "y": 0.6, "rotation": -10.7 },
+	{ "name": "right foot", "parent": "right lower leg", "length": 45.45, "x": 64.88, "y": 0.04, "rotation": 110.3 },
+	{ "name": "torso", "parent": "hip", "length": 85.82, "x": -6.42, "y": 1.97, "rotation": 94.95 },
+	{ "name": "neck", "parent": "torso", "length": 18.38, "x": 83.64, "y": -1.78, "rotation": 0.9 },
+	{ "name": "head", "parent": "neck", "length": 68.28, "x": 19.09, "y": 6.97, "rotation": -8.94 },
+	{ "name": "right shoulder", "parent": "torso", "length": 49.95, "x": 81.9, "y": 6.79, "rotation": 130.6 },
+	{ "name": "right arm", "parent": "right shoulder", "length": 36.74, "x": 49.95, "y": -0.12, "rotation": 40.12 },
+	{ "name": "right hand", "parent": "right arm", "length": 15.32, "x": 36.9, "y": 0.34, "rotation": 2.35 },
+	{ "name": "left shoulder", "parent": "torso", "length": 44.19, "x": 78.96, "y": -15.75, "rotation": -156.96 },
+	{ "name": "left arm", "parent": "left shoulder", "length": 35.62, "x": 44.19, "y": -0.01, "rotation": 28.16 },
+	{ "name": "left hand", "parent": "left arm", "length": 11.52, "x": 35.62, "y": 0.07, "rotation": 2.7 },
+	{ "name": "pelvis", "parent": "hip", "length": 0, "x": 1.41, "y": -6.57 }
+],
+"slots": [
+	{ "name": "template", "bone": "root", "color": "ff898c86" },
+	{ "name": "left shoulder", "bone": "left shoulder", "attachment": "left-shoulder" },
+	{ "name": "left arm", "bone": "left arm", "attachment": "left-arm" },
+	{ "name": "left hand", "bone": "left hand", "attachment": "left-hand" },
+	{ "name": "left foot", "bone": "left foot", "attachment": "left-foot" },
+	{ "name": "left lower leg", "bone": "left lower leg", "attachment": "left-lower-leg" },
+	{ "name": "left upper leg", "bone": "left upper leg", "attachment": "left-upper-leg" },
+	{ "name": "pelvis", "bone": "pelvis", "attachment": "pelvis" },
+	{ "name": "right foot", "bone": "right foot", "attachment": "right-foot" },
+	{ "name": "right lower leg", "bone": "right lower leg", "attachment": "right-lower-leg" },
+	{ "name": "right upper leg", "bone": "right upper leg", "attachment": "right-upper-leg" },
+	{ "name": "torso", "bone": "torso", "attachment": "torso" },
+	{ "name": "neck", "bone": "neck", "attachment": "neck" },
+	{ "name": "head", "bone": "head", "attachment": "head" },
+	{ "name": "eyes", "bone": "head", "attachment": "eyes" },
+	{ "name": "right shoulder", "bone": "right shoulder", "attachment": "right-shoulder" },
+	{ "name": "right arm", "bone": "right arm", "attachment": "right-arm" },
+	{ "name": "right hand", "bone": "right hand", "attachment": "right-hand" }
+],
+"skins": {
+	"default": {
+		"template": {
+			"spineboy": { "y": 167.82, "width": 145, "height": 341 }
+		},
+		"left shoulder": {
+			"left-shoulder": { "x": 23.74, "y": 0.11, "rotation": 62.01, "width": 34, "height": 53 }
+		},
+		"left arm": {
+			"left-arm": { "x": 15.11, "y": -0.44, "rotation": 33.84, "width": 35, "height": 29 }
+		},
+		"left hand": {
+			"left-hand": { "x": 0.75, "y": 1.86, "rotation": 31.14, "width": 35, "height": 38 }
+		},
+		"left foot": {
+			"left-foot": { "x": 24.35, "y": 8.88, "rotation": 3.32, "width": 65, "height": 30 }
+		},
+		"left lower leg": {
+			"left-lower-leg": { "x": 24.55, "y": -1.92, "rotation": 105.75, "width": 49, "height": 64 }
+		},
+		"left upper leg": {
+			"left-upper-leg": { "x": 26.12, "y": -1.85, "rotation": 89.09, "width": 33, "height": 67 }
+		},
+		"pelvis": {
+			"pelvis": { "x": -4.83, "y": 10.62, "width": 63, "height": 47 }
+		},
+		"right foot": {
+			"right-foot": { "x": 19.02, "y": 8.47, "rotation": 1.52, "width": 67, "height": 30 }
+		},
+		"right lower leg": {
+			"right-lower-leg": { "x": 23.28, "y": -2.59, "rotation": 111.83, "width": 51, "height": 64 }
+		},
+		"right upper leg": {
+			"right-upper-leg": { "x": 23.03, "y": 0.25, "rotation": 101.13, "width": 44, "height": 70 }
+		},
+		"torso": {
+			"torso": { "x": 44.57, "y": -7.08, "rotation": -94.95, "width": 68, "height": 92 }
+		},
+		"neck": {
+			"neck": { "x": 9.42, "y": -3.66, "rotation": -100.15, "width": 34, "height": 28 }
+		},
+		"head": {
+			"head": { "x": 53.94, "y": -5.75, "rotation": -86.9, "width": 121, "height": 132 }
+		},
+		"eyes": {
+			"eyes": { "x": 28.94, "y": -32.92, "rotation": -86.9, "width": 34, "height": 27 },
+			"eyes-closed": { "x": 28.77, "y": -32.86, "rotation": -86.9, "width": 34, "height": 27 }
+		},
+		"right shoulder": {
+			"right-shoulder": { "x": 25.86, "y": 0.03, "rotation": 134.44, "width": 52, "height": 51 }
+		},
+		"right arm": {
+			"right-arm": { "x": 18.34, "y": -2.64, "rotation": 94.32, "width": 21, "height": 45 }
+		},
+		"right hand": {
+			"right-hand": { "x": 6.82, "y": 1.25, "rotation": 91.96, "width": 32, "height": 32 }
+		}
+	}
+}
+}

+ 278 - 0
spine-corona/data/spineboy-walk.json

@@ -0,0 +1,278 @@
+{
+"bones": {
+	"left upper leg": {
+		"rotate": [
+			{ "time": 0, "angle": -26.55 },
+			{ "time": 0.1333, "angle": -8.78 },
+			{ "time": 0.2666, "angle": 9.51 },
+			{ "time": 0.4, "angle": 30.74 },
+			{ "time": 0.5333, "angle": 25.33 },
+			{ "time": 0.6666, "angle": 26.11 },
+			{ "time": 0.8, "angle": -7.7 },
+			{ "time": 0.9333, "angle": -21.19 },
+			{ "time": 1.0666, "angle": -26.55 }
+		],
+		"translate": [
+			{ "time": 0, "x": -3, "y": -2.25 },
+			{ "time": 0.4, "x": -2.18, "y": -2.25 },
+			{ "time": 1.0666, "x": -3, "y": -2.25 }
+		]
+	},
+	"right upper leg": {
+		"rotate": [
+			{ "time": 0, "angle": 42.45 },
+			{ "time": 0.1333, "angle": 52.1 },
+			{ "time": 0.2666, "angle": 5.96 },
+			{ "time": 0.5333, "angle": -16.93 },
+			{ "time": 0.6666, "angle": 1.89 },
+			{
+				"time": 0.8,
+				"angle": 28.06,
+				"curve": [ 0.462, 0.11, 1, 1 ]
+			},
+			{
+				"time": 0.9333,
+				"angle": 58.68,
+				"curve": [ 0.5, 0.02, 1, 1 ]
+			},
+			{ "time": 1.0666, "angle": 42.45 }
+		],
+		"translate": [
+			{ "time": 0, "x": 8.11, "y": -2.36 },
+			{ "time": 0.1333, "x": 10.03, "y": -2.56 },
+			{ "time": 0.4, "x": 2.76, "y": -2.97 },
+			{ "time": 0.5333, "x": 2.76, "y": -2.81 },
+			{ "time": 0.9333, "x": 8.67, "y": -2.54 },
+			{ "time": 1.0666, "x": 8.11, "y": -2.36 }
+		]
+	},
+	"left lower leg": {
+		"rotate": [
+			{ "time": 0, "angle": -10.21 },
+			{ "time": 0.1333, "angle": -55.64 },
+			{ "time": 0.2666, "angle": -68.12 },
+			{ "time": 0.5333, "angle": 5.11 },
+			{ "time": 0.6666, "angle": -28.29 },
+			{ "time": 0.8, "angle": 4.08 },
+			{ "time": 0.9333, "angle": 3.53 },
+			{ "time": 1.0666, "angle": -10.21 }
+		]
+	},
+	"left foot": {
+		"rotate": [
+			{ "time": 0, "angle": -3.69 },
+			{ "time": 0.1333, "angle": -10.42 },
+			{ "time": 0.2666, "angle": -17.14 },
+			{ "time": 0.4, "angle": -2.83 },
+			{ "time": 0.5333, "angle": -3.87 },
+			{ "time": 0.6666, "angle": 2.78 },
+			{ "time": 0.8, "angle": 1.68 },
+			{ "time": 0.9333, "angle": -8.54 },
+			{ "time": 1.0666, "angle": -3.69 }
+		]
+	},
+	"right shoulder": {
+		"rotate": [
+			{
+				"time": 0,
+				"angle": 20.89,
+				"curve": [ 0.264, 0, 0.75, 1 ]
+			},
+			{
+				"time": 0.1333,
+				"angle": 3.72,
+				"curve": [ 0.272, 0, 0.841, 1 ]
+			},
+			{ "time": 0.6666, "angle": -278.28 },
+			{ "time": 1.0666, "angle": 20.89 }
+		],
+		"translate": [
+			{ "time": 0, "x": -7.84, "y": 7.19 },
+			{ "time": 0.1333, "x": -6.36, "y": 6.42 },
+			{ "time": 0.6666, "x": -11.07, "y": 5.25 },
+			{ "time": 1.0666, "x": -7.84, "y": 7.19 }
+		]
+	},
+	"right arm": {
+		"rotate": [
+			{
+				"time": 0,
+				"angle": -4.02,
+				"curve": [ 0.267, 0, 0.804, 0.99 ]
+			},
+			{
+				"time": 0.1333,
+				"angle": -13.99,
+				"curve": [ 0.341, 0, 1, 1 ]
+			},
+			{
+				"time": 0.6666,
+				"angle": 36.54,
+				"curve": [ 0.307, 0, 0.787, 0.99 ]
+			},
+			{ "time": 1.0666, "angle": -4.02 }
+		]
+	},
+	"right hand": {
+		"rotate": [
+			{ "time": 0, "angle": 22.92 },
+			{ "time": 0.4, "angle": -8.97 },
+			{ "time": 0.6666, "angle": 0.51 },
+			{ "time": 1.0666, "angle": 22.92 }
+		]
+	},
+	"left shoulder": {
+		"rotate": [
+			{ "time": 0, "angle": -1.47 },
+			{ "time": 0.1333, "angle": 13.6 },
+			{ "time": 0.6666, "angle": 280.74 },
+			{ "time": 1.0666, "angle": -1.47 }
+		],
+		"translate": [
+			{ "time": 0, "x": -1.76, "y": 0.56 },
+			{ "time": 0.6666, "x": -2.47, "y": 8.14 },
+			{ "time": 1.0666, "x": -1.76, "y": 0.56 }
+		]
+	},
+	"left hand": {
+		"rotate": [
+			{
+				"time": 0,
+				"angle": 11.58,
+				"curve": [ 0.169, 0.37, 0.632, 1.55 ]
+			},
+			{
+				"time": 0.1333,
+				"angle": 28.13,
+				"curve": [ 0.692, 0, 0.692, 0.99 ]
+			},
+			{
+				"time": 0.6666,
+				"angle": -27.42,
+				"curve": [ 0.117, 0.41, 0.738, 1.76 ]
+			},
+			{ "time": 0.8, "angle": -36.32 },
+			{ "time": 1.0666, "angle": 11.58 }
+		]
+	},
+	"left arm": {
+		"rotate": [
+			{ "time": 0, "angle": -8.27 },
+			{ "time": 0.1333, "angle": 18.43 },
+			{ "time": 0.6666, "angle": 0.88 },
+			{ "time": 1.0666, "angle": -8.27 }
+		]
+	},
+	"torso": {
+		"rotate": [
+			{ "time": 0, "angle": -10.28 },
+			{
+				"time": 0.1333,
+				"angle": -15.38,
+				"curve": [ 0.545, 0, 1, 1 ]
+			},
+			{
+				"time": 0.4,
+				"angle": -9.78,
+				"curve": [ 0.58, 0.17, 1, 1 ]
+			},
+			{ "time": 0.6666, "angle": -15.75 },
+			{ "time": 0.9333, "angle": -7.06 },
+			{ "time": 1.0666, "angle": -10.28 }
+		],
+		"translate": [
+			{ "time": 0, "x": -3.67, "y": 1.68 },
+			{ "time": 0.1333, "x": -3.67, "y": 0.68 },
+			{ "time": 0.4, "x": -3.67, "y": 1.97 },
+			{ "time": 0.6666, "x": -3.67, "y": -0.14 },
+			{ "time": 1.0666, "x": -3.67, "y": 1.68 }
+		]
+	},
+	"right foot": {
+		"rotate": [
+			{ "time": 0, "angle": -5.25 },
+			{ "time": 0.2666, "angle": -4.08 },
+			{ "time": 0.4, "angle": -6.45 },
+			{ "time": 0.5333, "angle": -5.39 },
+			{ "time": 0.8, "angle": -11.68 },
+			{ "time": 0.9333, "angle": 0.46 },
+			{ "time": 1.0666, "angle": -5.25 }
+		]
+	},
+	"right lower leg": {
+		"rotate": [
+			{ "time": 0, "angle": -3.39 },
+			{ "time": 0.1333, "angle": -45.53 },
+			{ "time": 0.2666, "angle": -2.59 },
+			{ "time": 0.5333, "angle": -19.53 },
+			{ "time": 0.6666, "angle": -64.8 },
+			{
+				"time": 0.8,
+				"angle": -82.56,
+				"curve": [ 0.557, 0.18, 1, 1 ]
+			},
+			{ "time": 1.0666, "angle": -3.39 }
+		]
+	},
+	"hip": {
+		"rotate": [
+			{ "time": 0, "angle": 0, "curve": "stepped" },
+			{ "time": 1.0666, "angle": 0 }
+		],
+		"translate": [
+			{ "time": 0, "x": 0, "y": 0 },
+			{
+				"time": 0.1333,
+				"x": 0,
+				"y": -7.61,
+				"curve": [ 0.272, 0.86, 1, 1 ]
+			},
+			{ "time": 0.4, "x": 0, "y": 8.7 },
+			{ "time": 0.5333, "x": 0, "y": -0.41 },
+			{
+				"time": 0.6666,
+				"x": 0,
+				"y": -7.05,
+				"curve": [ 0.235, 0.89, 1, 1 ]
+			},
+			{ "time": 0.8, "x": 0, "y": 2.92 },
+			{ "time": 0.9333, "x": 0, "y": 6.78 },
+			{ "time": 1.0666, "x": 0, "y": 0 }
+		]
+	},
+	"neck": {
+		"rotate": [
+			{ "time": 0, "angle": 3.6 },
+			{ "time": 0.1333, "angle": 17.49 },
+			{ "time": 0.2666, "angle": 6.1 },
+			{ "time": 0.4, "angle": 3.45 },
+			{ "time": 0.5333, "angle": 5.17 },
+			{ "time": 0.6666, "angle": 18.36 },
+			{ "time": 0.8, "angle": 6.09 },
+			{ "time": 0.9333, "angle": 2.28 },
+			{ "time": 1.0666, "angle": 3.6 }
+		]
+	},
+	"head": {
+		"rotate": [
+			{
+				"time": 0,
+				"angle": 3.6,
+				"curve": [ 0, 0, 0.704, 1.61 ]
+			},
+			{ "time": 0.1666, "angle": -0.2 },
+			{ "time": 0.2666, "angle": 6.1 },
+			{ "time": 0.4, "angle": 3.45 },
+			{
+				"time": 0.5333,
+				"angle": 5.17,
+				"curve": [ 0, 0, 0.704, 1.61 ]
+			},
+			{ "time": 0.7, "angle": 1.1 },
+			{ "time": 0.8, "angle": 6.09 },
+			{ "time": 0.9333, "angle": 2.28 },
+			{ "time": 1.0666, "angle": 3.6 }
+		]
+	}
+}
+}

BIN
spine-corona/data/torso.png


+ 36 - 0
spine-corona/main.lua

@@ -0,0 +1,36 @@
+
+local spine = require "spine.spine"
+
+-- Optional attachment resolver customizes where images are loaded. Eg, could use an image sheet.
+local attachmentResolver = spine.AttachmentResolver.new()
+function attachmentResolver:createImage (attachment)
+	return display.newImage("data/" .. attachment.name .. ".png")
+end
+
+local json = spine.SkeletonJson.new(attachmentResolver)
+json.scale = 1
+local skeletonData = json:readSkeletonDataFile("data/spineboy-skeleton.json")
+local walkAnimation = json:readAnimationFile(skeletonData, "data/spineboy-walk.json")
+
+-- Optional second parameter can be the group for the Skeleton to use. Eg, could be an image group.
+local skeleton = spine.Skeleton.new(skeletonData)
+skeleton.x = 150
+skeleton.y = 325
+skeleton.flipX = false
+skeleton.flipY = false
+skeleton.debug = true -- Omit or set to false to not draw debug lines on top of the images.
+skeleton:setToBindPose()
+
+local lastTime = 0
+local animationTime = 0
+Runtime:addEventListener("enterFrame", function (event)
+	-- Compute time in seconds since last frame.
+	local currentTime = event.time / 1000
+	local delta = currentTime - lastTime
+	lastTime = currentTime
+
+	-- Accumulate time and pose skeleton using animation.
+	animationTime = animationTime + delta
+	walkAnimation:apply(skeleton, animationTime, true)
+	skeleton:updateWorldTransform()
+end)

+ 397 - 0
spine-corona/spine/Animation.lua

@@ -0,0 +1,397 @@
+
+local utils = require "spine.utils"
+
+local Animation = {}
+function Animation.new (timelines, duration)
+	if not timelines then error("timelines cannot be nil", 2) end
+
+	local self = {
+		timelines = timelines,
+		duration = duration
+	}
+
+	function self:apply (skeleton, time, loop)
+		if not skeleton then error("skeleton cannot be nil.", 2) end
+
+		if loop and duration then time = time % duration end
+
+		for i,timeline in ipairs(self.timelines) do
+			timeline:apply(skeleton, time, 1)
+		end
+	end
+
+	function self:mix (skeleton, time, loop, alpha)
+		if not skeleton then error("skeleton cannot be nil.", 2) end
+
+		if loop and duration then time = time % duration end
+
+		for i,timeline in ipairs(self.timelines) do
+			timeline:apply(skeleton, time, alpha)
+		end
+	end
+
+	return self
+end
+
+local function binarySearch (values, target, step)
+	local low = 0
+	local high = math.floor((#values + 1) / step - 2)
+	if high == 0 then return step end
+	local current = math.floor(high / 2)
+	while true do
+		if values[(current + 1) * step] <= target then
+			low = current + 1
+		else
+			high = current
+		end
+		if low == high then return (low + 1) * step end
+		current = math.floor((low + high) / 2)
+	end
+end
+
+local function linearSearch (values, target, step)
+	for i = 0, #values, step do
+		if (values[i] > target) then return i end
+	end
+	return -1
+end
+
+Animation.CurveTimeline = {}
+function Animation.CurveTimeline.new ()
+	local LINEAR = 0
+	local STEPPED = -1
+	local BEZIER_SEGMENTS = 10
+
+	local self = {
+		curves = {}
+	}
+
+	function self:setLinear (keyframeIndex)
+		self.curves[keyframeIndex * 6] = LINEAR
+	end
+
+	function self:setStepped (keyframeIndex)
+		self.curves[keyframeIndex * 6] = STEPPED
+	end
+
+	function self:setCurve (keyframeIndex, cx1, cy1, cx2, cy2)
+		local subdiv_step = 1 / BEZIER_SEGMENTS
+		local subdiv_step2 = subdiv_step * subdiv_step
+		local subdiv_step3 = subdiv_step2 * subdiv_step
+		local pre1 = 3 * subdiv_step
+		local pre2 = 3 * subdiv_step2
+		local pre4 = 6 * subdiv_step2
+		local pre5 = 6 * subdiv_step3
+		local tmp1x = -cx1 * 2 + cx2
+		local tmp1y = -cy1 * 2 + cy2
+		local tmp2x = (cx1 - cx2) * 3 + 1
+		local tmp2y = (cy1 - cy2) * 3 + 1
+		local i = keyframeIndex * 6
+		local curves = self.curves
+		curves[i] = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv_step3
+		curves[i + 1] = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv_step3
+		curves[i + 2] = tmp1x * pre4 + tmp2x * pre5
+		curves[i + 3] = tmp1y * pre4 + tmp2y * pre5
+		curves[i + 4] = tmp2x * pre5
+		curves[i + 5] = tmp2y * pre5
+	end
+
+	function self:getCurvePercent (keyframeIndex, percent)
+		local curveIndex = keyframeIndex * 6
+		local curves = self.curves
+		local dfx = curves[curveIndex]
+		if not dfx then return percent end -- linear
+		if dfx == STEPPED then return 0 end
+		local dfy = curves[curveIndex + 1]
+		local ddfx = curves[curveIndex + 2]
+		local ddfy = curves[curveIndex + 3]
+		local dddfx = curves[curveIndex + 4]
+		local dddfy = curves[curveIndex + 5]
+		local x = dfx
+		local y = dfy
+		local i = BEZIER_SEGMENTS - 2
+		while true do
+			if x >= percent then
+				local lastX = x - dfx
+				local lastY = y - dfy
+				return lastY + (y - lastY) * (percent - lastX) / (x - lastX)
+			end
+			if i == 0 then break end
+			i = i - 1
+			dfx = dfx + ddfx
+			dfy = dfy + ddfy
+			ddfx = ddfx + dddfx
+			ddfy = ddfy + dddfy
+			x = x + dfx
+			y = y + dfy
+		end
+		return y + (1 - y) * (percent - x) / (1 - x) -- Last point is 1,1.
+	end
+
+	return self
+end
+
+Animation.RotateTimeline = {}
+function Animation.RotateTimeline.new ()
+	local LAST_FRAME_TIME = -2
+	local FRAME_VALUE = 1
+
+	local self = Animation.CurveTimeline.new()
+	self.frames = {}
+
+	function self:getDuration ()
+		return self.frames[#self.frames - 1]
+	end
+
+	function self:getKeyframeCount ()
+		return (#self.frames + 1) / 2
+	end
+
+	function self:setKeyframe (keyframeIndex, time, value)
+		keyframeIndex = keyframeIndex * 2
+		self.frames[keyframeIndex] = time
+		self.frames[keyframeIndex + 1] = value
+	end
+
+	function self:apply (skeleton, time, alpha)
+		local frames = self.frames
+		if time < frames[0] then return end -- Time is before first frame.
+
+		local bone = skeleton.bones[self.boneIndex]
+
+		if time >= frames[#frames - 1] then -- Time is after last frame.
+			local amount = bone.data.rotation + frames[#frames] - bone.rotation
+			while amount > 180 do
+				amount = amount - 360
+			end
+			while amount < -180 do
+				amount = amount + 360
+			end
+			bone.rotation = bone.rotation + amount * alpha
+			return
+		end
+
+		-- Interpolate between the last frame and the current frame.
+		local frameIndex = binarySearch(frames, time, 2)
+		local lastFrameValue = frames[frameIndex - 1]
+		local frameTime = frames[frameIndex]
+		local percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime)
+		if percent < 0 then percent = 0 elseif percent > 1 then percent = 1 end
+		percent = self:getCurvePercent(frameIndex / 2 - 1, percent)
+
+		local amount = frames[frameIndex + FRAME_VALUE] - lastFrameValue
+		while amount > 180 do
+			amount = amount - 360
+		end
+		while amount < -180 do
+			amount = amount + 360
+		end
+		amount = bone.data.rotation + (lastFrameValue + amount * percent) - bone.rotation
+		while amount > 180 do
+			amount = amount - 360
+		end
+		while amount < -180 do
+			amount = amount + 360
+		end
+		bone.rotation = bone.rotation + amount * alpha
+	end
+
+	return self
+end
+
+Animation.TranslateTimeline = {}
+function Animation.TranslateTimeline.new ()
+	local LAST_FRAME_TIME = -3
+	local FRAME_X = 1
+	local FRAME_Y = 2
+
+	local self = Animation.CurveTimeline.new()
+	self.frames = {}
+
+	function self:getDuration ()
+		return self.frames[#self.frames - 2]
+	end
+
+	function self:getKeyframeCount ()
+		return (#self.frames + 1) / 3
+	end
+
+	function self:setKeyframe (keyframeIndex, time, x, y)
+		keyframeIndex = keyframeIndex * 3
+		self.frames[keyframeIndex] = time
+		self.frames[keyframeIndex + 1] = x
+		self.frames[keyframeIndex + 2] = y
+	end
+
+	function self:apply (skeleton, time, alpha)
+		local frames = self.frames
+		if time < frames[0] then return end -- Time is before first frame.
+
+		local bone = skeleton.bones[self.boneIndex]
+		
+		if time >= frames[#frames - 2] then -- Time is after last frame.
+			bone.x = bone.x + (bone.data.x + frames[#frames - 1] - bone.x) * alpha
+			bone.y = bone.y + (bone.data.y + frames[#frames] - bone.y) * alpha
+			return
+		end
+
+		-- Interpolate between the last frame and the current frame.
+		local frameIndex = binarySearch(frames, time, 3)
+		local lastFrameX = frames[frameIndex - 2]
+		local lastFrameY = frames[frameIndex - 1]
+		local frameTime = frames[frameIndex]
+		local percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime)
+		if percent < 0 then percent = 0 elseif percent > 1 then percent = 1 end
+		percent = self:getCurvePercent(frameIndex / 3 - 1, percent)
+
+		bone.x = bone.x + (bone.data.x + lastFrameX + (frames[frameIndex + FRAME_X] - lastFrameX) * percent - bone.x) * alpha
+		bone.y = bone.y + (bone.data.y + lastFrameY + (frames[frameIndex + FRAME_Y] - lastFrameY) * percent - bone.y) * alpha
+	end
+
+	return self
+end
+
+Animation.ScaleTimeline = {}
+function Animation.ScaleTimeline.new ()
+	local LAST_FRAME_TIME = -3
+	local FRAME_X = 1
+	local FRAME_Y = 2
+
+	local self = Animation.TranslateTimeline.new()
+
+	function self:apply (skeleton, time, alpha)
+		local frames = self.frames
+		if time < frames[0] then return end -- Time is before first frame.
+
+		local bone = skeleton.bones[self.boneIndex]
+
+		if time >= frames[#frames - 2] then -- Time is after last frame.
+			bone.scaleX = bone.scaleX + (bone.data.scaleX - 1 + frames[#frames - 1] - bone.scaleX) * alpha
+			bone.scaleY = bone.scaleY + (bone.data.scaleY - 1 + frames[#frames] - bone.scaleY) * alpha
+			return
+		end
+
+		-- Interpolate between the last frame and the current frame.
+		local frameIndex = binarySearch(frames, time, 3)
+		local lastFrameX = frames[frameIndex - 2]
+		local lastFrameY = frames[frameIndex - 1]
+		local frameTime = frames[frameIndex]
+		local percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime)
+		if percent < 0 then percent = 0 elseif percent > 1 then percent = 1 end
+		percent = self:getCurvePercent(frameIndex / 3 - 1, percent)
+
+		bone.scaleX = bone.scaleX + (bone.data.scaleX - 1 + lastFrameX + (frames[frameIndex + FRAME_X] - lastFrameX) * percent - bone.scaleX) * alpha
+		bone.scaleY = bone.scaleY + (bone.data.scaleY - 1 + lastFrameY + (frames[frameIndex + FRAME_Y] - lastFrameY) * percent - bone.scaleY) * alpha
+	end
+
+	return self
+end
+
+Animation.ColorTimeline = {}
+function Animation.ColorTimeline.new ()
+	local LAST_FRAME_TIME = -5
+	local FRAME_R = 1
+	local FRAME_G = 2
+	local FRAME_B = 3
+	local FRAME_A = 4
+
+	local self = Animation.CurveTimeline.new()
+	self.frames = {}
+
+	function self:getDuration ()
+		return self.frames[#self.frames - 4]
+	end
+
+	function self:getKeyframeCount ()
+		return (#self.frames + 1) / 5
+	end
+
+	function self:setKeyframe (keyframeIndex, time, r, g, b, a)
+		keyframeIndex = keyframeIndex * 5
+		self.frames[keyframeIndex] = time
+		self.frames[keyframeIndex + 1] = r
+		self.frames[keyframeIndex + 2] = g
+		self.frames[keyframeIndex + 3] = b
+		self.frames[keyframeIndex + 4] = a
+	end
+
+	function self:apply (skeleton, time, alpha)
+		local frames = self.frames
+		if time < frames[0] then return end -- Time is before first frame.
+
+		local slot = skeleton.slots[self.slotIndex]
+
+		if time >= frames[#frames - 4] then -- Time is after last frame.
+			local r = frames[#frames - 3]
+			local g = frames[#frames - 2]
+			local b = frames[#frames - 1]
+			local a = frames[#frames]
+			slot:setColor(r, g, b, a)
+			return
+		end
+
+		-- Interpolate between the last frame and the current frame.
+		local frameIndex = binarySearch(frames, time, 5)
+		local lastFrameR = frames[frameIndex - 4]
+		local lastFrameG = frames[frameIndex - 3]
+		local lastFrameB = frames[frameIndex - 2]
+		local lastFrameA = frames[frameIndex - 1]
+		local frameTime = frames[frameIndex]
+		local percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime)
+		if percent < 0 then percent = 0 elseif percent > 255 then percent = 255 end
+		percent = self:getCurvePercent(frameIndex / 5 - 1, percent)
+
+		local r = lastFrameR + (frames[frameIndex + FRAME_R] - lastFrameR) * percent
+		local g = lastFrameG + (frames[frameIndex + FRAME_G] - lastFrameG) * percent
+		local b = lastFrameB + (frames[frameIndex + FRAME_B] - lastFrameB) * percent
+		local a = lastFrameA + (frames[frameIndex + FRAME_A] - lastFrameA) * percent
+		if alpha < 1 then
+			slot:setColor(slot.r + (r - color.r) * alpha, slot.g + (g - color.g) * alpha, slot.b + (b - color.b) * alpha, slot.a + (a - color.a) * alpha)
+		else
+			slot:setColor(r, g, b, a)
+		end
+	end
+
+	return self
+end
+
+Animation.AttachmentTimeline = {}
+function Animation.AttachmentTimeline.new ()
+	local self = Animation.CurveTimeline.new()
+	self.frames = {}
+	self.attachmentNames = {}
+
+	function self:getDuration ()
+		return self.frames[#self.frames]
+	end
+
+	function self:getKeyframeCount ()
+		return #self.frames + 1
+	end
+
+	function self:setKeyframe (keyframeIndex, time, attachmentName)
+		self.frames[keyframeIndex] = time
+		self.attachmentNames[keyframeIndex] = attachmentName
+	end
+
+	function self:apply (skeleton, time, alpha)
+		local frames = self.frames
+		if time < frames[0] then return end -- Time is before first frame.
+
+		local frameIndex
+		if time >= frames[#frames] then -- Time is after last frame.
+			frameIndex = #frames
+		else
+			frameIndex = binarySearch(frames, time, 1) - 1
+		end
+
+		local attachmentName = self.attachmentNames[frameIndex]
+		local attachment
+		if attachmentName then attachment = skeleton:getAttachment(self.slotName, attachmentName) end
+		skeleton:findSlot(self.slotName):setAttachment(attachment)
+	end
+
+	return self
+end
+
+return Animation

+ 30 - 0
spine-corona/spine/AttachmentResolver.lua

@@ -0,0 +1,30 @@
+
+local AttachmentResolver = {
+	failed = {}
+}
+function AttachmentResolver.new ()
+	local self = {
+		images = {}
+	}
+
+	function self:resolve (skeleton, attachment)
+		local image = self:createImage(attachment)
+		if image then
+			image:setReferencePoint(display.CenterReferencePoint);
+			image.width = attachment.width
+			image.height = attachment.height
+		else
+			print("Error creating image: " .. attachment.name)
+			image = AttachmentResolver.failed
+		end
+		skeleton.images[attachment] = image
+		return image
+	end
+
+	function self:createImage (attachment)
+		return display.newImage(attachment.name .. ".png")
+	end
+
+	return self
+end
+return AttachmentResolver

+ 54 - 0
spine-corona/spine/Bone.lua

@@ -0,0 +1,54 @@
+
+local Bone = {}
+function Bone.new (data, parent)
+	if not data then error("data cannot be nil", 2) end
+	
+	local self = {
+		data = data,
+		parent = parent
+	}
+
+	function self:updateWorldTransform (flipX, flipY)
+		local parent = self.parent
+		if parent then
+			self.worldX = self.x * parent.m00 + self.y * parent.m01 + parent.worldX
+			self.worldY = self.x * parent.m10 + self.y * parent.m11 + parent.worldY
+			self.worldScaleX = parent.worldScaleX * self.scaleX
+			self.worldScaleY = parent.worldScaleY * self.scaleY
+			self.worldRotation = parent.worldRotation + self.rotation
+		else
+			self.worldX = self.x
+			self.worldY = self.y
+			self.worldScaleX = self.scaleX
+			self.worldScaleY = self.scaleY
+			self.worldRotation = self.rotation
+		end
+		local radians = math.rad(self.worldRotation)
+		local cos = math.cos(radians)
+		local sin = math.sin(radians)
+		self.m00 = cos * self.worldScaleX
+		self.m10 = sin * self.worldScaleX
+		self.m01 = -sin * self.worldScaleY
+		self.m11 = cos * self.worldScaleY
+		if flipX then
+			self.m00 = -self.m00
+			self.m01 = -self.m01
+		end
+		if flipY then
+			self.m10 = -self.m10
+			self.m11 = -self.m11
+		end
+	end
+
+	function self:setToBindPose ()
+		local data = self.data
+		self.x = data.x
+		self.y = data.y
+		self.rotation = data.rotation
+		self.scaleX = data.scaleX
+		self.scaleY = data.scaleY
+	end
+	
+	return self
+end
+return Bone

+ 13 - 0
spine-corona/spine/BoneData.lua

@@ -0,0 +1,13 @@
+
+local BoneData = {}
+function BoneData.new (name, parent)
+	if not name then error("name cannot be nil", 2) end
+
+	local self = {
+		name = name,
+		parent = parent
+	}
+
+	return self
+end
+return BoneData

+ 12 - 0
spine-corona/spine/RegionAttachment.lua

@@ -0,0 +1,12 @@
+
+local RegionAttachment = {}
+function RegionAttachment.new (name)
+	if not name then error("name cannot be nil", 2) end
+	
+	local self = {
+		name = name
+	}
+	
+	return self
+end
+return RegionAttachment

+ 172 - 0
spine-corona/spine/Skeleton.lua

@@ -0,0 +1,172 @@
+
+local utils = require "spine.utils"
+local Bone = require "spine.Bone"
+local Slot = require "spine.Slot"
+local AttachmentResolver = require "spine.AttachmentResolver"
+
+local Skeleton = {}
+function Skeleton.new (skeletonData, group)
+	if not skeletonData then error("skeletonData cannot be nil", 2) end
+
+	local self = group or display.newGroup()
+	self.data = skeletonData
+	self.bones = {}
+	self.slots = {}
+	self.drawOrder = {}
+	self.images = {}
+
+	for i,boneData in ipairs(skeletonData.bones) do
+		local parent
+		if boneData.parent then parent = self.bones[utils.indexOf(skeletonData.bones, boneData.parent)] end
+		table.insert(self.bones, Bone.new(boneData, parent))
+	end
+
+	for i,slotData in ipairs(skeletonData.slots) do
+		local bone = self.bones[utils.indexOf(skeletonData.bones, slotData.boneData)]
+		local slot = Slot.new(slotData, self, bone)
+		table.insert(self.slots, slot)
+		table.insert(self.drawOrder, slot)
+	end
+
+	function self:updateWorldTransform ()
+		for i,bone in ipairs(self.bones) do
+			bone:updateWorldTransform(self.flipX, self.flipY)
+		end
+
+		for i,slot in ipairs(self.drawOrder) do
+			if slot.attachment then
+				local image = self.images[slot.attachment]
+				if not image then image = self.data.attachmentResolver:resolve(self, slot.attachment) end
+				if image ~= AttachmentResolver.failed then
+					image.x = slot.bone.worldX + slot.attachment.x * slot.bone.m00 + slot.attachment.y * slot.bone.m01
+					image.y = -(slot.bone.worldY + slot.attachment.x * slot.bone.m10 + slot.attachment.y * slot.bone.m11)
+					image.rotation = -(slot.bone.worldRotation + slot.attachment.rotation)
+					image.xScale = slot.bone.worldScaleX + slot.attachment.scaleX - 1
+					image.yScale = slot.bone.worldScaleY + slot.attachment.scaleY - 1
+					if self.flipX then
+						image.xScale = -image.xScale
+						image.rotation = -image.rotation
+					end
+					if self.flipY then
+						image.yScale = -image.yScale
+						image.rotation = -image.rotation
+					end
+					image:setFillColor(slot.r, slot.g, slot.b, slot.a)
+					self:insert(image)
+				end
+			end
+		end
+
+		if self.debug then
+			for i,bone in ipairs(self.bones) do
+				if not bone.line then bone.line = display.newLine(0, 0, bone.data.length, 0) end
+				bone.line.x = bone.worldX
+				bone.line.y = -bone.worldY
+				bone.line.rotation = -bone.worldRotation
+				if self.flipX then
+					bone.line.xScale = -1
+					bone.line.rotation = -bone.line.rotation
+				else
+					bone.line.xScale = 1
+				end
+				if self.flipY then
+					bone.line.yScale = -1
+					bone.line.rotation = -bone.line.rotation
+				else
+					bone.line.yScale = 1
+				end
+				bone.line:setColor(255, 0, 0)
+				self:insert(bone.line)
+
+				if not bone.circle then bone.circle = display.newCircle(0, 0, 3) end
+				bone.circle.x = bone.worldX
+				bone.circle.y = -bone.worldY
+				bone.circle:setFillColor(0, 255, 0)
+				self:insert(bone.circle)
+			end
+		end
+	end
+
+	function self:setToBindPose ()
+		self:setBonesToBindPose()
+		self:setSlotsToBindPose()
+	end
+
+	function self:setBonesToBindPose ()
+		for i,bone in ipairs(self.bones) do
+			bone:setToBindPose()
+		end
+	end
+
+	function self:setSlotsToBindPose ()
+		for i,slot in ipairs(self.slots) do
+			slot:setToBindPose()
+		end
+	end
+
+	function self:getRootBone ()
+		return self.bones[1]
+	end
+
+	function self:findSlot (slotName)
+		if not slotName then error("slotName cannot be nil.", 2) end
+		for i,slot in ipairs(self.slots) do
+			if slot.data.name == slotName then return slot end
+		end
+		return nil
+	end
+
+	function self:setSkin (skinName)
+		local newSkin
+		if skinName then
+			newSkin = self.data:findSkin(skinName)
+			if not newSkin then error("Skin not found: " .. skinName, 2) end
+			if self.skin then
+				-- Attach all attachments from the new skin if the corresponding attachment from the old skin is currently attached.
+				for k,v in self.skin.attachments do
+					local attachment = v[3]
+					local slotIndex = v[1]
+					local slot = self.slots[slotIndex]
+					if slot.attachment == attachment then
+						local name = v[2]
+						local newAttachment = newSkin:getAttachment(slotIndex, name)
+						if newAttachment then slot:setAttachment(newAttachment) end
+					end
+				end
+			end
+		end
+		self.skin = newSkin
+	end
+
+	function self:getAttachment (slotName, attachmentName)
+		if not slotName then error("slotName cannot be nil.", 2) end
+		if not attachmentName then error("attachmentName cannot be nil.", 2) end
+		local slotIndex = self.data:findSlotIndex(slotName)
+		if slotIndex == -1 then error("Slot not found: " .. slotName, 2) end
+		if self.data.defaultSkin then
+			local attachment = self.data.defaultSkin:getAttachment(slotIndex, attachmentName)
+			if attachment then return attachment end
+		end
+		if self.skin then return self.skin:getAttachment(slotIndex, attachmentName) end
+		return nil
+	end
+
+	function self:setAttachment (slotName, attachmentName)
+		if not slotName then error("slotName cannot be nil.", 2) end
+		if not attachmentName then error("attachmentName cannot be nil.", 2) end
+		for i,slot in ipairs(self.slots) do
+			if slot.data.name == slotName then
+				slot:setAttachment(self:getAttachment(slotName, attachmentName))
+				return
+			end
+		end
+		error("Slot not found: " + slotName, 2)
+	end
+
+	function self:update (delta)
+		self.time = self.time + delta
+	end
+
+	return self
+end
+return Skeleton

+ 55 - 0
spine-corona/spine/SkeletonData.lua

@@ -0,0 +1,55 @@
+
+local SkeletonData = {}
+function SkeletonData.new (attachmentResolver)
+	if not attachmentResolver then error("attachmentResolver cannot be nil", 2) end
+
+	local self = {
+		attachmentResolver = attachmentResolver,
+		bones = {},
+		slots = {},
+		skins = {}
+	}
+
+	function self:findBone (boneName)
+		if not boneName then error("boneName cannot be nil.", 2) end
+		for i,bone in ipairs(self.bones) do
+			if bone.name == boneName then return bone end
+		end
+		return nil
+	end
+
+	function self:findBoneIndex (boneName)
+		if not boneName then error("boneName cannot be nil.", 2) end
+		for i,bone in ipairs(self.bones) do
+			if bone.name == boneName then return i end
+		end
+		return -1
+	end
+
+	function self:findSlot (slotName)
+		if not slotName then error("slotName cannot be nil.", 2) end
+		for i,slot in ipairs(self.slots) do
+			if slot.name == slotName then return slot end
+		end
+		return nil
+	end
+
+	function self:findSlotIndex (slotName)
+		if not slotName then error("slotName cannot be nil.", 2) end
+		for i,slot in ipairs(self.slots) do
+			if slot.name == slotName then return i end
+		end
+		return -1
+	end
+
+	function self:findSkin (skinName)
+		if not skinName then error("skinName cannot be nil.", 2) end
+		for i,skin in ipairs(self.skins) do
+			if skin.name == skinName then return skin end
+		end
+		return nil
+	end
+
+	return self
+end
+return SkeletonData

+ 255 - 0
spine-corona/spine/SkeletonJson.lua

@@ -0,0 +1,255 @@
+
+local utils = require "spine.utils"
+local SkeletonData = require "spine.SkeletonData"
+local BoneData = require "spine.BoneData"
+local SlotData = require "spine.SlotData"
+local Skin = require "spine.Skin"
+local RegionAttachment = require "spine.RegionAttachment"
+local AttachmentResolver = require "spine.AttachmentResolver"
+local Animation = require "spine.Animation"
+local json = require "json"
+
+local TIMELINE_SCALE = "scale"
+local TIMELINE_ROTATE = "rotate"
+local TIMELINE_TRANSLATE = "translate"
+local TIMELINE_ATTACHMENT = "attachment"
+local TIMELINE_COLOR = "color"
+
+local ATTACHMENT_REGION = "region"
+local ATTACHMENT_ANIMATED_REGION = "animatedRegion"
+
+local SkeletonJson = {}
+function SkeletonJson.new (attachmentResolver)
+	if not attachmentResolver then attachmentResolver = AttachmentResolver.new() end
+
+	local self = {
+		attachmentResolver = attachmentResolver,
+		scale = 1
+	}
+
+	function self:readSkeletonDataFile (fileName, base)
+		return self:readSkeletonData(utils.readFile(fileName, base))
+	end
+
+	local readAttachment
+
+	function self:readSkeletonData (jsonText)
+		local skeletonData = SkeletonData.new(self.attachmentResolver)
+
+		local root = json.decode(jsonText)
+		if not root then error("Invalid JSON: " .. jsonText, 2) end
+
+		-- Bones.
+		for i,boneMap in ipairs(root["bones"]) do
+			local boneName = boneMap["name"]
+			local parent = nil
+			local parentName = boneMap["parent"]
+			if parentName then
+				parent = skeletonData:findBone(parentName)
+				if not parent then error("Parent bone not found: " .. parentName) end
+			end
+			local boneData = BoneData.new(boneName, parent)
+			boneData.length = (boneMap["length"] or 0) * self.scale
+			boneData.x = (boneMap["x"] or 0) * self.scale
+			boneData.y = (boneMap["y"] or 0) * self.scale
+			boneData.rotation = (boneMap["rotation"] or 0)
+			boneData.scaleX = (boneMap["scaleX"] or 1)
+			boneData.scaleY = (boneMap["scaleY"] or 1)
+			table.insert(skeletonData.bones, boneData)
+		end
+
+		-- Slots.
+		if root["slots"] then
+			for i,slotMap in ipairs(root["slots"]) do
+				local slotName = slotMap["name"]
+				local boneName = slotMap["bone"]
+				local boneData = skeletonData:findBone(boneName)
+				if not boneData then error("Slot bone not found: " .. boneName) end
+				local slotData = SlotData.new(slotName, boneData)
+
+				local color = slotMap["color"]
+				if color then
+					slotData:setColor(
+						tonumber(color:sub(1, 2), 16),
+						tonumber(color:sub(3, 4), 16),
+						tonumber(color:sub(5, 6), 16),
+						tonumber(color:sub(7, 8), 16)
+					)
+				end
+
+				slotData.attachmentName = slotMap["attachment"]
+
+				table.insert(skeletonData.slots, slotData)
+			end
+		end
+
+		-- Skins.
+		map = root["skins"]
+		if map then
+			for skinName,skinMap in pairs(map) do
+				local skin = Skin.new(skinName)
+				for slotName,slotMap in pairs(skinMap) do
+					local slotIndex = skeletonData:findSlotIndex(slotName)
+					for attachmentName,attachmentMap in pairs(slotMap) do
+						local attachment = readAttachment(attachmentName, attachmentMap, self.scale)
+						skin:addAttachment(slotIndex, attachmentName, attachment)
+					end
+				end
+				if skin.name == "default" then
+					skeletonData.defaultSkin = skin
+				else
+					table.insert(skeletonData.skins, skin)
+				end
+			end
+		end
+
+		return skeletonData
+	end
+
+	readAttachment = function (name, map, scale)
+		name = map["name"] or name
+		local attachment
+		local type = map["type"] or ATTACHMENT_REGION
+		if type == ATTACHMENT_REGION then
+			attachment = RegionAttachment.new(name)
+		else
+			error("Unknown attachment type: " .. type .. " (" + name + ")")
+		end
+
+		attachment.x = (map["x"] or 0) * scale
+		attachment.y = (map["y"] or 0) * scale
+		attachment.scaleX = (map["scaleX"] or 1)
+		attachment.scaleY = (map["scaleY"] or 1)
+		attachment.rotation = (map["rotation"] or 0)
+		attachment.width = map["width"] * scale
+		attachment.height = map["height"] * scale
+		return attachment
+	end
+
+	function self:readAnimationFile (skeletonData, fileName, base)
+		return self:readAnimation(skeletonData, utils.readFile(fileName, base))
+	end
+
+	local readCurve
+	
+	function self:readAnimation (skeletonData, jsonText)
+		local timelines = {}
+		local duration = 0
+
+		local root = json.decode(jsonText)
+		if not root then error("Invalid JSON: " .. jsonText, 2) end
+
+		local bonesMap = root["bones"]
+		for boneName,propertyMap in pairs(bonesMap) do
+			local boneIndex = skeletonData:findBoneIndex(boneName)
+			if boneIndex == -1 then error("Bone not found: " .. boneName) end
+
+			for timelineType,values in pairs(propertyMap) do
+				if timelineType == TIMELINE_ROTATE then
+					local timeline = Animation.RotateTimeline.new()
+					timeline.boneIndex = boneIndex
+
+					local keyframeIndex = 0
+					for i,valueMap in ipairs(values) do
+						local time = valueMap["time"]
+						timeline:setKeyframe(keyframeIndex, time, valueMap["angle"])
+						readCurve(timeline, keyframeIndex, valueMap)
+						keyframeIndex = keyframeIndex + 1
+					end
+					table.insert(timelines, timeline)
+					duration = math.max(duration, timeline:getDuration())
+
+				elseif timelineType == TIMELINE_TRANSLATE or timelineType == TIMELINE_SCALE then
+					local timeline
+					local timelineScale = 1
+					if timelineType == TIMELINE_SCALE then
+						timeline = Animation.ScaleTimeline.new()
+					else
+						timeline = Animation.TranslateTimeline.new()
+						timelineScale = self.scale
+					end
+					timeline.boneIndex = boneIndex
+
+					local keyframeIndex = 0
+					for i,valueMap in ipairs(values) do
+						local time = valueMap["time"]
+						local x = (valueMap["x"] or 0) * timelineScale
+						local y = (valueMap["y"] or 0) * timelineScale
+						timeline:setKeyframe(keyframeIndex, time, x, y)
+						readCurve(timeline, keyframeIndex, valueMap)
+						keyframeIndex = keyframeIndex + 1
+					end
+					table.insert(timelines, timeline)
+					duration = math.max(duration, timeline:getDuration())
+
+				else
+					error("Invalid timeline type for a bone: " .. timelineType .. " (" .. boneName .. ")")
+				end
+			end
+		end
+
+		local slotsMap = root["slots"]
+		if slotsMap then
+			for slotName,propertyMap in pairs(slotsMap) do
+				local slotIndex = skeletonData:findSlotIndex(slotName)
+
+				for timelineType,values in pairs(propertyMap) do
+					if timelineType == TIMELINE_COLOR then
+						local timeline = Animation.ColorTimeline.new()
+						timeline.slotIndex = slotIndex
+
+						local keyframeIndex = 0
+						for i,valueMap in ipairs(values) do
+							local time = valueMap["time"]
+							local color = valueMap["color"]
+							timeline:setKeyframe(
+								keyframeIndex, time, 
+								tonumber(color:sub(1, 2), 16),
+								tonumber(color:sub(3, 4), 16),
+								tonumber(color:sub(5, 6), 16),
+								tonumber(color:sub(7, 8), 16)
+							)
+							readCurve(timeline, keyframeIndex, valueMap)
+							keyframeIndex = keyframeIndex + 1
+						end
+						table.insert(timelines, timeline)
+						duration = math.max(duration, timeline:getDuration())
+
+					elseif timelineType == TIMELINE_ATTACHMENT then
+						local timeline = Animation.AttachmentTimeline.new()
+						timeline.slotName = slotName
+
+						local keyframeIndex = 0
+						for i,valueMap in ipairs(values) do
+							local time = valueMap["time"]
+							local attachmentName = valueMap["name"]
+							if attachmentName == json.null then attachmentName = nil end
+							timeline:setKeyframe(keyframeIndex, time, attachmentName)
+							keyframeIndex = keyframeIndex + 1
+						end
+						table.insert(timelines, timeline)
+						duration = math.max(duration, timeline:getDuration())
+
+					else
+						error("Invalid frame type for a slot: " .. timelineType .. " (" .. slotName .. ")")
+					end
+				end
+			end
+		end
+
+		return Animation.new(timelines, duration)
+	end
+
+	readCurve = function (timeline, keyframeIndex, valueMap)
+		local curve = valueMap["curve"]
+		if not curve then return end
+		if curve == "stepped" then
+			timeline:setStepped(keyframeIndex)
+		else
+			timeline:setCurve(keyframeIndex, curve[1], curve[2], curve[3], curve[4])
+		end
+	end
+
+	return self
+end
+return SkeletonJson

+ 39 - 0
spine-corona/spine/Skin.lua

@@ -0,0 +1,39 @@
+
+local Skin = {}
+function Skin.new (name)
+	if not name then error("name cannot be nil", 2) end
+	
+	local self = {
+		name = name,
+		attachments = {}
+	}
+
+	function self:addAttachment (slotIndex, name, attachment)
+		if not name then error("name cannot be nil.", 2) end
+		self.attachments[slotIndex .. ":" .. name] = { slotIndex, name, attachment }
+	end
+
+	function self:getAttachment (slotIndex, name)
+		if not name then error("name cannot be nil.", 2) end
+		local values = self.attachments[slotIndex .. ":" .. name]
+		if not values then return nil end
+		return values[3]
+	end
+
+	function self:findNamesForSlot (slotIndex)
+		local names = {}
+		for k,v in self.attachments do
+			if v[1] == slotIndex then table.insert(names, v[2]) end
+		end
+	end
+
+	function self:findAttachmentsForSlot (slotIndex)
+		local attachments = {}
+		for k,v in self.attachments do
+			if v[1] == slotIndex then table.insert(attachments, v[3]) end
+		end
+	end
+
+	return self
+end
+return Skin

+ 54 - 0
spine-corona/spine/Slot.lua

@@ -0,0 +1,54 @@
+
+local utils = require "spine.utils"
+
+local Slot = {}
+function Slot.new (slotData, skeleton, bone)
+	if not slotData then error("slotData cannot be nil", 2) end
+	if not skeleton then error("skeleton cannot be nil", 2) end
+	if not bone then error("bone cannot be nil", 2) end
+
+	local self = {
+		data = slotData,
+		skeleton = skeleton,
+		bone = bone
+	}
+
+	function self:setColor (r, g, b, a)
+		self.r = r
+		self.g = g
+		self.b = b
+		self.a = a
+	end
+
+	function self:setAttachment (attachment)
+		if self.attachment and self.attachment ~= attachment and self.skeleton.images[self.attachment] then
+			self.skeleton.images[self.attachment]:removeSelf()
+			self.skeleton.images[self.attachment] = nil
+		end
+		self.attachment = attachment
+		self.attachmentTime = self.skeleton.time
+	end
+
+	function self:setAttachmentTime (time)
+		self.attachmentTime = self.skeleton.time - time
+	end
+
+	function self:getAttachmentTime ()
+		return self.skeleton.time - self.attachmentTime
+	end
+
+	function self:setToBindPose ()
+		local data = self.data
+
+		self:setColor(data.r, data.g, data.b, data.a)
+
+		local attachment
+		if data.attachmentName then attachment = self.skeleton:getAttachment(data.name, data.attachmentName) end
+		self:setAttachment(attachment)
+	end
+
+	self:setColor(255, 255, 255, 255)
+
+	return self
+end
+return Slot

+ 23 - 0
spine-corona/spine/SlotData.lua

@@ -0,0 +1,23 @@
+
+local SlotData = {}
+function SlotData.new (name, boneData)
+	if not name then error("name cannot be nil", 2) end
+	if not boneData then error("boneData cannot be nil", 2) end
+	
+	local self = {
+		name = name,
+		boneData = boneData
+	}
+
+	function self:setColor (r, g, b, a)
+		self.r = r
+		self.g = g
+		self.b = b
+		self.a = a
+	end
+
+	self:setColor(255, 255, 255, 255)
+	
+	return self;
+end
+return SlotData

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

@@ -0,0 +1,17 @@
+
+local spine = {}
+
+spine.utils = require "spine.utils"
+spine.SkeletonJson = require "spine.SkeletonJson"
+spine.SkeletonData = require "spine.SkeletonData"
+spine.BoneData = require "spine.BoneData"
+spine.SlotData = require "spine.SlotData"
+spine.Skin = require "spine.Skin"
+spine.RegionAttachment = require "spine.RegionAttachment"
+spine.Skeleton = require "spine.Skeleton"
+spine.Bone = require "spine.Bone"
+spine.Slot = require "spine.Slot"
+spine.AttachmentResolver = require "spine.AttachmentResolver"
+spine.Animation = require "spine.Animation"
+
+return spine

+ 59 - 0
spine-corona/spine/utils.lua

@@ -0,0 +1,59 @@
+
+local utils = {}
+
+utils.readFile = function (fileName, base)
+	if not base then base = system.ResourceDirectory; end
+	local path = system.pathForFile(fileName, base)
+	local file = io.open(path, "r")
+	if not file then return nil; end
+	local contents = file:read("*a")
+	io.close(file)
+	return contents
+end
+
+function tablePrint (tt, indent, done)
+	done = done or {}
+	indent = indent or 0
+	if type(tt) == "table" then
+		local sb = {}
+		for key, value in pairs (tt) do
+			table.insert(sb, string.rep (" ", indent)) -- indent it
+			if type (value) == "table" and not done [value] then
+				done [value] = true
+				table.insert(sb, "{\n");
+				table.insert(sb, tablePrint (value, indent + 2, done))
+				table.insert(sb, string.rep (" ", indent)) -- indent it
+				table.insert(sb, "}\n");
+			elseif "number" == type(key) then
+				table.insert(sb, string.format("\"%s\"\n", tostring(value)))
+			else
+				table.insert(sb, string.format(
+					"%s = \"%s\"\n", tostring (key), tostring(value)))
+			end
+		end
+		return table.concat(sb)
+	else
+		return tt .. "\n"
+	end
+end
+
+function utils.print (value)
+	if "nil" == type(value) then
+		print(tostring(nil))
+	elseif "table" == type(value) then
+		print(tablePrint(value))
+	elseif "string" == type(value) then
+		print(value)
+	else
+		print(tostring(value))
+	end
+end
+
+function utils.indexOf (haystack, needle)
+	for i,value in ipairs(haystack) do
+		if value == needle then return i end
+	end
+	return nil
+end
+
+return utils