NathanSweet 11 سال پیش
والد
کامیت
9f19d9af39

+ 116 - 33
spine-libgdx/src/com/esotericsoftware/spine/Animation.java

@@ -28,9 +28,12 @@
 
 package com.esotericsoftware.spine;
 
+import com.esotericsoftware.spine.attachments.MeshAttachment;
+
 import com.badlogic.gdx.graphics.Color;
 import com.badlogic.gdx.math.MathUtils;
 import com.badlogic.gdx.utils.Array;
+import com.badlogic.gdx.utils.FloatArray;
 
 public class Animation {
 	final String name;
@@ -201,9 +204,9 @@ public class Animation {
 			int i = BEZIER_SEGMENTS - 2;
 			while (true) {
 				if (x >= percent) {
-					float lastX = x - dfx;
-					float lastY = y - dfy;
-					return lastY + (y - lastY) * (percent - lastX) / (x - lastX);
+					float prevX = x - dfx;
+					float prevY = y - dfy;
+					return prevY + (y - prevY) * (percent - prevX) / (x - prevX);
 				}
 				if (i == 0) break;
 				i--;
@@ -219,7 +222,7 @@ public class Animation {
 	}
 
 	static public class RotateTimeline extends CurveTimeline {
-		static private final int LAST_FRAME_TIME = -2;
+		static private final int PREV_FRAME_TIME = -2;
 		static private final int FRAME_VALUE = 1;
 
 		int boneIndex;
@@ -265,19 +268,19 @@ public class Animation {
 				return;
 			}
 
-			// Interpolate between the last frame and the current frame.
+			// Interpolate between the previous frame and the current frame.
 			int frameIndex = binarySearch(frames, time, 2);
-			float lastFrameValue = frames[frameIndex - 1];
+			float prevFrameValue = frames[frameIndex - 1];
 			float frameTime = frames[frameIndex];
-			float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime), 0, 1);
+			float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime), 0, 1);
 			percent = getCurvePercent(frameIndex / 2 - 1, percent);
 
-			float amount = frames[frameIndex + FRAME_VALUE] - lastFrameValue;
+			float amount = frames[frameIndex + FRAME_VALUE] - prevFrameValue;
 			while (amount > 180)
 				amount -= 360;
 			while (amount < -180)
 				amount += 360;
-			amount = bone.data.rotation + (lastFrameValue + amount * percent) - bone.rotation;
+			amount = bone.data.rotation + (prevFrameValue + amount * percent) - bone.rotation;
 			while (amount > 180)
 				amount -= 360;
 			while (amount < -180)
@@ -287,7 +290,7 @@ public class Animation {
 	}
 
 	static public class TranslateTimeline extends CurveTimeline {
-		static final int LAST_FRAME_TIME = -3;
+		static final int PREV_FRAME_TIME = -3;
 		static final int FRAME_X = 1;
 		static final int FRAME_Y = 2;
 
@@ -331,16 +334,16 @@ public class Animation {
 				return;
 			}
 
-			// Interpolate between the last frame and the current frame.
+			// Interpolate between the previous frame and the current frame.
 			int frameIndex = binarySearch(frames, time, 3);
-			float lastFrameX = frames[frameIndex - 2];
-			float lastFrameY = frames[frameIndex - 1];
+			float prevFrameX = frames[frameIndex - 2];
+			float prevFrameY = frames[frameIndex - 1];
 			float frameTime = frames[frameIndex];
-			float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime), 0, 1);
+			float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime), 0, 1);
 			percent = getCurvePercent(frameIndex / 3 - 1, percent);
 
-			bone.x += (bone.data.x + lastFrameX + (frames[frameIndex + FRAME_X] - lastFrameX) * percent - bone.x) * alpha;
-			bone.y += (bone.data.y + lastFrameY + (frames[frameIndex + FRAME_Y] - lastFrameY) * percent - bone.y) * alpha;
+			bone.x += (bone.data.x + prevFrameX + (frames[frameIndex + FRAME_X] - prevFrameX) * percent - bone.x) * alpha;
+			bone.y += (bone.data.y + prevFrameY + (frames[frameIndex + FRAME_Y] - prevFrameY) * percent - bone.y) * alpha;
 		}
 	}
 
@@ -360,23 +363,23 @@ public class Animation {
 				return;
 			}
 
-			// Interpolate between the last frame and the current frame.
+			// Interpolate between the previous frame and the current frame.
 			int frameIndex = binarySearch(frames, time, 3);
-			float lastFrameX = frames[frameIndex - 2];
-			float lastFrameY = frames[frameIndex - 1];
+			float prevFrameX = frames[frameIndex - 2];
+			float prevFrameY = frames[frameIndex - 1];
 			float frameTime = frames[frameIndex];
-			float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime), 0, 1);
+			float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime), 0, 1);
 			percent = getCurvePercent(frameIndex / 3 - 1, percent);
 
-			bone.scaleX += (bone.data.scaleX - 1 + lastFrameX + (frames[frameIndex + FRAME_X] - lastFrameX) * percent - bone.scaleX)
+			bone.scaleX += (bone.data.scaleX - 1 + prevFrameX + (frames[frameIndex + FRAME_X] - prevFrameX) * percent - bone.scaleX)
 				* alpha;
-			bone.scaleY += (bone.data.scaleY - 1 + lastFrameY + (frames[frameIndex + FRAME_Y] - lastFrameY) * percent - bone.scaleY)
+			bone.scaleY += (bone.data.scaleY - 1 + prevFrameY + (frames[frameIndex + FRAME_Y] - prevFrameY) * percent - bone.scaleY)
 				* alpha;
 		}
 	}
 
 	static public class ColorTimeline extends CurveTimeline {
-		static private final int LAST_FRAME_TIME = -5;
+		static private final int PREV_FRAME_TIME = -5;
 		static private final int FRAME_R = 1;
 		static private final int FRAME_G = 2;
 		static private final int FRAME_B = 3;
@@ -428,20 +431,20 @@ public class Animation {
 				return;
 			}
 
-			// Interpolate between the last frame and the current frame.
+			// Interpolate between the previous frame and the current frame.
 			int frameIndex = binarySearch(frames, time, 5);
-			float lastFrameR = frames[frameIndex - 4];
-			float lastFrameG = frames[frameIndex - 3];
-			float lastFrameB = frames[frameIndex - 2];
-			float lastFrameA = frames[frameIndex - 1];
+			float prevFrameR = frames[frameIndex - 4];
+			float prevFrameG = frames[frameIndex - 3];
+			float prevFrameB = frames[frameIndex - 2];
+			float prevFrameA = frames[frameIndex - 1];
 			float frameTime = frames[frameIndex];
-			float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime), 0, 1);
+			float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex + PREV_FRAME_TIME] - frameTime), 0, 1);
 			percent = getCurvePercent(frameIndex / 5 - 1, percent);
 
