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

cpp, loading, applying and animating with SFML is working.

NathanSweet 12 жил өмнө
parent
commit
6710c1e9c9

+ 11 - 11
spine-corona/spine/SkeletonJson.lua

@@ -140,12 +140,12 @@ function SkeletonJson.new (attachmentResolver)
 		if not root then error("Invalid JSON: " .. jsonText, 2) end
 
 		local bonesMap = root["bones"]
-		for boneName,propertyMap in pairs(bonesMap) do
+		for boneName,timelineMap 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
+			for timelineName,values in pairs(timelineMap) do
+				if timelineName == TIMELINE_ROTATE then
 					local timeline = Animation.RotateTimeline.new()
 					timeline.boneIndex = boneIndex
 
@@ -159,10 +159,10 @@ function SkeletonJson.new (attachmentResolver)
 					table.insert(timelines, timeline)
 					duration = math.max(duration, timeline:getDuration())
 
-				elseif timelineType == TIMELINE_TRANSLATE or timelineType == TIMELINE_SCALE then
+				elseif timelineName == TIMELINE_TRANSLATE or timelineName == TIMELINE_SCALE then
 					local timeline
 					local timelineScale = 1
-					if timelineType == TIMELINE_SCALE then
+					if timelineName == TIMELINE_SCALE then
 						timeline = Animation.ScaleTimeline.new()
 					else
 						timeline = Animation.TranslateTimeline.new()
@@ -183,18 +183,18 @@ function SkeletonJson.new (attachmentResolver)
 					duration = math.max(duration, timeline:getDuration())
 
 				else
-					error("Invalid timeline type for a bone: " .. timelineType .. " (" .. boneName .. ")")
+					error("Invalid timeline type for a bone: " .. timelineName .. " (" .. boneName .. ")")
 				end
 			end
 		end
 
 		local slotsMap = root["slots"]
 		if slotsMap then
-			for slotName,propertyMap in pairs(slotsMap) do
+			for slotName,timelineMap in pairs(slotsMap) do
 				local slotIndex = skeletonData:findSlotIndex(slotName)
 
-				for timelineType,values in pairs(propertyMap) do
-					if timelineType == TIMELINE_COLOR then
+				for timelineName,values in pairs(timelineMap) do
+					if timelineName == TIMELINE_COLOR then
 						local timeline = Animation.ColorTimeline.new()
 						timeline.slotIndex = slotIndex
 
@@ -215,7 +215,7 @@ function SkeletonJson.new (attachmentResolver)
 						table.insert(timelines, timeline)
 						duration = math.max(duration, timeline:getDuration())
 
-					elseif timelineType == TIMELINE_ATTACHMENT then
+					elseif timelineName == TIMELINE_ATTACHMENT then
 						local timeline = Animation.AttachmentTimeline.new()
 						timeline.slotName = slotName
 
@@ -231,7 +231,7 @@ function SkeletonJson.new (attachmentResolver)
 						duration = math.max(duration, timeline:getDuration())
 
 					else
-						error("Invalid frame type for a slot: " .. timelineType .. " (" .. slotName .. ")")
+						error("Invalid frame type for a slot: " .. timelineName .. " (" .. slotName .. ")")
 					end
 				end
 			end

+ 2 - 1
spine-cpp/.settings/org.eclipse.cdt.codan.core.prefs

@@ -19,7 +19,7 @@ org.eclipse.cdt.codan.internal.checkers.CatchByReference=Warning
 org.eclipse.cdt.codan.internal.checkers.CatchByReference.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},unknown\=>false,exceptions\=>()}
 org.eclipse.cdt.codan.internal.checkers.CircularReferenceProblem=Error
 org.eclipse.cdt.codan.internal.checkers.CircularReferenceProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}}
-org.eclipse.cdt.codan.internal.checkers.ClassMembersInitialization=Warning
+org.eclipse.cdt.codan.internal.checkers.ClassMembersInitialization=-Warning
 org.eclipse.cdt.codan.internal.checkers.ClassMembersInitialization.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},skip\=>true}
 org.eclipse.cdt.codan.internal.checkers.FieldResolutionProblem=Error
 org.eclipse.cdt.codan.internal.checkers.FieldResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}}
@@ -65,3 +65,4 @@ org.eclipse.cdt.codan.internal.checkers.UnusedVariableDeclarationProblem=Warning
 org.eclipse.cdt.codan.internal.checkers.UnusedVariableDeclarationProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},macro\=>true,exceptions\=>("@(\#)","$Id")}
 org.eclipse.cdt.codan.internal.checkers.VariableResolutionProblem=Error
 org.eclipse.cdt.codan.internal.checkers.VariableResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}}