-			float r = lastFrameR + (frames[frameIndex + FRAME_R] - lastFrameR) * percent;
-			float g = lastFrameG + (frames[frameIndex + FRAME_G] - lastFrameG) * percent;
-			float b = lastFrameB + (frames[frameIndex + FRAME_B] - lastFrameB) * percent;
-			float a = lastFrameA + (frames[frameIndex + FRAME_A] - lastFrameA) * percent;
+			float r = prevFrameR + (frames[frameIndex + FRAME_R] - prevFrameR) * percent;
+			float g = prevFrameG + (frames[frameIndex + FRAME_G] - prevFrameG) * percent;
+			float b = prevFrameB + (frames[frameIndex + FRAME_B] - prevFrameB) * percent;
+			float a = prevFrameA + (frames[frameIndex + FRAME_A] - prevFrameA) * percent;
 			if (alpha < 1)
 				color.add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha);
 			else
@@ -606,4 +609,84 @@ public class Animation {
 			}
 		}
 	}
+
+	static public class FfdTimeline extends CurveTimeline {
+		private final float[] frames; // time, ...
+		private final float[][] frameVertices;
+		int slotIndex;
+		MeshAttachment meshAttachment;
+
+		public FfdTimeline (int frameCount) {
+			super(frameCount);
+			frames = new float[frameCount];
+			frameVertices = new float[frameCount][];
+		}
+
+		public void setSlotIndex (int slotIndex) {
+			this.slotIndex = slotIndex;
+		}
+
+		public int getSlotIndex () {
+			return slotIndex;
+		}
+
+		public void setMeshAttachment (MeshAttachment attachment) {
+			this.meshAttachment = attachment;
+		}
+
+		public MeshAttachment getMeshAttachment () {
+			return meshAttachment;
+		}
+
+		public float[] getFrames () {
+			return frames;
+		}
+
+		public float[][] getVertices () {
+			return frameVertices;
+		}
+
+		/** Sets the time of the specified keyframe. */
+		public void setFrame (int frameIndex, float time, float[] vertices) {
+			frames[frameIndex] = time;
+			frameVertices[frameIndex] = vertices;
+		}
+
+		public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> firedEvents, float alpha) {
+			Slot slot = skeleton.slots.get(slotIndex);
+			if (slot.getAttachment() != meshAttachment) return;
+
+			FloatArray verticesArray = slot.getAttachmentVertices();
+			verticesArray.size = 0;
+
+			float[] frames = this.frames;
+			if (time < frames[0]) return; // Time is before first frame.
+
+			float[][] frameVertices = this.frameVertices;
+			int vertexCount = frameVertices[0].length;
+			verticesArray.ensureCapacity(vertexCount);
+			verticesArray.size = vertexCount;
+			float[] vertices = verticesArray.items;
+
+			if (time >= frames[frames.length - 1]) { // Time is after last frame.
+				System.arraycopy(frameVertices[frames.length - 1], 0, vertices, 0, vertexCount);
+				return;
+			}
+
+			// Interpolate between the previous frame and the current frame.
+			int frameIndex = binarySearch(frames, time, 1);
+			float frameTime = frames[frameIndex];
+			float percent = MathUtils.clamp(1 - (time - frameTime) / (frames[frameIndex - 1] - frameTime), 0, 1);
+			percent = getCurvePercent(frameIndex - 1, percent);
+
+			float[] prevVertices = frameVertices[frameIndex - 1];
+			float[] nextVertices = frameVertices[frameIndex];
+
+			// BOZO - FFD, use alpha for mixing?
+			for (int i = 0; i < vertexCount; i++) {
+				float prev = prevVertices[i];
+				vertices[i] = prev + (nextVertices[i] - prev) * percent;
+			}
+		}
+	}
 }

+ 9 - 0
spine-libgdx/src/com/esotericsoftware/spine/BoneData.java

@@ -28,6 +28,8 @@
 
 package com.esotericsoftware.spine;
 