+useParentScope=false

+ 1 - 1
spine-cpp/includes/spine-sfml/Skeleton.h

@@ -9,7 +9,7 @@ namespace spine {
 class Skeleton: public BaseSkeleton, public sf::Drawable {
 public:
 	sf::VertexArray vertexArray;
-	sf::Texture *texture;
+	sf::Texture *texture; // BOZO - This is ugly. Support multiple textures?
 
 	Skeleton (SkeletonData *skeletonData);
 

+ 15 - 15
spine-cpp/includes/spine/Animation.h

@@ -15,6 +15,8 @@ public:
 	float duration;
 
 	Animation (const std::vector<Timeline*> &timelines, float duration);
+
+	void apply (BaseSkeleton *skeleton, float time, bool loop);
 };
 
 //
@@ -63,9 +65,9 @@ public:
 	RotateTimeline (int keyframeCount);
 	virtual ~RotateTimeline ();
 
-	virtual float getDuration () = 0;
-	virtual int getKeyframeCount () = 0;
-	virtual void apply (BaseSkeleton *skeleton, float time, float alpha) = 0;
+	virtual float getDuration ();
+	virtual int getKeyframeCount ();
+	virtual void apply (BaseSkeleton *skeleton, float time, float alpha);
 
 	void setKeyframe (int keyframeIndex, float time, float value);
 };
@@ -81,9 +83,9 @@ public:
 	TranslateTimeline (int keyframeCount);
 	virtual ~TranslateTimeline ();
 
-	virtual float getDuration () = 0;
-	virtual int getKeyframeCount () = 0;
-	virtual void apply (BaseSkeleton *skeleton, float time, float alpha) = 0;
+	virtual float getDuration ();
+	virtual int getKeyframeCount ();
+	virtual void apply (BaseSkeleton *skeleton, float time, float alpha);
 
 	void setKeyframe (int keyframeIndex, float time, float x, float y);
 };
@@ -94,9 +96,7 @@ class ScaleTimeline: public TranslateTimeline {
 public:
 	ScaleTimeline (int keyframeCount);
 
-	virtual float getDuration () = 0;
-	virtual int getKeyframeCount () = 0;
-	virtual void apply (BaseSkeleton *skeleton, float time, float alpha) = 0;
+	virtual void apply (BaseSkeleton *skeleton, float time, float alpha);
 };
 
 //
@@ -110,9 +110,9 @@ public:
 	ColorTimeline (int keyframeCount);
 	virtual ~ColorTimeline ();
 
-	virtual float getDuration () = 0;
-	virtual int getKeyframeCount () = 0;
-	virtual void apply (BaseSkeleton *skeleton, float time, float alpha) = 0;
+	virtual float getDuration ();
+	virtual int getKeyframeCount ();
+	virtual void apply (BaseSkeleton *skeleton, float time, float alpha);
 
 	void setKeyframe (int keyframeIndex, float time, float r, float g, float b, float a);
 };
@@ -129,9 +129,9 @@ public:
 	AttachmentTimeline (int keyframeCount);
 	virtual ~AttachmentTimeline ();
 
-	virtual float getDuration () = 0;
-	virtual int getKeyframeCount () = 0;
-	virtual void apply (BaseSkeleton *skeleton, float time, float alpha) = 0;
+	virtual float getDuration ();
+	virtual int getKeyframeCount ();
+	virtual void apply (BaseSkeleton *skeleton, float time, float alpha);
 
 	void setKeyframe (int keyframeIndex, float time, std::string *attachmentName);
 };

+ 0 - 1
spine-cpp/includes/spine/Attachment.h

@@ -11,7 +11,6 @@ class Slot;
 class Attachment {
 public:
 	std::string name;
-	float x, y, scaleX, scaleY, rotation, width, height;
 
 	virtual ~Attachment () {
 	}

+ 1 - 0
spine-cpp/includes/spine/BaseRegionAttachment.h

@@ -10,6 +10,7 @@ class Slot;
 
 class BaseRegionAttachment: public Attachment {
 public:
+	float x, y, scaleX, scaleY, rotation, width, height;
 	float offset[8];
 
 	void updateOffset ();

+ 7 - 1
spine-cpp/includes/spine/BaseSkeletonJson.h

@@ -5,8 +5,9 @@
 
 namespace spine {
 
-class SkeletonData;
 class BaseAttachmentLoader;
+class SkeletonData;
+class Animation;
 
 class BaseSkeletonJson {
 public:
@@ -20,6 +21,11 @@ public:
 	SkeletonData* readSkeletonData (std::istream &file) const;
 	SkeletonData* readSkeletonData (const std::string &json) const;
 	SkeletonData* readSkeletonData (const char *begin, const char *end) const;
+
+	Animation* readAnimation (std::ifstream &file, const SkeletonData *skeletonData) const;
+	Animation* readAnimation (std::istream &file, const SkeletonData *skeletonData) const;
+	Animation* readAnimation (const std::string &json, const SkeletonData *skeletonData) const;
+	Animation* readAnimation (const char *begin, const char *end, const SkeletonData *skeletonData) const;
 };
 
 } /* namespace spine */

+ 22 - 4
spine-cpp/src/main.cpp

@@ -7,13 +7,19 @@ using namespace std;
 using namespace spine;
 
 int main () {
-	ifstream file("../data/spineboy-skeleton.json");
 
 	try {
-		ifstream file2("../data/spineboy.atlas");
-		Atlas *atlas = new Atlas(file2);
+		ifstream atlasFile("../data/spineboy.atlas");
+		Atlas *atlas = new Atlas(atlasFile);
+
 		SkeletonJson skeletonJson(atlas);
-		SkeletonData *skeletonData = skeletonJson.readSkeletonData(file);
+
+		ifstream skeletonFile("../data/spineboy-skeleton.json");
+		SkeletonData *skeletonData = skeletonJson.readSkeletonData(skeletonFile);
+
+		ifstream animationFile("../data/spineboy-walk.json");
+		Animation *animation = skeletonJson.readAnimation(animationFile, skeletonData);
+
 		Skeleton *skeleton = new Skeleton(skeletonData);
 		skeleton->setToBindPose();
 		skeleton->getRootBone()->x = 200;
@@ -23,12 +29,24 @@ int main () {
 		sf::RenderWindow window(sf::VideoMode(640, 480), "Spine SFML");
 		window.setFramerateLimit(60);
 		sf::Event event;
+		sf::Clock deltaClock;
+		float animationTime = 0;
 		while (window.isOpen()) {
 			while (window.pollEvent(event))
 				if (event.type == sf::Event::Closed) window.close();
 			window.clear();
 			window.draw(*skeleton);
 			window.display();
+
+			float delta = deltaClock.getElapsedTime().asSeconds();
+			deltaClock.restart();
+			animationTime += delta;
+
+			skeleton->setToBindPose();
+			skeleton->getRootBone()->x = 200;
+			skeleton->getRootBone()->y = 420;
+			animation->apply(skeleton, animationTime, true);
+			skeleton->updateWorldTransform();
 		}
 	} catch (exception &ex) {
 		cout << ex.what() << endl << flush;

+ 1 - 2
spine-cpp/src/spine-sfml/RegionAttachment.cpp

@@ -9,8 +9,7 @@
 namespace spine {
 
 RegionAttachment::RegionAttachment (AtlasRegion *region) {
-	texture = region->page->texture;
-	sf::Vector2u size = texture->getSize();
+	texture = region->page->texture; // BOZO - Resolve attachment as late as possible?
 	int u = region->x;
 	int u2 = u + region->width;
 	int v = region->y;

+ 23 - 5
spine-cpp/src/spine/Animation.cpp

@@ -1,3 +1,7 @@
+#include <cstring>
+#include <iostream>
+#include <stdexcept>
+#include <math.h>
 #include <spine/Animation.h>
 #include <spine/Bone.h>
 #include <spine/Slot.h>
@@ -14,6 +18,15 @@ Animation::Animation (const vector<Timeline*> &timelines, float duration) :
 				duration(duration) {
 }
 
+void Animation::apply (BaseSkeleton *skeleton, float time, bool loop) {
+	if (!skeleton) throw std::invalid_argument("skeleton cannot be null.");
+
+	if (loop && duration) time = fmodf(time, duration);
+
+	for (int i = 0, n = timelines.size(); i < n; i++)
+		timelines[i]->apply(skeleton, time, 1);
+}
+
 //
 
 static const float LINEAR = 0;
@@ -22,6 +35,7 @@ static const int BEZIER_SEGMENTS = 10;
 
 CurveTimeline::CurveTimeline (int keyframeCount) :
 				curves(new float[(keyframeCount - 1) * 6]) {
+	memset(curves, 0, sizeof(float) * (keyframeCount - 1) * 6);
 }
 
 CurveTimeline::~CurveTimeline () {
@@ -122,7 +136,8 @@ static const int ROTATE_FRAME_VALUE = 1;
 RotateTimeline::RotateTimeline (int keyframeCount) :
 				CurveTimeline(keyframeCount),
 				framesLength(keyframeCount * 2),
-				frames(new float[framesLength]) {
+				frames(new float[framesLength]),
+				boneIndex(0) {
 }
 
 RotateTimeline::~RotateTimeline () {
@@ -159,7 +174,7 @@ void RotateTimeline::apply (BaseSkeleton *skeleton, float time, float alpha) {
 	}
 
 	// Interpolate between the last frame and the current frame.
-	int frameIndex = binarySearch(frames, framesLength, time, 2);
+	int frameIndex = linearSearch(frames, framesLength, time, 2);
 	float lastFrameValue = frames[frameIndex - 1];
 	float frameTime = frames[frameIndex];
 	float percent = 1 - (time - frameTime) / (frames[frameIndex + ROTATE_LAST_FRAME_TIME] - frameTime);
@@ -191,7 +206,8 @@ static const int TRANSLATE_FRAME_Y = 2;
 TranslateTimeline::TranslateTimeline (int keyframeCount) :
 				CurveTimeline(keyframeCount),
 				framesLength(keyframeCount * 3),
-				frames(new float[framesLength]) {
+				frames(new float[framesLength]),
+				boneIndex(0) {
 }
 
 TranslateTimeline::~TranslateTimeline () {
@@ -286,7 +302,8 @@ static const int COLOR_FRAME_A = 4;
 ColorTimeline::ColorTimeline (int keyframeCount) :
 				CurveTimeline(keyframeCount),
 				framesLength(keyframeCount * 5),
-				frames(new float[framesLength]) {
+				frames(new float[framesLength]),
+				slotIndex(0) {
 }
 
 ColorTimeline::~ColorTimeline () {
@@ -360,7 +377,8 @@ void ColorTimeline::apply (BaseSkeleton *skeleton, float time, float alpha) {
 AttachmentTimeline::AttachmentTimeline (int keyframeCount) :
 				framesLength(keyframeCount),
 				frames(new float[keyframeCount]),
-				attachmentNames(new string*[keyframeCount]) {
+				attachmentNames(new string*[keyframeCount]),
+				slotIndex(0) {
 }
 
 AttachmentTimeline::~AttachmentTimeline () {

+ 8 - 0
spine-cpp/src/spine/BaseAtlas.cpp

@@ -7,6 +7,7 @@
 
 using std::string;
 using std::runtime_error;
+using std::invalid_argument;
 
 namespace spine {
 
@@ -75,11 +76,15 @@ BaseAtlas::~BaseAtlas () {
 }
 
 void BaseAtlas::load (std::ifstream &file) {
+	if (!file) throw invalid_argument("file cannot be null.");
 	if (!file.is_open()) throw runtime_error("Atlas file is not open.");
+
 	load((std::istream&)file);
 }
 
 void BaseAtlas::load (std::istream &input) {
+	if (!input) throw invalid_argument("input cannot be null.");
+
 	string text;
 	std::getline(input, text, (char)EOF);
 	const char *begin = text.c_str();
@@ -94,6 +99,9 @@ void BaseAtlas::load (const string &text) {
 }
 
 void BaseAtlas::load (const char *current, const char *end) {
+	if (!current) throw invalid_argument("current cannot be null.");
+	if (!end) throw invalid_argument("end cannot be null.");
+
 	string value;
 	string tuple[4];
 	BaseAtlasPage *page;

+ 137 - 15
spine-cpp/src/spine/BaseSkeletonJson.cpp

@@ -4,17 +4,30 @@
 #include <json/json.h>
 #include <spine/BaseSkeletonJson.h>
 #include <spine/BaseAttachmentLoader.h>
+#include <spine/BaseRegionAttachment.h>
 #include <spine/SkeletonData.h>
 #include <spine/BoneData.h>
 #include <spine/SlotData.h>
 #include <spine/Skin.h>
+#include <spine/Animation.h>
 
 using std::string;
 using std::vector;
 using std::runtime_error;
+using std::invalid_argument;
 
 namespace spine {
 
+static float toColor (const string &value, int index) {
+	if (value.size() != 8) throw runtime_error("Error parsing color, length must be 8: " + value);
+	char *p;
+	int color = strtoul(value.substr(index * 2, 2).c_str(), &p, 16);
+	if (*p != 0) throw runtime_error("Error parsing color: " + value + ", invalid hex value: " + value.substr(index * 2, 2));
+	return color / (float)255;
+}
+
+//
+
 BaseSkeletonJson::BaseSkeletonJson (BaseAttachmentLoader *attachmentLoader) :
 				attachmentLoader(attachmentLoader),
 				scale(1) {
@@ -23,20 +36,16 @@ BaseSkeletonJson::BaseSkeletonJson (BaseAttachmentLoader *attachmentLoader) :
 BaseSkeletonJson::~BaseSkeletonJson () {
 }
 
-float toColor (const string &value, int index) {
-	if (value.size() != 8) throw runtime_error("Error parsing color, length must be 8: " + value);
-	char *p;
-	int color = strtoul(value.substr(index * 2, 2).c_str(), &p, 16);
-	if (*p != 0) throw runtime_error("Error parsing color: " + value + ", invalid hex value: " + value.substr(index * 2, 2));
-	return color / (float)255;
-}
-
 SkeletonData* BaseSkeletonJson::readSkeletonData (std::ifstream &file) const {
+	if (!file) throw invalid_argument("file cannot be null.");
 	if (!file.is_open()) throw runtime_error("Skeleton file is not open.");
+
 	return readSkeletonData((std::istream&)file);
 }
 
 SkeletonData* BaseSkeletonJson::readSkeletonData (std::istream &input) const {
+	if (!input) throw invalid_argument("input cannot be null.");
+
 	string json;
 	std::getline(input, json, (char)EOF);
 	return readSkeletonData(json);
@@ -49,6 +58,9 @@ SkeletonData* BaseSkeletonJson::readSkeletonData (const string &json) const {
 }
 
 SkeletonData* BaseSkeletonJson::readSkeletonData (const char *begin, const char *end) const {
+	if (!begin) throw invalid_argument("begin cannot be null.");
+	if (!end) throw invalid_argument("end cannot be null.");
+
 	static string const ATTACHMENT_REGION = "region";
 	static string const ATTACHMENT_REGION_SEQUENCE = "regionSequence";
 
@@ -140,13 +152,17 @@ SkeletonData* BaseSkeletonJson::readSkeletonData (const char *begin, const char
 
 					Attachment* attachment = attachmentLoader->newAttachment(type,
 							attachmentMap.get("name", attachmentName).asString());
-					attachment->x = attachmentMap.get("x", 0).asDouble() * scale;
-					attachment->y = attachmentMap.get("y", 0).asDouble() * scale;
-					attachment->scaleX = attachmentMap.get("scaleX", 1).asDouble();
-					attachment->scaleY = attachmentMap.get("scaleY", 1).asDouble();
-					attachment->rotation = attachmentMap.get("rotation", 0).asDouble();
-					attachment->width = attachmentMap.get("width", 32).asDouble() * scale;
-					attachment->height = attachmentMap.get("height", 32).asDouble() * scale;
+
+					if (type == region || type == regionSequence) {
+						BaseRegionAttachment *regionAttachment = reinterpret_cast<BaseRegionAttachment*>(attachment);
+						regionAttachment->x = attachmentMap.get("x", 0).asDouble() * scale;
+						regionAttachment->y = attachmentMap.get("y", 0).asDouble() * scale;
+						regionAttachment->scaleX = attachmentMap.get("scaleX", 1).asDouble();
+						regionAttachment->scaleY = attachmentMap.get("scaleY", 1).asDouble();
+						regionAttachment->rotation = attachmentMap.get("rotation", 0).asDouble();
+						regionAttachment->width = attachmentMap.get("width", 32).asDouble() * scale;
+						regionAttachment->height = attachmentMap.get("height", 32).asDouble() * scale;
+					}
 
 					skin->addAttachment(slotIndex, attachmentName, attachment);
 				}
@@ -157,4 +173,110 @@ SkeletonData* BaseSkeletonJson::readSkeletonData (const char *begin, const char
 	return skeletonData;
 }
 
+Animation* BaseSkeletonJson::readAnimation (std::ifstream &file, const SkeletonData *skeletonData) const {
+	if (!file) throw invalid_argument("file cannot be null.");
+	if (!file.is_open()) throw runtime_error("Animation file is not open.");
+
+	return readAnimation((std::istream&)file, skeletonData);
+}
+
+Animation* BaseSkeletonJson::readAnimation (std::istream &input, const SkeletonData *skeletonData) const {
+	if (!input) throw invalid_argument("input cannot be null.");
+
+	string json;
+	std::getline(input, json, (char)EOF);
+	return readAnimation(json, skeletonData);
+}
+
+Animation* BaseSkeletonJson::readAnimation (const string &json, const SkeletonData *skeletonData) const {
+	const char *begin = json.c_str();
+	const char *end = begin + json.length();
+	return readAnimation(begin, end, skeletonData);
+}
+
+Animation* BaseSkeletonJson::readAnimation (const char *begin, const char *end, const SkeletonData *skeletonData) const {
+	if (!begin) throw invalid_argument("begin cannot be null.");
+	if (!end) throw invalid_argument("end cannot be null.");
+	if (!skeletonData) throw invalid_argument("skeletonData cannot be null.");
+
+	static string const TIMELINE_SCALE = "scale";
+	static string const TIMELINE_ROTATE = "rotate";
+	static string const TIMELINE_TRANSLATE = "translate";
+	static string const TIMELINE_ATTACHMENT = "attachment";
+	static string const TIMELINE_COLOR = "color";
+
+	vector<Timeline*> timelines;
+	float duration = 0;
+
+	Json::Value root;
+	Json::Reader reader;
+	if (!reader.parse(begin, end, root))
+		throw runtime_error("Error parsing animation JSON.\n" + reader.getFormatedErrorMessages());
+
+	Json::Value bones = root["bones"];
+	vector<string> boneNames = bones.getMemberNames();
+	for (int i = 0; i < boneNames.size(); i++) {
+		string boneName = boneNames[i];
+		int boneIndex = skeletonData->findBoneIndex(boneName);
+		if (boneIndex == -1) throw runtime_error("Bone not found: " + boneName);
+
+		Json::Value timelineMap = bones[boneName];
+		vector<string> timelineNames = timelineMap.getMemberNames();
+		for (int i = 0; i < timelineNames.size(); i++) {
+			string timelineName = timelineNames[i];
+			Json::Value values = timelineMap[timelineName];
+
+			if (timelineName == TIMELINE_ROTATE) {
+				RotateTimeline *timeline = new RotateTimeline(values.size());
+				timeline->boneIndex = boneIndex;
+
+				int keyframeIndex = 0;
+				for (int i = 0; i < values.size(); i++) {
+					Json::Value valueMap = values[i];
+
+					float time = valueMap["time"].asDouble();
+					timeline->setKeyframe(keyframeIndex, time, valueMap["angle"].asDouble());
+					// BOZO
+					// readCurve(timeline, keyframeIndex, valueMap);
+					keyframeIndex++;
+				}
+				timelines.push_back(timeline);
+				if (timeline->getDuration() > duration) duration = timeline->getDuration();
+
+			} else if (timelineName == TIMELINE_TRANSLATE || timelineName == TIMELINE_SCALE) {
+				TranslateTimeline *timeline;
+				float timelineScale = 1;
+				if (timelineName == TIMELINE_SCALE)
+					timeline = new ScaleTimeline(values.size());
+				else {
+					timeline = new TranslateTimeline(values.size());
+					timelineScale = scale;
+				}
+				timeline->boneIndex = boneIndex;
+
+				int keyframeIndex = 0;
+				for (int i = 0; i < values.size(); i++) {
+					Json::Value valueMap = values[i];
+
+					float time = valueMap["time"].asDouble();
+					timeline->setKeyframe(keyframeIndex, //
+							valueMap["time"].asDouble(), //
+							valueMap.get("x", 0).asDouble() * timelineScale, //
+							valueMap.get("y", 0).asDouble() * timelineScale);
+					// readCurve(timeline, keyframeIndex, valueMap);
+					keyframeIndex++;
+				}
+				timelines.push_back(timeline);
+				if (timeline->getDuration() > duration) duration = timeline->getDuration();
+
+			} else {
+				throw runtime_error("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")");
+			}
+		}
+	}
+
+	Animation *animation = new Animation(timelines, duration);
+	return animation;
+}
+
 } /* namespace spine */

+ 2 - 1
spine-cpp/src/spine/Slot.cpp

@@ -12,7 +12,8 @@ Slot::Slot (SlotData *data, BaseSkeleton *skeleton, Bone *bone) :
 				g(1),
 				b(1),
 				a(1),
-				attachment(0) {
+				attachment(0),
+				attachmentTime(0) {
 	if (!data) throw std::invalid_argument("data cannot be null.");
 	if (!skeleton) throw std::invalid_argument("skeleton cannot be null.");
 	if (!bone) throw std::invalid_argument("bone cannot be null.");

+ 6 - 6
spine-libgdx/src/com/esotericsoftware/spine/Animation.java

@@ -35,9 +35,9 @@ public class Animation {
 
 		if (loop && duration != 0) time %= duration;
 
-		Array<Timeline> timeline = this.timelines;
-		for (int i = 0, n = timeline.size; i < n; i++)
-			timeline.get(i).apply(skeleton, time, 1);
+		Array<Timeline> timelines = this.timelines;
+		for (int i = 0, n = timelines.size; i < n; i++)
+			timelines.get(i).apply(skeleton, time, 1);
 	}
 
 	/** Poses the skeleton at the specified time for this animation mixed with the current pose.
@@ -47,9 +47,9 @@ public class Animation {
 
 		if (loop && duration != 0) time %= duration;
 
-		Array<Timeline> timeline = this.timelines;
-		for (int i = 0, n = timeline.size; i < n; i++)
-			timeline.get(i).apply(skeleton, time, alpha);
+		Array<Timeline> timelines = this.timelines;
+		for (int i = 0, n = timelines.size; i < n; i++)
+			timelines.get(i).apply(skeleton, time, alpha);
 	}
 
 	/** @param target After the first and before the last entry. */

+ 0 - 57
spine-libgdx/src/com/esotericsoftware/spine/Attachment.java

@@ -6,7 +6,6 @@ import com.badlogic.gdx.graphics.g2d.SpriteBatch;
 abstract public class Attachment {
 	final String name;
 	boolean resolved;
-	private float x, y, scaleX, scaleY, rotation, width, height;
 
 	public Attachment (String name) {
 		if (name == null) throw new IllegalArgumentException("name cannot be null.");
@@ -17,62 +16,6 @@ abstract public class Attachment {
 
 	abstract public void draw (SpriteBatch batch, Slot slot);
 
-	public float getX () {
-		return x;
-	}
-
-	public void setX (float x) {
-		this.x = x;
-	}
-
-	public float getY () {
-		return y;
-	}
-
-	public void setY (float y) {
-		this.y = y;
-	}
-
-	public float getScaleX () {
-		return scaleX;
-	}
-
-	public void setScaleX (float scaleX) {
-		this.scaleX = scaleX;
-	}
-
-	public float getScaleY () {
-		return scaleY;
-	}
-
-	public void setScaleY (float scaleY) {
-		this.scaleY = scaleY;
-	}
-
-	public float getRotation () {
-		return rotation;
-	}
-
-	public void setRotation (float rotation) {
-		this.rotation = rotation;
-	}
-
-	public float getWidth () {
-		return width;
-	}
-
-	public void setWidth (float width) {
-		this.width = width;
-	}
-
-	public float getHeight () {
-		return height;
-	}
-
-	public void setHeight (float height) {
-		this.height = height;
-	}
-
 	public boolean isResolved () {
 		return resolved;
 	}

+ 11 - 7
spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java

@@ -149,13 +149,17 @@ public class SkeletonBinary {
 			throw new SerializationException("Unknown attachment type: " + type + " (" + name + ")");
 		}
 
-		attachment.setX(input.readFloat() * scale);
-		attachment.setY(input.readFloat() * scale);
-		attachment.setScaleX(input.readFloat());
-		attachment.setScaleY(input.readFloat());
-		attachment.setRotation(input.readFloat());
-		attachment.setWidth(input.readFloat() * scale);
-		attachment.setHeight(input.readFloat() * scale);
+		if (attachment instanceof RegionAttachment) {
+			RegionAttachment regionAttachment = (RegionAttachment)attachment;
+			regionAttachment.setX(input.readFloat() * scale);
+			regionAttachment.setY(input.readFloat() * scale);
+			regionAttachment.setScaleX(input.readFloat());
+			regionAttachment.setScaleY(input.readFloat());
+			regionAttachment.setRotation(input.readFloat());
+			regionAttachment.setWidth(input.readFloat() * scale);
+			regionAttachment.setHeight(input.readFloat() * scale);
+		}
+		
 		return attachment;
 	}
 

+ 19 - 15
spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java

@@ -140,13 +140,17 @@ public class SkeletonJson {
 		} else
 			throw new SerializationException("Unknown attachment type: " + type + " (" + name + ")");
 
-		attachment.setX(getFloat(map, "x", 0) * scale);
-		attachment.setY(getFloat(map, "y", 0) * scale);
-		attachment.setScaleX(getFloat(map, "scaleX", 1));
-		attachment.setScaleY(getFloat(map, "scaleY", 1));
-		attachment.setRotation(getFloat(map, "rotation", 0));
-		attachment.setWidth(getFloat(map, "width", 32) * scale);
-		attachment.setHeight(getFloat(map, "height", 32) * scale);
+		if (attachment instanceof RegionAttachment) {
+			RegionAttachment regionAttachment = (RegionAttachment)attachment;
+			regionAttachment.setX(getFloat(map, "x", 0) * scale);
+			regionAttachment.setY(getFloat(map, "y", 0) * scale);
+			regionAttachment.setScaleX(getFloat(map, "scaleX", 1));
+			regionAttachment.setScaleY(getFloat(map, "scaleY", 1));
+			regionAttachment.setRotation(getFloat(map, "rotation", 0));
+			regionAttachment.setWidth(getFloat(map, "width", 32) * scale);
+			regionAttachment.setHeight(getFloat(map, "height", 32) * scale);
+		}
+
 		return attachment;
 	}
 
@@ -170,12 +174,12 @@ public class SkeletonJson {
 			String boneName = entry.key;
 			int boneIndex = skeletonData.findBoneIndex(boneName);
 			if (boneIndex == -1) throw new SerializationException("Bone not found: " + boneName);
-			OrderedMap<?, ?> propertyMap = (OrderedMap)entry.value;
 
-			for (Entry propertyEntry : propertyMap.entries()) {
-				Array<OrderedMap> values = (Array)propertyEntry.value;
-				String timelineType = (String)propertyEntry.key;
-				if (timelineType.equals(TIMELINE_ROTATE)) {
+			OrderedMap<?, ?> timelineMap = (OrderedMap)entry.value;
+			for (Entry timelineEntry : timelineMap.entries()) {
+				Array<OrderedMap> values = (Array)timelineEntry.value;
+				String timelineName = (String)timelineEntry.key;
+				if (timelineName.equals(TIMELINE_ROTATE)) {
 					RotateTimeline timeline = new RotateTimeline(values.size);
 					timeline.setBoneIndex(boneIndex);
 
@@ -189,10 +193,10 @@ public class SkeletonJson {
 					timelines.add(timeline);
 					duration = Math.max(duration, timeline.getDuration());
 
-				} else if (timelineType.equals(TIMELINE_TRANSLATE) || timelineType.equals(TIMELINE_SCALE)) {
+				} else if (timelineName.equals(TIMELINE_TRANSLATE) || timelineName.equals(TIMELINE_SCALE)) {
 					TranslateTimeline timeline;
 					float timelineScale = 1;
-					if (timelineType.equals(TIMELINE_SCALE))
+					if (timelineName.equals(TIMELINE_SCALE))
 						timeline = new ScaleTimeline(values.size);
 					else {
 						timeline = new TranslateTimeline(values.size);
@@ -213,7 +217,7 @@ public class SkeletonJson {
 					duration = Math.max(duration, timeline.getDuration());
 
 				} else
-					throw new RuntimeException("Invalid timeline type for a bone: " + timelineType + " (" + boneName + ")");
+					throw new RuntimeException("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")");
 			}
 		}
 

+ 57 - 0
spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionAttachment.java

@@ -17,6 +17,7 @@ import com.badlogic.gdx.utils.NumberUtils;
 /** Attachment that displays a texture region. */
 public class RegionAttachment extends Attachment {
 	private TextureRegion region;
+	private float x, y, scaleX, scaleY, rotation, width, height;
 	private final float[] vertices = new float[20];
 	private final float[] offset = new float[8];
 
@@ -150,4 +151,60 @@ public class RegionAttachment extends Attachment {
 	public float[] getWorldVertices () {
 		return vertices;
 	}
+
+	public float getX () {
+		return x;
+	}
+
+	public void setX (float x) {
+		this.x = x;
+	}
+
+	public float getY () {
+		return y;
+	}
+
+	public void setY (float y) {
+		this.y = y;
+	}
+
+	public float getScaleX () {
+		return scaleX;
+	}
+
+	public void setScaleX (float scaleX) {
+		this.scaleX = scaleX;
+	}
+
+	public float getScaleY () {
+		return scaleY;
+	}
+
+	public void setScaleY (float scaleY) {
+		this.scaleY = scaleY;
+	}
+
+	public float getRotation () {
+		return rotation;
+	}
+
+	public void setRotation (float rotation) {
+		this.rotation = rotation;
+	}
+
+	public float getWidth () {
+		return width;
+	}
+
+	public void setWidth (float width) {
+		this.width = width;
+	}
+
+	public float getHeight () {
+		return height;
+	}
+
+	public void setHeight (float height) {
+		this.height = height;
+	}
 }