+import com.badlogic.gdx.graphics.Color;
+
 public class BoneData {
 	final BoneData parent;
 	final String name;
@@ -37,6 +39,9 @@ public class BoneData {
 	float scaleX = 1, scaleY = 1;
 	boolean inheritScale = true, inheritRotation = true;
 
+	// Nonessential.
+	final Color color = new Color(1, 1, 1, 1);
+
 	/** @param parent May be null. */
 	public BoneData (String name, BoneData parent) {
 		if (name == null) throw new IllegalArgumentException("name cannot be null.");
@@ -131,6 +136,10 @@ public class BoneData {
 		this.inheritRotation = inheritRotation;
 	}
 
+	public Color getColor () {
+		return color;
+	}
+
 	public String toString () {
 		return name;
 	}

+ 74 - 32
spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java

@@ -33,6 +33,7 @@ import com.esotericsoftware.spine.Animation.ColorTimeline;
 import com.esotericsoftware.spine.Animation.CurveTimeline;
 import com.esotericsoftware.spine.Animation.DrawOrderTimeline;
 import com.esotericsoftware.spine.Animation.EventTimeline;
+import com.esotericsoftware.spine.Animation.FfdTimeline;
 import com.esotericsoftware.spine.Animation.RotateTimeline;
 import com.esotericsoftware.spine.Animation.ScaleTimeline;
 import com.esotericsoftware.spine.Animation.Timeline;
@@ -62,6 +63,7 @@ public class SkeletonBinary {
 	static public final int TIMELINE_COLOR = 4;
 	static public final int TIMELINE_EVENT = 5;
 	static public final int TIMELINE_DRAWORDER = 6;
+	static public final int TIMELINE_FFD = 7;
 
 	static public final int CURVE_LINEAR = 0;
 	static public final int CURVE_STEPPED = 1;
@@ -92,11 +94,14 @@ public class SkeletonBinary {
 	public SkeletonData readSkeletonData (FileHandle file) {
 		if (file == null) throw new IllegalArgumentException("file cannot be null.");
 
+		float scale = this.scale;
+
 		SkeletonData skeletonData = new SkeletonData();
 		skeletonData.name = file.nameWithoutExtension();
 
 		DataInput input = new DataInput(file.read(512));
 		try {
+			boolean nonessential = input.readBoolean();
 			// Bones.
 			for (int i = 0, n = input.readInt(true); i < n; i++) {
 				String name = input.readString();
@@ -110,8 +115,9 @@ public class SkeletonBinary {
 				boneData.scaleY = input.readFloat();
 				boneData.rotation = input.readFloat();
 				boneData.length = input.readFloat() * scale;
-				boneData.inheritScale = input.readByte() == 1;
-				boneData.inheritRotation = input.readByte() == 1;
+				boneData.inheritScale = input.readBoolean();
+				boneData.inheritRotation = input.readBoolean();
+				if (nonessential) Color.rgba8888ToColor(boneData.getColor(), input.readInt());
 				skeletonData.addBone(boneData);
 			}
 
@@ -122,12 +128,12 @@ public class SkeletonBinary {
 				SlotData slotData = new SlotData(slotName, boneData);
 				Color.rgba8888ToColor(slotData.getColor(), input.readInt());
 				slotData.attachmentName = input.readString();
-				slotData.additiveBlending = input.readByte() == 1;
+				slotData.additiveBlending = input.readBoolean();
 				skeletonData.addSlot(slotData);
 			}
 
 			// Default skin.
-			Skin defaultSkin = readSkin(input, "default");
+			Skin defaultSkin = readSkin(input, "default", nonessential);
 			if (defaultSkin != null) {
 				skeletonData.defaultSkin = defaultSkin;
 				skeletonData.addSkin(defaultSkin);
@@ -135,7 +141,7 @@ public class SkeletonBinary {
 
 			// Skins.
 			for (int i = 0, n = input.readInt(true); i < n; i++)
-				skeletonData.addSkin(readSkin(input, input.readString()));
+				skeletonData.addSkin(readSkin(input, input.readString(), nonessential));
 
 			// Events.
 			for (int i = 0, n = input.readInt(true); i < n; i++) {
@@ -165,7 +171,7 @@ public class SkeletonBinary {
 		return skeletonData;
 	}
 
-	private Skin readSkin (DataInput input, String skinName) throws IOException {
+	private Skin readSkin (DataInput input, String skinName, boolean nonessential) throws IOException {
 		int slotCount = input.readInt(true);
 		if (slotCount == 0) return null;
 		Skin skin = new Skin(skinName);
@@ -173,13 +179,15 @@ public class SkeletonBinary {
 			int slotIndex = input.readInt(true);
 			for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) {
 				String name = input.readString();
-				skin.addAttachment(slotIndex, name, readAttachment(input, skin, name));
+				skin.addAttachment(slotIndex, name, readAttachment(input, skin, name, nonessential));
 			}
 		}
 		return skin;
 	}
 
-	private Attachment readAttachment (DataInput input, Skin skin, String attachmentName) throws IOException {
+	private Attachment readAttachment (DataInput input, Skin skin, String attachmentName, boolean nonessential) throws IOException {
+		float scale = this.scale;
+
 		String name = input.readString();
 		if (name == null) name = attachmentName;
 
@@ -214,8 +222,8 @@ public class SkeletonBinary {
 			short[] triangles = readShortArray(input);
 			float[] uvs = readFloatArray(input, 1);
 			Color.rgba8888ToColor(mesh.getColor(), input.readInt());
-			mesh.setEdges(readIntArray(input));
-			if (mesh.getEdges().length > 0) {
+			if (nonessential) {
+				mesh.setEdges(readIntArray(input));
 				mesh.setHullLength(input.readInt(true));
 				mesh.setWidth(input.readFloat() * scale);
 				mesh.setHeight(input.readFloat() * scale);
@@ -253,25 +261,26 @@ public class SkeletonBinary {
 
 	private void readAnimation (String name, DataInput input, SkeletonData skeletonData) {
 		Array<Timeline> timelines = new Array();
+		float scale = this.scale;
 		float duration = 0;
 
 		try {
+			// SRT.
 			for (int i = 0, n = input.readInt(true); i < n; i++) {
 				int boneIndex = input.readInt(true);
-				int itemCount = input.readInt(true);
-				for (int ii = 0; ii < itemCount; ii++) {
+				for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) {
 					int timelineType = input.readByte();
-					int keyCount = input.readInt(true);
+					int frameCount = input.readInt(true);
 					switch (timelineType) {
 					case TIMELINE_ROTATE: {
-						RotateTimeline timeline = new RotateTimeline(keyCount);
+						RotateTimeline timeline = new RotateTimeline(frameCount);
 						timeline.boneIndex = boneIndex;
-						for (int frameIndex = 0; frameIndex < keyCount; frameIndex++) {
+						for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
 							timeline.setFrame(frameIndex, input.readFloat(), input.readFloat());
-							if (frameIndex < keyCount - 1) readCurve(input, frameIndex, timeline);
+							if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
 						}
 						timelines.add(timeline);
-						duration = Math.max(duration, timeline.getFrames()[keyCount * 2 - 2]);
+						duration = Math.max(duration, timeline.getFrames()[frameCount * 2 - 2]);
 						break;
 					}
 					case TIMELINE_TRANSLATE:
@@ -279,56 +288,88 @@ public class SkeletonBinary {
 						TranslateTimeline timeline;
 						float timelineScale = 1;
 						if (timelineType == TIMELINE_SCALE)
-							timeline = new ScaleTimeline(keyCount);
+							timeline = new ScaleTimeline(frameCount);
 						else {
-							timeline = new TranslateTimeline(keyCount);
+							timeline = new TranslateTimeline(frameCount);
 							timelineScale = scale;
 						}
 						timeline.boneIndex = boneIndex;
-						for (int frameIndex = 0; frameIndex < keyCount; frameIndex++) {
+						for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
 							timeline.setFrame(frameIndex, input.readFloat(), input.readFloat() * timelineScale, input.readFloat()
 								* timelineScale);
-							if (frameIndex < keyCount - 1) readCurve(input, frameIndex, timeline);
+							if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
 						}
 						timelines.add(timeline);
-						duration = Math.max(duration, timeline.getFrames()[keyCount * 3 - 3]);
+						duration = Math.max(duration, timeline.getFrames()[frameCount * 3 - 3]);
 						break;
 					}
 				}
 			}
 
+			// FFD.
+			for (int i = 0, n = input.readInt(true); i < n; i++) {
+				Skin skin = skeletonData.getSkins().get(input.readInt(true) + 1);
+				for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) {
+					int slotIndex = input.readInt(true);
+					for (int iii = 0, nnn = input.readInt(true); iii < nnn; iii++) {
+						MeshAttachment mesh = (MeshAttachment)skin.getAttachment(slotIndex, input.readString());
+						int frameCount = input.readInt(true);
+						FfdTimeline timeline = new FfdTimeline(frameCount);
+						timeline.slotIndex = slotIndex;
+						timeline.meshAttachment = mesh;
+						for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
+							float time = input.readFloat();
+							float[] vertices;
+							int vertexCount = input.readInt(true);
+							if (vertexCount == 0)
+								vertices = mesh.getVertices();
+							else {
+								vertices = new float[vertexCount];
+								for (int vertex = 0; vertex < vertexCount; vertex++)
+									vertices[vertex] = input.readFloat() * scale;
+							}
+							timeline.setFrame(frameIndex, time, vertices);
+							if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
+						}
+						timelines.add(timeline);
+						duration = Math.max(duration, timeline.getFrames()[frameCount - 1]);
+					}
+				}
+			}
+
+			// Color, attachment.
 			for (int i = 0, n = input.readInt(true); i < n; i++) {
 				int slotIndex = input.readInt(true);
-				int itemCount = input.readInt(true);
-				for (int ii = 0; ii < itemCount; ii++) {
+				for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) {
 					int timelineType = input.readByte();
-					int keyCount = input.readInt(true);
+					int frameCount = input.readInt(true);
 					switch (timelineType) {
 					case TIMELINE_COLOR: {
-						ColorTimeline timeline = new ColorTimeline(keyCount);
+						ColorTimeline timeline = new ColorTimeline(frameCount);
 						timeline.slotIndex = slotIndex;
-						for (int frameIndex = 0; frameIndex < keyCount; frameIndex++) {
+						for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
 							float time = input.readFloat();
 							Color.rgba8888ToColor(tempColor, input.readInt());
 							timeline.setFrame(frameIndex, time, tempColor.r, tempColor.g, tempColor.b, tempColor.a);
-							if (frameIndex < keyCount - 1) readCurve(input, frameIndex, timeline);
+							if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
 						}
 						timelines.add(timeline);
-						duration = Math.max(duration, timeline.getFrames()[keyCount * 5 - 5]);
+						duration = Math.max(duration, timeline.getFrames()[frameCount * 5 - 5]);
 						break;
 					}
 					case TIMELINE_ATTACHMENT:
-						AttachmentTimeline timeline = new AttachmentTimeline(keyCount);
+						AttachmentTimeline timeline = new AttachmentTimeline(frameCount);
 						timeline.slotIndex = slotIndex;
-						for (int frameIndex = 0; frameIndex < keyCount; frameIndex++)
+						for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
 							timeline.setFrame(frameIndex, input.readFloat(), input.readString());
 						timelines.add(timeline);
-						duration = Math.max(duration, timeline.getFrames()[keyCount - 1]);
+						duration = Math.max(duration, timeline.getFrames()[frameCount - 1]);
 						break;
 					}
 				}
 			}
 
+			// Events.
 			int eventCount = input.readInt(true);
 			if (eventCount > 0) {
 				EventTimeline timeline = new EventTimeline(eventCount);
@@ -345,6 +386,7 @@ public class SkeletonBinary {
 				duration = Math.max(duration, timeline.getFrames()[eventCount - 1]);
 			}
 
+			// Draw order.
 			int drawOrderCount = input.readInt(true);
 			if (drawOrderCount > 0) {
 				DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount);

+ 105 - 83
spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java

@@ -33,6 +33,7 @@ import com.esotericsoftware.spine.Animation.ColorTimeline;
 import com.esotericsoftware.spine.Animation.CurveTimeline;
 import com.esotericsoftware.spine.Animation.DrawOrderTimeline;
 import com.esotericsoftware.spine.Animation.EventTimeline;
+import com.esotericsoftware.spine.Animation.FfdTimeline;
 import com.esotericsoftware.spine.Animation.RotateTimeline;
 import com.esotericsoftware.spine.Animation.ScaleTimeline;
 import com.esotericsoftware.spine.Animation.Timeline;
@@ -54,12 +55,6 @@ import com.badlogic.gdx.utils.JsonValue;
 import com.badlogic.gdx.utils.SerializationException;
 
 public class SkeletonJson {
-	static public final String TIMELINE_SCALE = "scale";
-	static public final String TIMELINE_ROTATE = "rotate";
-	static public final String TIMELINE_TRANSLATE = "translate";
-	static public final String TIMELINE_ATTACHMENT = "attachment";
-	static public final String TIMELINE_COLOR = "color";
-
 	private final AttachmentLoader attachmentLoader;
 	private float scale = 1;
 
@@ -83,13 +78,15 @@ public class SkeletonJson {
 	public SkeletonData readSkeletonData (FileHandle file) {
 		if (file == null) throw new IllegalArgumentException("file cannot be null.");
 
+		float scale = this.scale;
+
 		SkeletonData skeletonData = new SkeletonData();
 		skeletonData.name = file.nameWithoutExtension();
 
 		JsonValue root = new JsonReader().parse(file);
 
 		// Bones.
-		for (JsonValue boneMap = root.getChild("bones"); boneMap != null; boneMap = boneMap.next()) {
+		for (JsonValue boneMap = root.getChild("bones"); boneMap != null; boneMap = boneMap.next) {
 			BoneData parent = null;
 			String parentName = boneMap.getString("parent", null);
 			if (parentName != null) {
@@ -105,11 +102,15 @@ public class SkeletonJson {
 			boneData.scaleY = boneMap.getFloat("scaleY", 1);
 			boneData.inheritScale = boneMap.getBoolean("inheritScale", true);
 			boneData.inheritRotation = boneMap.getBoolean("inheritRotation", true);
+
+			String color = boneMap.getString("color", null);
+			if (color != null) boneData.getColor().set(Color.valueOf(color));
+
 			skeletonData.addBone(boneData);
 		}
 
 		// Slots.
-		for (JsonValue slotMap = root.getChild("slots"); slotMap != null; slotMap = slotMap.next()) {
+		for (JsonValue slotMap = root.getChild("slots"); slotMap != null; slotMap = slotMap.next) {
 			String slotName = slotMap.getString("name");
 			String boneName = slotMap.getString("bone");
 			BoneData boneData = skeletonData.findBone(boneName);
@@ -127,14 +128,14 @@ public class SkeletonJson {
 		}
 
 		// Skins.
-		for (JsonValue skinMap = root.getChild("skins"); skinMap != null; skinMap = skinMap.next()) {
-			Skin skin = new Skin(skinMap.name());
-			for (JsonValue slotEntry = skinMap.child(); slotEntry != null; slotEntry = slotEntry.next()) {
-				int slotIndex = skeletonData.findSlotIndex(slotEntry.name());
-				if (slotIndex == -1) throw new SerializationException("Slot not found: " + slotEntry.name());
-				for (JsonValue entry = slotEntry.child(); entry != null; entry = entry.next()) {
-					Attachment attachment = readAttachment(skin, entry.name(), entry);
-					if (attachment != null) skin.addAttachment(slotIndex, entry.name(), attachment);
+		for (JsonValue skinMap = root.getChild("skins"); skinMap != null; skinMap = skinMap.next) {
+			Skin skin = new Skin(skinMap.name);
+			for (JsonValue slotEntry = skinMap.child; slotEntry != null; slotEntry = slotEntry.next) {
+				int slotIndex = skeletonData.findSlotIndex(slotEntry.name);
+				if (slotIndex == -1) throw new SerializationException("Slot not found: " + slotEntry.name);
+				for (JsonValue entry = slotEntry.child; entry != null; entry = entry.next) {
+					Attachment attachment = readAttachment(skin, entry.name, entry);
+					if (attachment != null) skin.addAttachment(slotIndex, entry.name, attachment);
 				}
 			}
 			skeletonData.addSkin(skin);
@@ -142,8 +143,8 @@ public class SkeletonJson {
 		}
 
 		// Events.
-		for (JsonValue eventMap = root.getChild("events"); eventMap != null; eventMap = eventMap.next()) {
-			EventData eventData = new EventData(eventMap.name());
+		for (JsonValue eventMap = root.getChild("events"); eventMap != null; eventMap = eventMap.next) {
+			EventData eventData = new EventData(eventMap.name);
 			eventData.intValue = eventMap.getInt("int", 0);
 			eventData.floatValue = eventMap.getFloat("float", 0f);
 			eventData.stringValue = eventMap.getString("string", null);
@@ -151,8 +152,8 @@ public class SkeletonJson {
 		}
 
 		// Animations.
-		for (JsonValue animationMap = root.getChild("animations"); animationMap != null; animationMap = animationMap.next())
-			readAnimation(animationMap.name(), animationMap, skeletonData);
+		for (JsonValue animationMap = root.getChild("animations"); animationMap != null; animationMap = animationMap.next)
+			readAnimation(animationMap.name, animationMap, skeletonData);
 
 		skeletonData.bones.shrink();
 		skeletonData.slots.shrink();
@@ -162,6 +163,7 @@ public class SkeletonJson {
 	}
 
 	private Attachment readAttachment (Skin skin, String name, JsonValue map) {
+		float scale = this.scale;
 		name = map.getString("name", name);
 
 		switch (AttachmentType.valueOf(map.getString("type", AttachmentType.region.name()))) {
@@ -182,17 +184,26 @@ public class SkeletonJson {
 			return region;
 		case boundingbox: {
 			BoundingBoxAttachment box = attachmentLoader.newBoundingBoxAttachment(skin, name);
-			box.setVertices(readFloatArray(map.require("vertices"), scale));
+			float[] vertices = map.require("vertices").asFloatArray();
+			if (scale != 1) {
+				for (int i = 0, n = vertices.length; i < n; i++)
+					vertices[i] *= scale;
+			}
+			box.setVertices(vertices);
 			return box;
 		}
 		case mesh: {
 			MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, map.getString("path", name));
-			float[] vertices = readFloatArray(map.require("vertices"), scale);
-			short[] triangles = readShortArray(map.require("triangles"));
-			float[] uvs = readFloatArray(map.require("uvs"), 1);
+			float[] vertices = map.require("vertices").asFloatArray();
+			if (scale != 1) {
+				for (int i = 0, n = vertices.length; i < n; i++)
+					vertices[i] *= scale;
+			}
+			short[] triangles = map.require("triangles").asShortArray();
+			float[] uvs = map.require("uvs").asFloatArray();
 			mesh.setMesh(vertices, triangles, uvs);
 			if (map.has("hull")) mesh.setHullLength(map.require("hull").asInt());
-			if (map.has("edges")) mesh.setEdges(readIntArray(map.require("edges")));
+			if (map.has("edges")) mesh.setEdges(map.require("edges").asIntArray());
 			mesh.setWidth(map.getFloat("width", 0) * scale);
 			mesh.setHeight(map.getFloat("height", 0) * scale);
 			return mesh;
@@ -210,58 +221,34 @@ public class SkeletonJson {
 		return null;
 	}
 
-	private float[] readFloatArray (JsonValue jsonArray, float scale) {
-		float[] array = new float[jsonArray.size];
-		int i = 0;
-		for (JsonValue point = jsonArray.child; point != null; point = point.next())
-			array[i++] = point.asFloat() * scale;
-		return array;
-	}
-
-	private short[] readShortArray (JsonValue jsonArray) {
-		short[] array = new short[jsonArray.size];
-		int i = 0;
-		for (JsonValue point = jsonArray.child; point != null; point = point.next())
-			array[i++] = (short)point.asInt();
-		return array;
-	}
-
-	private int[] readIntArray (JsonValue jsonArray) {
-		int[] array = new int[jsonArray.size];
-		int i = 0;
-		for (JsonValue point = jsonArray.child; point != null; point = point.next())
-			array[i++] = point.asInt();
-		return array;
-	}
-
 	private void readAnimation (String name, JsonValue map, SkeletonData skeletonData) {
 		Array<Timeline> timelines = new Array();
 		float duration = 0;
 
-		for (JsonValue boneMap = map.getChild("bones"); boneMap != null; boneMap = boneMap.next()) {
-			int boneIndex = skeletonData.findBoneIndex(boneMap.name());
-			if (boneIndex == -1) throw new SerializationException("Bone not found: " + boneMap.name());
+		// SRT.
+		for (JsonValue boneMap = map.getChild("bones"); boneMap != null; boneMap = boneMap.next) {
+			int boneIndex = skeletonData.findBoneIndex(boneMap.name);
+			if (boneIndex == -1) throw new SerializationException("Bone not found: " + boneMap.name);
 
-			for (JsonValue timelineMap = boneMap.child(); timelineMap != null; timelineMap = timelineMap.next()) {
-				String timelineName = timelineMap.name();
-				if (timelineName.equals(TIMELINE_ROTATE)) {
+			for (JsonValue timelineMap = boneMap.child; timelineMap != null; timelineMap = timelineMap.next) {
+				String timelineName = timelineMap.name;
+				if (timelineName.equals("rotate")) {
 					RotateTimeline timeline = new RotateTimeline(timelineMap.size);
 					timeline.boneIndex = boneIndex;
 
 					int frameIndex = 0;
-					for (JsonValue valueMap = timelineMap.child(); valueMap != null; valueMap = valueMap.next()) {
-						float time = valueMap.getFloat("time");
-						timeline.setFrame(frameIndex, time, valueMap.getFloat("angle"));
+					for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next) {
+						timeline.setFrame(frameIndex, valueMap.getFloat("time"), valueMap.getFloat("angle"));
 						readCurve(timeline, frameIndex, valueMap);
 						frameIndex++;
 					}
 					timelines.add(timeline);
 					duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() * 2 - 2]);
 
-				} else if (timelineName.equals(TIMELINE_TRANSLATE) || timelineName.equals(TIMELINE_SCALE)) {
+				} else if (timelineName.equals("translate") || timelineName.equals("scale")) {
 					TranslateTimeline timeline;
 					float timelineScale = 1;
-					if (timelineName.equals(TIMELINE_SCALE))
+					if (timelineName.equals("scale"))
 						timeline = new ScaleTimeline(timelineMap.size);
 					else {
 						timeline = new TranslateTimeline(timelineMap.size);
@@ -270,10 +257,9 @@ public class SkeletonJson {
 					timeline.boneIndex = boneIndex;
 
 					int frameIndex = 0;
-					for (JsonValue valueMap = timelineMap.child(); valueMap != null; valueMap = valueMap.next()) {
-						float time = valueMap.getFloat("time");
+					for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next) {
 						float x = valueMap.getFloat("x", 0), y = valueMap.getFloat("y", 0);
-						timeline.setFrame(frameIndex, time, x * timelineScale, y * timelineScale);
+						timeline.setFrame(frameIndex, valueMap.getFloat("time"), x * timelineScale, y * timelineScale);
 						readCurve(timeline, frameIndex, valueMap);
 						frameIndex++;
 					}
@@ -281,53 +267,88 @@ public class SkeletonJson {
 					duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() * 3 - 3]);
 
 				} else
-					throw new RuntimeException("Invalid timeline type for a bone: " + timelineName + " (" + boneMap.name() + ")");
+					throw new RuntimeException("Invalid timeline type for a bone: " + timelineName + " (" + boneMap.name + ")");
 			}
 		}
 
-		for (JsonValue slotMap = map.getChild("slots"); slotMap != null; slotMap = slotMap.next()) {
-			int slotIndex = skeletonData.findSlotIndex(slotMap.name());
-			if (slotIndex == -1) throw new SerializationException("Slot not found: " + slotMap.name());
+		// FFD.
+		for (JsonValue ffdMap = map.getChild("ffd"); ffdMap != null; ffdMap = ffdMap.next) {
+			Skin skin = skeletonData.findSkin(ffdMap.name);
+			if (skin == null) throw new SerializationException("Skin not found: " + ffdMap.name);
+			for (JsonValue slotMap = ffdMap.child; slotMap != null; slotMap = slotMap.next) {
+				int slotIndex = skeletonData.findSlotIndex(slotMap.name);
+				if (slotIndex == -1) throw new SerializationException("Slot not found: " + slotMap.name);
+				for (JsonValue meshMap = slotMap.child; meshMap != null; meshMap = meshMap.next) {
+					FfdTimeline timeline = new FfdTimeline(meshMap.size);
+					MeshAttachment mesh = (MeshAttachment)skin.getAttachment(slotIndex, meshMap.name);
+					if (mesh == null) throw new SerializationException("Mesh attachment not found: " + meshMap.name);
+					timeline.slotIndex = slotIndex;
+					timeline.meshAttachment = mesh;
 
-			for (JsonValue timelineMap = slotMap.child(); timelineMap != null; timelineMap = timelineMap.next()) {
-				String timelineName = timelineMap.name();
-				if (timelineName.equals(TIMELINE_COLOR)) {
+					int frameIndex = 0;
+					for (JsonValue valueMap = meshMap.child; valueMap != null; valueMap = valueMap.next) {
+						float[] vertices;
+						JsonValue verticesValue = valueMap.get("vertices");
+						if (verticesValue == null)
+							vertices = mesh.getVertices();
+						else {
+							vertices = verticesValue.asFloatArray();
+							if (scale != 1) {
+								for (int i = 0, n = vertices.length; i < n; i++)
+									vertices[i] *= scale;
+							}
+						}
+						timeline.setFrame(frameIndex, valueMap.getFloat("time"), vertices);
+						readCurve(timeline, frameIndex, valueMap);
+						frameIndex++;
+					}
+					timelines.add(timeline);
+					duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() - 1]);
+				}
+			}
+		}
+
+		// Color, attachment.
+		for (JsonValue slotMap = map.getChild("slots"); slotMap != null; slotMap = slotMap.next) {
+			int slotIndex = skeletonData.findSlotIndex(slotMap.name);
+			if (slotIndex == -1) throw new SerializationException("Slot not found: " + slotMap.name);
+
+			for (JsonValue timelineMap = slotMap.child; timelineMap != null; timelineMap = timelineMap.next) {
+				String timelineName = timelineMap.name;
+				if (timelineName.equals("color")) {
 					ColorTimeline timeline = new ColorTimeline(timelineMap.size);
 					timeline.slotIndex = slotIndex;
 
 					int frameIndex = 0;
-					for (JsonValue valueMap = timelineMap.child(); valueMap != null; valueMap = valueMap.next()) {
-						float time = valueMap.getFloat("time");
+					for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next) {
 						Color color = Color.valueOf(valueMap.getString("color"));
-						timeline.setFrame(frameIndex, time, color.r, color.g, color.b, color.a);
+						timeline.setFrame(frameIndex, valueMap.getFloat("time"), color.r, color.g, color.b, color.a);
 						readCurve(timeline, frameIndex, valueMap);
 						frameIndex++;
 					}
 					timelines.add(timeline);
 					duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() * 5 - 5]);
 
-				} else if (timelineName.equals(TIMELINE_ATTACHMENT)) {
+				} else if (timelineName.equals("attachment")) {
 					AttachmentTimeline timeline = new AttachmentTimeline(timelineMap.size);
 					timeline.slotIndex = slotIndex;
 
 					int frameIndex = 0;
-					for (JsonValue valueMap = timelineMap.child(); valueMap != null; valueMap = valueMap.next()) {
-						float time = valueMap.getFloat("time");
-						timeline.setFrame(frameIndex++, time, valueMap.getString("name"));
-					}
+					for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next)
+						timeline.setFrame(frameIndex++, valueMap.getFloat("time"), valueMap.getString("name"));
 					timelines.add(timeline);
 					duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() - 1]);
-
 				} else
-					throw new RuntimeException("Invalid timeline type for a slot: " + timelineName + " (" + slotMap.name() + ")");
+					throw new RuntimeException("Invalid timeline type for a slot: " + timelineName + " (" + slotMap.name + ")");
 			}
 		}
 
+		// Events.
 		JsonValue eventsMap = map.get("events");
 		if (eventsMap != null) {
 			EventTimeline timeline = new EventTimeline(eventsMap.size);
 			int frameIndex = 0;
-			for (JsonValue eventMap = eventsMap.child; eventMap != null; eventMap = eventMap.next()) {
+			for (JsonValue eventMap = eventsMap.child; eventMap != null; eventMap = eventMap.next) {
 				EventData eventData = skeletonData.findEvent(eventMap.getString("name"));
 				if (eventData == null) throw new SerializationException("Event not found: " + eventMap.getString("name"));
 				Event event = new Event(eventData);
@@ -340,12 +361,13 @@ public class SkeletonJson {
 			duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() - 1]);
 		}
 
+		// Draw order.
 		JsonValue drawOrdersMap = map.get("draworder");
 		if (drawOrdersMap != null) {
 			DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrdersMap.size);
 			int slotCount = skeletonData.slots.size;
 			int frameIndex = 0;
-			for (JsonValue drawOrderMap = drawOrdersMap.child; drawOrderMap != null; drawOrderMap = drawOrderMap.next()) {
+			for (JsonValue drawOrderMap = drawOrdersMap.child; drawOrderMap != null; drawOrderMap = drawOrderMap.next) {
 				int[] drawOrder = null;
 				JsonValue offsets = drawOrderMap.get("offsets");
 				if (offsets != null) {
@@ -354,7 +376,7 @@ public class SkeletonJson {
 						drawOrder[i] = -1;
 					int[] unchanged = new int[slotCount - offsets.size];
 					int originalIndex = 0, unchangedIndex = 0;
-					for (JsonValue offsetMap = offsets.child; offsetMap != null; offsetMap = offsetMap.next()) {
+					for (JsonValue offsetMap = offsets.child; offsetMap != null; offsetMap = offsetMap.next) {
 						int slotIndex = skeletonData.findSlotIndex(offsetMap.getString("slot"));
 						if (slotIndex == -1) throw new SerializationException("Slot not found: " + offsetMap.getString("slot"));
 						// Collect unchanged items.

+ 9 - 1
spine-libgdx/src/com/esotericsoftware/spine/Slot.java

@@ -31,6 +31,7 @@ package com.esotericsoftware.spine;
 import com.esotericsoftware.spine.attachments.Attachment;
 
 import com.badlogic.gdx.graphics.Color;
+import com.badlogic.gdx.utils.FloatArray;
 
 public class Slot {
 	final SlotData data;
@@ -39,6 +40,7 @@ public class Slot {
 	final Color color;
 	Attachment attachment;
 	private float attachmentTime;
+	private final FloatArray attachmentVertices = new FloatArray();
 
 	Slot () {
 		data = null;
@@ -92,11 +94,13 @@ public class Slot {
 		return attachment;
 	}
 
-	/** Sets the attachment and resets {@link #getAttachmentTime()}.
+	/** Sets the attachment, resets {@link #getAttachmentTime()}, and clears {@link #getAttachmentVertices()}.
 	 * @param attachment May be null. */
 	public void setAttachment (Attachment attachment) {
+		if (this.attachment == attachment) return;
 		this.attachment = attachment;
 		attachmentTime = skeleton.time;
+		attachmentVertices.clear();
 	}
 
 	public void setAttachmentTime (float time) {
@@ -108,6 +112,10 @@ public class Slot {
 		return skeleton.time - attachmentTime;
 	}
 
+	public FloatArray getAttachmentVertices () {
+		return attachmentVertices;
+	}
+
 	void setToSetupPose (int slotIndex) {
 		color.set(data.color);
 		setAttachment(data.attachmentName == null ? null : skeleton.getAttachment(slotIndex, data.attachmentName));

+ 33 - 37
spine-libgdx/src/com/esotericsoftware/spine/attachments/MeshAttachment.java

@@ -34,19 +34,22 @@ import com.esotericsoftware.spine.Slot;
 
 import com.badlogic.gdx.graphics.Color;
 import com.badlogic.gdx.graphics.g2d.TextureRegion;
+import com.badlogic.gdx.utils.FloatArray;
 import com.badlogic.gdx.utils.NumberUtils;
 
 /** Attachment that displays a texture region. */
 public class MeshAttachment extends Attachment {
 	private TextureRegion region;
 	private String path;
-	private int hullLength;
 	private float[] vertices;
 	private short[] triangles;
-	private int[] edges;
 	private float[] worldVertices;
 	private final Color color = new Color(1, 1, 1, 1);
+
+	// Nonessential.
+	private int[] edges;
 	private float width, height;
+	private int hullLength;
 
 	public MeshAttachment (String name) {
 		super(name);
@@ -71,41 +74,24 @@ public class MeshAttachment extends Attachment {
 		float g = skeletonColor.g * slotColor.g * regionColor.g;
 		float b = skeletonColor.b * slotColor.b * regionColor.b;
 		float a = skeletonColor.a * slotColor.a * regionColor.a * 255;
-		float color;
-		if (premultipliedAlpha) {
-			r *= a;
-			g *= a;
-			b *= a;
-		} else {
-			r *= 255;
-			g *= 255;
-			b *= 255;
-		}
-		color = NumberUtils.intToFloatColor( //
+		float multiplier = premultipliedAlpha ? a : 255;
+		float color = NumberUtils.intToFloatColor( //
 			((int)(a) << 24) //
-				| ((int)(b) << 16) //
-				| ((int)(g) << 8) //
-				| ((int)(r)));
+				| ((int)(b * multiplier) << 16) //
+				| ((int)(g * multiplier) << 8) //
+				| ((int)(r * multiplier)));
 
 		float[] worldVertices = this.worldVertices;
-		float[] vertices = this.vertices;
-		Bone bone1 = slot.getBone();
-		float x = skeleton.getX();
-		float y = skeleton.getY();
-		float m00 = bone1.getM00();
-		float m01 = bone1.getM01();
-		float m10 = bone1.getM10();
-		float m11 = bone1.getM11();
-
-		float vx, vy;
-		for (int v = 0, w = 0, n = vertices.length; v < n; v += 2, w += 5) {
-			vx = vertices[v];
-			vy = vertices[v + 1];
-			float wx1 = vx * m00 + vy * m01 + x + bone1.getWorldX();
-			float wy1 = vx * m10 + vy * m11 + y + bone1.getWorldY();
-			worldVertices[w] = wx1;
-			worldVertices[w + 1] = wy1;
-			worldVertices[w + 2] = Color.WHITE.toFloatBits();
+		FloatArray verticesArray = slot.getAttachmentVertices();
+		float[] vertices = verticesArray.size > 0 ? verticesArray.items : this.vertices;
+		Bone bone = slot.getBone();
+		float x = skeleton.getX() + bone.getWorldX(), y = skeleton.getY() + bone.getWorldY();
+		float m00 = bone.getM00(), m01 = bone.getM01(), m10 = bone.getM10(), m11 = bone.getM11();
+		for (int v = 0, w = 0, n = worldVertices.length; w < n; v += 2, w += 5) {
+			float vx = vertices[v];
+			float vy = vertices[v + 1];
+			worldVertices[w] = vx * m00 + vy * m01 + x;
+			worldVertices[w + 1] = vx * m10 + vy * m11 + y;
 			worldVertices[w + 2] = color;
 		}
 	}
@@ -173,9 +159,19 @@ public class MeshAttachment extends Attachment {
 		int worldVerticesLength = vertices.length / 2 * 5;
 		if (worldVertices == null || worldVertices.length != worldVerticesLength) worldVertices = new float[worldVerticesLength];
 
-		for (int i = 0, w = 3, n = vertices.length; i < n; i += 2, w += 5) {
-			worldVertices[w] = uvs[i];
-			worldVertices[w + 1] = uvs[i + 1];
+		float u, v, w, h;
+		if (region == null) {
+			u = v = 0;
+			w = h = 1;
+		} else {
+			u = region.getU();
+			v = region.getV();
+			w = region.getU2() - u;
+			h = region.getV2() - v;
+		}
+		for (int i = 0, ii = 3, n = vertices.length; i < n; i += 2, ii += 5) {
+			worldVertices[ii] = u + uvs[i] * w;
+			worldVertices[ii + 1] = v + uvs[i + 1] * h;
 		}
 	}
 }

+ 8 - 20
spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionAttachment.java

@@ -153,32 +153,20 @@ public class RegionAttachment extends Attachment {
 		float g = skeletonColor.g * slotColor.g * regionColor.g;
 		float b = skeletonColor.b * slotColor.b * regionColor.b;
 		float a = skeletonColor.a * slotColor.a * regionColor.a * 255;
-		float color;
-		if (premultipliedAlpha) {
-			r *= a;
-			g *= a;
-			b *= a;
-		} else {
-			r *= 255;
-			g *= 255;
-			b *= 255;
-		}
-		color = NumberUtils.intToFloatColor( //
+		float multiplier = premultipliedAlpha ? a : 255;
+		float color = NumberUtils.intToFloatColor( //
 			((int)(a) << 24) //
-				| ((int)(b) << 16) //
-				| ((int)(g) << 8) //
-				| ((int)(r)));
+				| ((int)(b * multiplier) << 16) //
+				| ((int)(g * multiplier) << 8) //
+				| ((int)(r * multiplier)));
 
 		float[] vertices = this.vertices;
 		float[] offset = this.offset;
 		Bone bone = slot.getBone();
-		float x = bone.getWorldX() + skeleton.getX();
-		float y = bone.getWorldY() + skeleton.getY();
-		float m00 = bone.getM00();
-		float m01 = bone.getM01();
-		float m10 = bone.getM10();
-		float m11 = bone.getM11();
+		float x = skeleton.getX() + bone.getWorldX(), y = skeleton.getY() + bone.getWorldY();
+		float m00 = bone.getM00(), m01 = bone.getM01(), m10 = bone.getM10(), m11 = bone.getM11();
 		float offsetX, offsetY;
+
 		offsetX = offset[BRX];
 		offsetY = offset[BRY];
 		vertices[X1] = offsetX * m00 + offsetY * m01 + x; // br

+ 5 - 4
spine-libgdx/test/com/esotericsoftware/spine/SkeletonTest.java

@@ -38,14 +38,14 @@ import com.badlogic.gdx.graphics.GL10;
 import com.badlogic.gdx.graphics.Pixmap;
 import com.badlogic.gdx.graphics.Pixmap.Format;
 import com.badlogic.gdx.graphics.Texture;
-import com.badlogic.gdx.graphics.g2d.SpriteBatch;
+import com.badlogic.gdx.graphics.g2d.PolygonSpriteBatch;
 import com.badlogic.gdx.graphics.g2d.TextureAtlas;
 import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
 import com.badlogic.gdx.graphics.g2d.TextureAtlas.TextureAtlasData;
 import com.badlogic.gdx.utils.Array;
 
 public class SkeletonTest extends ApplicationAdapter {
-	SpriteBatch batch;
+	PolygonSpriteBatch batch;
 	float time;
 	SkeletonRenderer renderer;
 	SkeletonRendererDebug debugRenderer;
@@ -56,7 +56,7 @@ public class SkeletonTest extends ApplicationAdapter {
 	Array<Event> events = new Array();
 
 	public void create () {
-		batch = new SpriteBatch();
+		batch = new PolygonSpriteBatch();
 		renderer = new SkeletonRenderer();
 		debugRenderer = new SkeletonRendererDebug();
 
@@ -120,6 +120,7 @@ public class SkeletonTest extends ApplicationAdapter {
 		if (x > Gdx.graphics.getWidth()) skeleton.setFlipX(true);
 		if (x < 0) skeleton.setFlipX(false);
 		skeleton.setX(x);
+		skeleton.setX(300);
 
 		Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
 
@@ -134,7 +135,7 @@ public class SkeletonTest extends ApplicationAdapter {
 		renderer.draw(batch, skeleton);
 		batch.end();
 
-		debugRenderer.draw(skeleton);
+		//debugRenderer.draw(skeleton);
 	}
 
 	public void resize (int width, int height) {

+ 35 - 68
spine-libgdx/test/goblins.json

@@ -8,10 +8,10 @@
 	{ "name": "torso", "parent": "hip", "length": 85.82, "x": -6.42, "y": 1.97, "rotation": 93.92 },
 	{ "name": "left lower leg", "parent": "left upper leg", "length": 49.89, "x": 56.34, "y": 0.98, "rotation": -16.65 },
 	{ "name": "left shoulder", "parent": "torso", "length": 35.43, "x": 74.04, "y": -20.38, "rotation": -156.96 },
-	{ "name": "neck", "parent": "torso", "length": 18.38, "x": 81.67, "y": -6.34, "rotation": -13.92 },
+	{ "name": "neck", "parent": "torso", "length": 18.38, "x": 81.67, "y": -6.34, "rotation": -1.51 },
 	{ "name": "right lower leg", "parent": "right upper leg", "length": 58.52, "x": 42.99, "y": -0.61, "rotation": -14.34 },
 	{ "name": "right shoulder", "parent": "torso", "length": 37.24, "x": 76.02, "y": 18.14, "rotation": 133.88 },
-	{ "name": "head", "parent": "neck", "length": 68.28, "x": 20.93, "y": 11.59 },
+	{ "name": "head", "parent": "neck", "length": 68.28, "x": 20.93, "y": 11.59, "rotation": -13.92, "color": "d48387ff" },
 	{ "name": "left arm", "parent": "left shoulder", "length": 35.62, "x": 37.85, "y": -2.34, "rotation": 28.16 },
 	{ "name": "left foot", "parent": "left lower leg", "length": 46.5, "x": 58.94, "y": -7.61, "rotation": 102.43 },
 	{ "name": "right arm", "parent": "right shoulder", "length": 36.74, "x": 37.6, "y": 0.31, "rotation": 36.32 },
@@ -26,9 +26,9 @@
 	{ "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": "boundingbox" },
+	{ "name": "left upper leg", "bone": "left upper leg", "attachment": "left upper leg" },
 	{ "name": "neck", "bone": "neck", "attachment": "neck" },
-	{ "name": "torso", "bone": "torso", "attachment": "torso" },
+	{ "name": "torso", "bone": "torso", "attachment": "goblin/head" },
 	{ "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" },
@@ -40,81 +40,34 @@
 	{ "name": "right shoulder", "bone": "right shoulder", "attachment": "right shoulder" },
 	{ "name": "right arm", "bone": "right arm", "attachment": "right arm" },
 	{ "name": "right hand item", "bone": "right hand", "attachment": "dagger" },
-	{ "name": "right hand", "bone": "right hand", "attachment": "right hand" },
-	{ "name": "bounding box", "bone": "head", "attachment": "bbox" }
+	{ "name": "right hand", "bone": "right hand", "attachment": "right hand" }
 ],
 "skins": {
 	"default": {
-		"bounding box": {
-			"bbox": {
-				"type": "boundingbox",
-				"vertices": [
-					-7.2252045,
-					-34.808647,
-					-1.9847412,
-					-40.70198,
-					12.63089,
-					-37.503178,
-					13.559738,
-					-44.273937,
-					21.15651,
-					-45.197586,
-					25.916733,
-					-36.59557,
-					30.31987,
-					-37.271088,
-					31.82283,
-					-47.966175,
-					36.74109,
-					-47.863335,
-					37.828888,
-					-37.763573,
-					52.86676,
-					-18.987732,
-					54.687653,
-					4.058235,
-					42.064606,
-					24.16484,
-					37.09868,
-					25.39936,
-					36.084442,
-					44.725235,
-					28.417389,
-					53.716904,
-					23.382614,
-					49.773712,
-					28.766006,
-					39.868294,
-					25.873352,
-					27.605164,
-					8.960373,
-					14.733994,
-					-0.746933,
-					1.7576027
-				]
-			}
-		},
 		"left hand item": {
 			"dagger": { "x": 7.88, "y": -23.45, "rotation": 10.47, "width": 26, "height": 108 },
 			"spear": { "x": -4.55, "y": 39.2, "rotation": 13.04, "width": 22, "height": 368 }
 		},
-		"left upper leg": {
-			"boundingbox": {
-				"type": "boundingbox",
-				"vertices": [ -73.94766, 8.514406, -49.917465, 25.294191, -79.28125, 46.664314, -95.755325, 34.604897, -74.9664, 27.453112 ]
-			}
-		},
 		"right hand item": {
 			"dagger": { "x": 6.51, "y": -24.15, "rotation": -8.06, "width": 26, "height": 108 }
+		},
+		"torso": {
+			"goblin/head": {
+				"type": "mesh",
+				"vertices": [ 87.34, -39.83, 109.98, 60.57, 174.32, 46.07, 151.68, -54.33 ],
+				"triangles": [ 1, 2, 3, 1, 3, 0 ],
+				"uvs": [ 1, 1, 0, 1, 0, 0, 1, 0 ],
+				"edges": [ 0, 2, 2, 4, 4, 6, 0, 6 ],
+				"hull": 8,
+				"width": 103,
+				"height": 66
+			}
 		}
 	},
 	"goblin": {
 		"eyes": {
 			"eyes closed": { "name": "goblin/eyes-closed", "x": 32.21, "y": -21.27, "rotation": -88.92, "width": 34, "height": 12 }
 		},
-		"head": {
-			"head": { "name": "goblin/head", "x": 25.73, "y": 2.33, "rotation": -92.29, "width": 103, "height": 66 }
-		},
 		"left arm": {
 			"left arm": {
 				"name": "goblin/left-arm",
@@ -546,11 +499,25 @@
 				]
 			}
 		},
+		"ffd": {
+			"default": {
+				"torso": {
+					"goblin/head": [
+						{ "time": 0 },
+						{
+							"time": 0.5,
+							"vertices": [ 78.53, -53.55, 96.74, 63.46, 197.1, 24.55, 144.41, -37.48 ]
+						},
+						{ "time": 1 }
+					]
+				}
+			}
+		},
 		"slots": {
-			"eyes": {
-				"attachment": [
-					{ "time": 0.7, "name": "eyes closed" },
-					{ "time": 0.8, "name": null }
+			"head": {
+				"color": [
+					{ "time": 0.1, "color": "ffffffff" },
+					{ "time": 0.8, "color": "fcff00ff" }
 				]
 			}
 		},

BIN
spine-libgdx/test/goblins.skel