Browse Source

Merge branch '3.9-beta' of https://github.com/esotericsoftware/spine-runtimes into 3.9-beta

badlogic 5 years ago
parent
commit
fa422fb1ce

+ 2 - 6
spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/SimpleTest1.java

@@ -58,15 +58,11 @@ public class SimpleTest1 extends ApplicationAdapter {
 
 
 		atlas = new TextureAtlas(Gdx.files.internal("spineboy/spineboy-pma.atlas"));
 		atlas = new TextureAtlas(Gdx.files.internal("spineboy/spineboy-pma.atlas"));
 
 
-		SkeletonJson loader = new SkeletonJson(atlas); // This loads skeleton JSON data, which is stateless.
+		SkeletonLoader loader = new SkeletonJson(atlas); // This loads skeleton JSON data, which is stateless.
+		// SkeletonLoader loader = new SkeletonBinary(atlas); // Or use SkeletonBinary to load binary data.
 		loader.setScale(0.6f); // Load the skeleton at 60% the size it was in Spine.
 		loader.setScale(0.6f); // Load the skeleton at 60% the size it was in Spine.
 		SkeletonData skeletonData = loader.readSkeletonData(Gdx.files.internal("spineboy/spineboy-pro.json"));
 		SkeletonData skeletonData = loader.readSkeletonData(Gdx.files.internal("spineboy/spineboy-pro.json"));
 
 
-		// Binary would be loaded similarly:
-		// SkeletonBinary loader = new SkeletonBinary(atlas);
-		// loader.setScale(0.6f);
-		// SkeletonData skeletonData = loader.readSkeletonData(Gdx.files.internal("spineboy/spineboy-pro.skel"));
-
 		skeleton = new Skeleton(skeletonData); // Skeleton holds skeleton state (bone positions, slot attachments, etc).
 		skeleton = new Skeleton(skeletonData); // Skeleton holds skeleton state (bone positions, slot attachments, etc).
 		skeleton.setPosition(250, 20);
 		skeleton.setPosition(250, 20);
 
 

+ 1 - 5
spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/SimpleTest2.java

@@ -65,14 +65,10 @@ public class SimpleTest2 extends ApplicationAdapter {
 		atlas = new TextureAtlas(Gdx.files.internal("spineboy/spineboy-pma.atlas"));
 		atlas = new TextureAtlas(Gdx.files.internal("spineboy/spineboy-pma.atlas"));
 
 
 		SkeletonJson loader = new SkeletonJson(atlas); // This loads skeleton JSON data, which is stateless.
 		SkeletonJson loader = new SkeletonJson(atlas); // This loads skeleton JSON data, which is stateless.
+		// SkeletonLoader loader = new SkeletonBinary(atlas); // Or use SkeletonBinary to load binary data.
 		loader.setScale(0.6f); // Load the skeleton at 60% the size it was in Spine.
 		loader.setScale(0.6f); // Load the skeleton at 60% the size it was in Spine.
 		SkeletonData skeletonData = loader.readSkeletonData(Gdx.files.internal("spineboy/spineboy-ess.json"));
 		SkeletonData skeletonData = loader.readSkeletonData(Gdx.files.internal("spineboy/spineboy-ess.json"));
 
 
-		// Binary would be loaded similarly:
-		// SkeletonBinary loader = new SkeletonBinary(atlas);
-		// loader.setScale(0.6f);
-		// SkeletonData skeletonData = loader.readSkeletonData(Gdx.files.internal("spineboy/spineboy-ess.skel"));
-
 		skeleton = new Skeleton(skeletonData); // Skeleton holds skeleton state (bone positions, slot attachments, etc).
 		skeleton = new Skeleton(skeletonData); // Skeleton holds skeleton state (bone positions, slot attachments, etc).
 		skeleton.setPosition(250, 20);
 		skeleton.setPosition(250, 20);
 		skeleton.setAttachment("head-bb", "head"); // Attach "head" bounding box to "head-bb" slot.
 		skeleton.setAttachment("head-bb", "head"); // Attach "head" bounding box to "head-bb" slot.

+ 2 - 6
spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/SimpleTest3.java

@@ -60,14 +60,10 @@ public class SimpleTest3 extends ApplicationAdapter {
 		atlas = new TextureAtlas(Gdx.files.internal("raptor/raptor-pma.atlas"));
 		atlas = new TextureAtlas(Gdx.files.internal("raptor/raptor-pma.atlas"));
 
 
 		SkeletonJson loader = new SkeletonJson(atlas); // This loads skeleton JSON data, which is stateless.
 		SkeletonJson loader = new SkeletonJson(atlas); // This loads skeleton JSON data, which is stateless.
-		loader.setScale(0.5f); // Load the skeleton at 50% the size it was in Spine.
+		// SkeletonLoader loader = new SkeletonBinary(atlas); // Or use SkeletonBinary to load binary data.
+		loader.setScale(0.1f); // Load the skeleton at 50% the size it was in Spine.
 		SkeletonData skeletonData = loader.readSkeletonData(Gdx.files.internal("raptor/raptor-pro.json"));
 		SkeletonData skeletonData = loader.readSkeletonData(Gdx.files.internal("raptor/raptor-pro.json"));
 
 
-		// Binary would be loaded similarly:
-		// SkeletonBinary loader = new SkeletonBinary(atlas);
-		// loader.setScale(0.5f);
-		// SkeletonData skeletonData = loader.readSkeletonData(Gdx.files.internal("raptor/raptor-pro.skel"));
-
 		skeleton = new Skeleton(skeletonData); // Skeleton holds skeleton state (bone positions, slot attachments, etc).
 		skeleton = new Skeleton(skeletonData); // Skeleton holds skeleton state (bone positions, slot attachments, etc).
 		skeleton.setPosition(250, 20);
 		skeleton.setPosition(250, 20);
 
 

+ 3 - 1
spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/SimpleTest4.java

@@ -35,6 +35,7 @@ import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
 import com.badlogic.gdx.graphics.GL20;
 import com.badlogic.gdx.graphics.GL20;
 import com.badlogic.gdx.graphics.OrthographicCamera;
 import com.badlogic.gdx.graphics.OrthographicCamera;
 import com.badlogic.gdx.graphics.g2d.TextureAtlas;
 import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+
 import com.esotericsoftware.spine.utils.TwoColorPolygonBatch;
 import com.esotericsoftware.spine.utils.TwoColorPolygonBatch;
 
 
 public class SimpleTest4 extends ApplicationAdapter {
 public class SimpleTest4 extends ApplicationAdapter {
@@ -59,7 +60,8 @@ public class SimpleTest4 extends ApplicationAdapter {
 		atlas = new TextureAtlas(Gdx.files.internal("goblins/goblins-pma.atlas"));
 		atlas = new TextureAtlas(Gdx.files.internal("goblins/goblins-pma.atlas"));
 
 
 		SkeletonJson loader = new SkeletonJson(atlas); // This loads skeleton JSON data, which is stateless.
 		SkeletonJson loader = new SkeletonJson(atlas); // This loads skeleton JSON data, which is stateless.
-		loader.setScale(0.6f); // Load the skeleton at 60% the size it was in Spine.
+		// SkeletonLoader loader = new SkeletonBinary(atlas); // Or use SkeletonBinary to load binary data.
+		loader.setScale(1.3f); // Load the skeleton at 130% the size it was in Spine.
 		SkeletonData skeletonData = loader.readSkeletonData(Gdx.files.internal("goblins/goblins-pro.json"));
 		SkeletonData skeletonData = loader.readSkeletonData(Gdx.files.internal("goblins/goblins-pro.json"));
 
 
 		skeleton = new Skeleton(skeletonData); // Skeleton holds skeleton state (bone positions, slot attachments, etc).
 		skeleton = new Skeleton(skeletonData); // Skeleton holds skeleton state (bone positions, slot attachments, etc).

+ 28 - 47
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java

@@ -66,7 +66,6 @@ import com.esotericsoftware.spine.PathConstraintData.PositionMode;
 import com.esotericsoftware.spine.PathConstraintData.RotateMode;
 import com.esotericsoftware.spine.PathConstraintData.RotateMode;
 import com.esotericsoftware.spine.PathConstraintData.SpacingMode;
 import com.esotericsoftware.spine.PathConstraintData.SpacingMode;
 import com.esotericsoftware.spine.SkeletonJson.LinkedMesh;
 import com.esotericsoftware.spine.SkeletonJson.LinkedMesh;
-import com.esotericsoftware.spine.attachments.AtlasAttachmentLoader;
 import com.esotericsoftware.spine.attachments.Attachment;
 import com.esotericsoftware.spine.attachments.Attachment;
 import com.esotericsoftware.spine.attachments.AttachmentLoader;
 import com.esotericsoftware.spine.attachments.AttachmentLoader;
 import com.esotericsoftware.spine.attachments.AttachmentType;
 import com.esotericsoftware.spine.attachments.AttachmentType;
@@ -83,7 +82,7 @@ import com.esotericsoftware.spine.attachments.VertexAttachment;
  * See <a href="http://esotericsoftware.com/spine-binary-format">Spine binary format</a> and
  * See <a href="http://esotericsoftware.com/spine-binary-format">Spine binary format</a> and
  * <a href="http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data">JSON and binary data</a> in the Spine
  * <a href="http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data">JSON and binary data</a> in the Spine
  * Runtimes Guide. */
  * Runtimes Guide. */
-public class SkeletonBinary {
+public class SkeletonBinary extends SkeletonLoader {
 	static public final int BONE_ROTATE = 0;
 	static public final int BONE_ROTATE = 0;
 	static public final int BONE_TRANSLATE = 1;
 	static public final int BONE_TRANSLATE = 1;
 	static public final int BONE_SCALE = 2;
 	static public final int BONE_SCALE = 2;
@@ -101,30 +100,12 @@ public class SkeletonBinary {
 	static public final int CURVE_STEPPED = 1;
 	static public final int CURVE_STEPPED = 1;
 	static public final int CURVE_BEZIER = 2;
 	static public final int CURVE_BEZIER = 2;
 
 
-	private final AttachmentLoader attachmentLoader;
-	private float scale = 1;
-	private final Array<LinkedMesh> linkedMeshes = new Array();
-
-	public SkeletonBinary (TextureAtlas atlas) {
-		attachmentLoader = new AtlasAttachmentLoader(atlas);
-	}
-
 	public SkeletonBinary (AttachmentLoader attachmentLoader) {
 	public SkeletonBinary (AttachmentLoader attachmentLoader) {
-		if (attachmentLoader == null) throw new IllegalArgumentException("attachmentLoader cannot be null.");
-		this.attachmentLoader = attachmentLoader;
+		super(attachmentLoader);
 	}
 	}
 
 
-	/** Scales bone positions, image sizes, and translations as they are loaded. This allows different size images to be used at
-	 * runtime than were used in Spine.
-	 * <p>
-	 * See <a href="http://esotericsoftware.com/spine-loading-skeleton-data#Scaling">Scaling</a> in the Spine Runtimes Guide. */
-	public float getScale () {
-		return scale;
-	}
-
-	public void setScale (float scale) {
-		if (scale == 0) throw new IllegalArgumentException("scale cannot be 0.");
-		this.scale = scale;
+	public SkeletonBinary (TextureAtlas atlas) {
+		super(atlas);
 	}
 	}
 
 
 	public SkeletonData readSkeletonData (FileHandle file) {
 	public SkeletonData readSkeletonData (FileHandle file) {
@@ -610,10 +591,10 @@ public class SkeletonBinary {
 							timeline.setStepped(frame);
 							timeline.setStepped(frame);
 							break;
 							break;
 						case CURVE_BEZIER:
 						case CURVE_BEZIER:
-							setBezier(input, timeline, bezier++, frame, 0, time, time2, r, r2);
-							setBezier(input, timeline, bezier++, frame, 1, time, time2, g, g2);
-							setBezier(input, timeline, bezier++, frame, 2, time, time2, b, b2);
-							setBezier(input, timeline, bezier++, frame, 3, time, time2, a, a2);
+							setBezier(input, timeline, bezier++, frame, 0, time, time2, r, r2, 1);
+							setBezier(input, timeline, bezier++, frame, 1, time, time2, g, g2, 1);
+							setBezier(input, timeline, bezier++, frame, 2, time, time2, b, b2, 1);
+							setBezier(input, timeline, bezier++, frame, 3, time, time2, a, a2, 1);
 						}
 						}
 						time = time2;
 						time = time2;
 						r = r2;
 						r = r2;
@@ -644,13 +625,13 @@ public class SkeletonBinary {
 							timeline.setStepped(frame);
 							timeline.setStepped(frame);
 							break;
 							break;
 						case CURVE_BEZIER:
 						case CURVE_BEZIER:
-							setBezier(input, timeline, bezier++, frame, 0, time, time2, r, nr);
-							setBezier(input, timeline, bezier++, frame, 1, time, time2, g, ng);
-							setBezier(input, timeline, bezier++, frame, 2, time, time2, b, nb);
-							setBezier(input, timeline, bezier++, frame, 3, time, time2, a, na);
-							setBezier(input, timeline, bezier++, frame, 4, time, time2, r2, nr2);
-							setBezier(input, timeline, bezier++, frame, 5, time, time2, g2, ng2);
-							setBezier(input, timeline, bezier++, frame, 6, time, time2, b2, nb2);
+							setBezier(input, timeline, bezier++, frame, 0, time, time2, r, nr, 1);
+							setBezier(input, timeline, bezier++, frame, 1, time, time2, g, ng, 1);
+							setBezier(input, timeline, bezier++, frame, 2, time, time2, b, nb, 1);
+							setBezier(input, timeline, bezier++, frame, 3, time, time2, a, na, 1);
+							setBezier(input, timeline, bezier++, frame, 4, time, time2, r2, nr2, 1);
+							setBezier(input, timeline, bezier++, frame, 5, time, time2, g2, ng2, 1);
+							setBezier(input, timeline, bezier++, frame, 6, time, time2, b2, nb2, 1);
 						}
 						}
 						time = time2;
 						time = time2;
 						r = nr;
 						r = nr;
@@ -703,8 +684,8 @@ public class SkeletonBinary {
 					timeline.setStepped(frame);
 					timeline.setStepped(frame);
 					break;
 					break;
 				case CURVE_BEZIER:
 				case CURVE_BEZIER:
-					setBezier(input, timeline, bezier++, frame, 0, time, time2, mix, mix2);
-					setBezier(input, timeline, bezier++, frame, 1, time, time2, softness, softness2);
+					setBezier(input, timeline, bezier++, frame, 0, time, time2, mix, mix2, 1);
+					setBezier(input, timeline, bezier++, frame, 1, time, time2, softness, softness2, 1);
 				}
 				}
 				time = time2;
 				time = time2;
 				mix = mix2;
 				mix = mix2;
@@ -729,10 +710,10 @@ public class SkeletonBinary {
 					timeline.setStepped(frame);
 					timeline.setStepped(frame);
 					break;
 					break;
 				case CURVE_BEZIER:
 				case CURVE_BEZIER:
-					setBezier(input, timeline, bezier++, frame, 0, time, time2, rotateMix, rotateMix2);
-					setBezier(input, timeline, bezier++, frame, 1, time, time2, translateMix, translateMix2);
-					setBezier(input, timeline, bezier++, frame, 2, time, time2, scaleMix, scaleMix2);
-					setBezier(input, timeline, bezier++, frame, 3, time, time2, shearMix, shearMix2);
+					setBezier(input, timeline, bezier++, frame, 0, time, time2, rotateMix, rotateMix2, 1);
+					setBezier(input, timeline, bezier++, frame, 1, time, time2, translateMix, translateMix2, 1);
+					setBezier(input, timeline, bezier++, frame, 2, time, time2, scaleMix, scaleMix2, 1);
+					setBezier(input, timeline, bezier++, frame, 3, time, time2, shearMix, shearMix2, 1);
 				}
 				}
 				time = time2;
 				time = time2;
 				rotateMix = rotateMix2;
 				rotateMix = rotateMix2;
@@ -812,7 +793,7 @@ public class SkeletonBinary {
 							timeline.setStepped(frame);
 							timeline.setStepped(frame);
 							break;
 							break;
 						case CURVE_BEZIER:
 						case CURVE_BEZIER:
-							setBezier(input, timeline, bezier++, frame, 0, time, time2, 0, 1);
+							setBezier(input, timeline, bezier++, frame, 0, time, time2, 0, 1, 1);
 						}
 						}
 						time = time2;
 						time = time2;
 					}
 					}
@@ -891,7 +872,7 @@ public class SkeletonBinary {
 				timeline.setStepped(frame);
 				timeline.setStepped(frame);
 				break;
 				break;
 			case CURVE_BEZIER:
 			case CURVE_BEZIER:
-				setBezier(input, timeline, bezier++, frame, 0, time, time2, value, value2);
+				setBezier(input, timeline, bezier++, frame, 0, time, time2, value, value2, 1);
 			}
 			}
 			time = time2;
 			time = time2;
 			value = value2;
 			value = value2;
@@ -910,8 +891,8 @@ public class SkeletonBinary {
 				timeline.setStepped(frame);
 				timeline.setStepped(frame);
 				break;
 				break;
 			case CURVE_BEZIER:
 			case CURVE_BEZIER:
-				setBezier(input, timeline, bezier++, frame, 0, time, time2, value1, nvalue1);
-				setBezier(input, timeline, bezier++, frame, 1, time, time2, value2, nvalue2);
+				setBezier(input, timeline, bezier++, frame, 0, time, time2, value1, nvalue1, scale);
+				setBezier(input, timeline, bezier++, frame, 1, time, time2, value2, nvalue2, scale);
 			}
 			}
 			time = time2;
 			time = time2;
 			value1 = nvalue1;
 			value1 = nvalue1;
@@ -921,9 +902,9 @@ public class SkeletonBinary {
 	}
 	}
 
 
 	void setBezier (SkeletonInput input, CurveTimeline timeline, int bezier, int frame, int value, float time1, float time2,
 	void setBezier (SkeletonInput input, CurveTimeline timeline, int bezier, int frame, int value, float time1, float time2,
-		float value1, float value2) throws IOException {
-		timeline.setBezier(bezier, frame, value, time1, value1, input.readFloat(), input.readFloat(), input.readFloat(),
-			input.readFloat(), time2, value2);
+		float value1, float value2, float scale) throws IOException {
+		timeline.setBezier(bezier, frame, value, time1, value1, input.readFloat(), input.readFloat() * scale, input.readFloat(),
+			input.readFloat() * scale, time2, value2);
 	}
 	}
 
 
 	static class Vertices {
 	static class Vertices {

+ 36 - 55
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java

@@ -64,7 +64,6 @@ import com.esotericsoftware.spine.BoneData.TransformMode;
 import com.esotericsoftware.spine.PathConstraintData.PositionMode;
 import com.esotericsoftware.spine.PathConstraintData.PositionMode;
 import com.esotericsoftware.spine.PathConstraintData.RotateMode;
 import com.esotericsoftware.spine.PathConstraintData.RotateMode;
 import com.esotericsoftware.spine.PathConstraintData.SpacingMode;
 import com.esotericsoftware.spine.PathConstraintData.SpacingMode;
-import com.esotericsoftware.spine.attachments.AtlasAttachmentLoader;
 import com.esotericsoftware.spine.attachments.Attachment;
 import com.esotericsoftware.spine.attachments.Attachment;
 import com.esotericsoftware.spine.attachments.AttachmentLoader;
 import com.esotericsoftware.spine.attachments.AttachmentLoader;
 import com.esotericsoftware.spine.attachments.AttachmentType;
 import com.esotericsoftware.spine.attachments.AttachmentType;
@@ -77,35 +76,19 @@ import com.esotericsoftware.spine.attachments.RegionAttachment;
 import com.esotericsoftware.spine.attachments.VertexAttachment;
 import com.esotericsoftware.spine.attachments.VertexAttachment;
 
 
 /** Loads skeleton data in the Spine JSON format.
 /** Loads skeleton data in the Spine JSON format.
+ * <p>
+ * JSON is human readable but the binary format is much smaller on disk and faster to load. See {@link SkeletonBinary}.
  * <p>
  * <p>
  * See <a href="http://esotericsoftware.com/spine-json-format">Spine JSON format</a> and
  * See <a href="http://esotericsoftware.com/spine-json-format">Spine JSON format</a> and
  * <a href="http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data">JSON and binary data</a> in the Spine
  * <a href="http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data">JSON and binary data</a> in the Spine
  * Runtimes Guide. */
  * Runtimes Guide. */
-public class SkeletonJson {
-	private final AttachmentLoader attachmentLoader;
-	private float scale = 1;
-	private final Array<LinkedMesh> linkedMeshes = new Array();
-
-	public SkeletonJson (TextureAtlas atlas) {
-		attachmentLoader = new AtlasAttachmentLoader(atlas);
-	}
-
+public class SkeletonJson extends SkeletonLoader {
 	public SkeletonJson (AttachmentLoader attachmentLoader) {
 	public SkeletonJson (AttachmentLoader attachmentLoader) {
-		if (attachmentLoader == null) throw new IllegalArgumentException("attachmentLoader cannot be null.");
-		this.attachmentLoader = attachmentLoader;
-	}
-
-	/** Scales bone positions, image sizes, and translations as they are loaded. This allows different size images to be used at
-	 * runtime than were used in Spine.
-	 * <p>
-	 * See <a href="http://esotericsoftware.com/spine-loading-skeleton-data#Scaling">Scaling</a> in the Spine Runtimes Guide. */
-	public float getScale () {
-		return scale;
+		super(attachmentLoader);
 	}
 	}
 
 
-	public void setScale (float scale) {
-		if (scale == 0) throw new IllegalArgumentException("scale cannot be 0.");
-		this.scale = scale;
+	public SkeletonJson (TextureAtlas atlas) {
+		super(atlas);
 	}
 	}
 
 
 	protected JsonValue parse (FileHandle file) {
 	protected JsonValue parse (FileHandle file) {
@@ -128,8 +111,6 @@ public class SkeletonJson {
 		if (skeletonMap != null) {
 		if (skeletonMap != null) {
 			skeletonData.hash = skeletonMap.getString("hash", null);
 			skeletonData.hash = skeletonMap.getString("hash", null);
 			skeletonData.version = skeletonMap.getString("spine", null);
 			skeletonData.version = skeletonMap.getString("spine", null);
-			if ("3.8.75".equals(skeletonData.version))
-				throw new RuntimeException("Unsupported skeleton data, please export with a newer version of Spine.");
 			skeletonData.x = skeletonMap.getFloat("x", 0);
 			skeletonData.x = skeletonMap.getFloat("x", 0);
 			skeletonData.y = skeletonMap.getFloat("y", 0);
 			skeletonData.y = skeletonMap.getFloat("y", 0);
 			skeletonData.width = skeletonMap.getFloat("width", 0);
 			skeletonData.width = skeletonMap.getFloat("width", 0);
@@ -491,7 +472,7 @@ public class SkeletonJson {
 		for (int i = 0, n = vertices.length; i < n;) {
 		for (int i = 0, n = vertices.length; i < n;) {
 			int boneCount = (int)vertices[i++];
 			int boneCount = (int)vertices[i++];
 			bones.add(boneCount);
 			bones.add(boneCount);
-			for (int nn = i + boneCount * 4; i < nn; i += 4) {
+			for (int nn = i + (boneCount << 2); i < nn; i += 4) {
 				bones.add((int)vertices[i]);
 				bones.add((int)vertices[i]);
 				weights.add(vertices[i + 1] * scale);
 				weights.add(vertices[i + 1] * scale);
 				weights.add(vertices[i + 2] * scale);
 				weights.add(vertices[i + 2] * scale);
@@ -544,10 +525,10 @@ public class SkeletonJson {
 						float na = Integer.parseInt(color.substring(6, 8), 16) / 255f;
 						float na = Integer.parseInt(color.substring(6, 8), 16) / 255f;
 						JsonValue curve = keyMap.get("curve");
 						JsonValue curve = keyMap.get("curve");
 						if (curve != null) {
 						if (curve != null) {
-							bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr);
-							bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng);
-							bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb);
-							bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, a, na);
+							bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1);
+							bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1);
+							bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1);
+							bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, a, na, 1);
 						}
 						}
 						time = time2;
 						time = time2;
 						r = nr;
 						r = nr;
@@ -589,13 +570,13 @@ public class SkeletonJson {
 						float nb2 = Integer.parseInt(color.substring(12, 14), 16) / 255f;
 						float nb2 = Integer.parseInt(color.substring(12, 14), 16) / 255f;
 						JsonValue curve = keyMap.get("curve");
 						JsonValue curve = keyMap.get("curve");
 						if (curve != null) {
 						if (curve != null) {
-							bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr);
-							bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng);
-							bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb);
-							bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, a, na);
-							bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, r2, nr2);
-							bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, g2, ng2);
-							bezier = readCurve(curve, timeline, bezier, frame, 6, time, time2, b2, nb2);
+							bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, r, nr, 1);
+							bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, g, ng, 1);
+							bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, b, nb, 1);
+							bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, a, na, 1);
+							bezier = readCurve(curve, timeline, bezier, frame, 4, time, time2, r2, nr2, 1);
+							bezier = readCurve(curve, timeline, bezier, frame, 5, time, time2, g2, ng2, 1);
+							bezier = readCurve(curve, timeline, bezier, frame, 6, time, time2, b2, nb2, 1);
 						}
 						}
 						time = time2;
 						time = time2;
 						r = nr;
 						r = nr;
@@ -660,8 +641,8 @@ public class SkeletonJson {
 				float mix2 = nextMap.getFloat("mix", 1), softness2 = nextMap.getFloat("softness", 0) * scale;
 				float mix2 = nextMap.getFloat("mix", 1), softness2 = nextMap.getFloat("softness", 0) * scale;
 				JsonValue curve = keyMap.get("curve");
 				JsonValue curve = keyMap.get("curve");
 				if (curve != null) {
 				if (curve != null) {
-					bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mix, mix2);
-					bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, softness, softness2);
+					bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, mix, mix2, 1);
+					bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, softness, softness2, 1);
 				}
 				}
 				time = time2;
 				time = time2;
 				mix = mix2;
 				mix = mix2;
@@ -693,10 +674,10 @@ public class SkeletonJson {
 				float scaleMix2 = nextMap.getFloat("scaleMix", 1), shearMix2 = nextMap.getFloat("shearMix", 1);
 				float scaleMix2 = nextMap.getFloat("scaleMix", 1), shearMix2 = nextMap.getFloat("shearMix", 1);
 				JsonValue curve = keyMap.get("curve");
 				JsonValue curve = keyMap.get("curve");
 				if (curve != null) {
 				if (curve != null) {
-					bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, rotateMix, rotateMix2);
-					bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, translateMix, translateMix2);
-					bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, scaleMix, scaleMix2);
-					bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, shearMix, shearMix2);
+					bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, rotateMix, rotateMix2, 1);
+					bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, translateMix, translateMix2, 1);
+					bezier = readCurve(curve, timeline, bezier, frame, 2, time, time2, scaleMix, scaleMix2, 1);
+					bezier = readCurve(curve, timeline, bezier, frame, 3, time, time2, shearMix, shearMix2, 1);
 				}
 				}
 				time = time2;
 				time = time2;
 				rotateMix = rotateMix2;
 				rotateMix = rotateMix2;
@@ -777,7 +758,7 @@ public class SkeletonJson {
 						}
 						}
 						float time2 = nextMap.getFloat("time", 0);
 						float time2 = nextMap.getFloat("time", 0);
 						JsonValue curve = keyMap.get("curve");
 						JsonValue curve = keyMap.get("curve");
-						if (curve != null) bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, 0, 1);
+						if (curve != null) bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, 0, 1, 1);
 						time = time2;
 						time = time2;
 						keyMap = nextMap;
 						keyMap = nextMap;
 					}
 					}
@@ -853,16 +834,16 @@ public class SkeletonJson {
 	}
 	}
 
 
 	private Timeline readTimeline (JsonValue keyMap, CurveTimeline1 timeline, float defaultValue, float scale) {
 	private Timeline readTimeline (JsonValue keyMap, CurveTimeline1 timeline, float defaultValue, float scale) {
-		float time = keyMap.getFloat("time", 0), value = keyMap.getFloat("value", defaultValue);
+		float time = keyMap.getFloat("time", 0), value = keyMap.getFloat("value", defaultValue) * scale;
 		int bezier = 0;
 		int bezier = 0;
 		for (int frame = 0;; frame++) {
 		for (int frame = 0;; frame++) {
 			timeline.setFrame(frame, time, value);
 			timeline.setFrame(frame, time, value);
 			JsonValue nextMap = keyMap.next;
 			JsonValue nextMap = keyMap.next;
 			if (nextMap == null) break;
 			if (nextMap == null) break;
 			float time2 = nextMap.getFloat("time", 0);
 			float time2 = nextMap.getFloat("time", 0);
-			float value2 = nextMap.getFloat("value", defaultValue);
+			float value2 = nextMap.getFloat("value", defaultValue) * scale;
 			JsonValue curve = keyMap.get("curve");
 			JsonValue curve = keyMap.get("curve");
-			if (curve != null) bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, value, value2);
+			if (curve != null) bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, value, value2, scale);
 			time = time2;
 			time = time2;
 			value = value2;
 			value = value2;
 			keyMap = nextMap;
 			keyMap = nextMap;
@@ -874,18 +855,18 @@ public class SkeletonJson {
 	private Timeline readTimeline (JsonValue keyMap, CurveTimeline2 timeline, String name1, String name2, float defaultValue,
 	private Timeline readTimeline (JsonValue keyMap, CurveTimeline2 timeline, String name1, String name2, float defaultValue,
 		float scale) {
 		float scale) {
 		float time = keyMap.getFloat("time", 0);
 		float time = keyMap.getFloat("time", 0);
-		float value1 = keyMap.getFloat(name1, defaultValue), value2 = keyMap.getFloat(name2, defaultValue);
+		float value1 = keyMap.getFloat(name1, defaultValue) * scale, value2 = keyMap.getFloat(name2, defaultValue) * scale;
 		int bezier = 0;
 		int bezier = 0;
 		for (int frame = 0;; frame++) {
 		for (int frame = 0;; frame++) {
 			timeline.setFrame(frame, time, value1, value2);
 			timeline.setFrame(frame, time, value1, value2);
 			JsonValue nextMap = keyMap.next;
 			JsonValue nextMap = keyMap.next;
 			if (nextMap == null) break;
 			if (nextMap == null) break;
 			float time2 = nextMap.getFloat("time", 0);
 			float time2 = nextMap.getFloat("time", 0);
-			float nvalue1 = nextMap.getFloat(name1, defaultValue), nvalue2 = nextMap.getFloat(name2, defaultValue);
+			float nvalue1 = nextMap.getFloat(name1, defaultValue) * scale, nvalue2 = nextMap.getFloat(name2, defaultValue) * scale;
 			JsonValue curve = keyMap.get("curve");
 			JsonValue curve = keyMap.get("curve");
 			if (curve != null) {
 			if (curve != null) {
-				bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, value1, nvalue1);
-				bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, value2, nvalue2);
+				bezier = readCurve(curve, timeline, bezier, frame, 0, time, time2, value1, nvalue1, scale);
+				bezier = readCurve(curve, timeline, bezier, frame, 1, time, time2, value2, nvalue2, scale);
 			}
 			}
 			time = time2;
 			time = time2;
 			value1 = nvalue1;
 			value1 = nvalue1;
@@ -897,18 +878,18 @@ public class SkeletonJson {
 	}
 	}
 
 
 	int readCurve (JsonValue curve, CurveTimeline timeline, int bezier, int frame, int value, float time1, float time2,
 	int readCurve (JsonValue curve, CurveTimeline timeline, int bezier, int frame, int value, float time1, float time2,
-		float value1, float value2) {
+		float value1, float value2, float scale) {
 		if (curve.isString()) {
 		if (curve.isString()) {
 			if (value != 0) timeline.setStepped(frame);
 			if (value != 0) timeline.setStepped(frame);
 		} else {
 		} else {
-			curve = curve.get(value * 4);
+			curve = curve.get(value << 2);
 			float cx1 = curve.asFloat();
 			float cx1 = curve.asFloat();
 			curve = curve.next;
 			curve = curve.next;
-			float cy1 = curve.asFloat();
+			float cy1 = curve.asFloat() * scale;
 			curve = curve.next;
 			curve = curve.next;
 			float cx2 = curve.asFloat();
 			float cx2 = curve.asFloat();
 			curve = curve.next;
 			curve = curve.next;
-			float cy2 = curve.asFloat();
+			float cy2 = curve.asFloat() * scale;
 			setBezier(timeline, frame, value, bezier++, time1, value1, cx1, cy1, cx2, cy2, time2, value2);
 			setBezier(timeline, frame, value, bezier++, time1, value1, cx1, cy1, cx2, cy2, time2, value2);
 		}
 		}
 		return bezier;
 		return bezier;

+ 46 - 0
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonLoader.java

@@ -0,0 +1,46 @@
+
+package com.esotericsoftware.spine;
+
+import com.badlogic.gdx.files.FileHandle;
+import com.badlogic.gdx.graphics.g2d.TextureAtlas;
+import com.badlogic.gdx.utils.Array;
+
+import com.esotericsoftware.spine.SkeletonJson.LinkedMesh;
+import com.esotericsoftware.spine.attachments.AtlasAttachmentLoader;
+import com.esotericsoftware.spine.attachments.AttachmentLoader;
+
+/** Base class for loading skeleton data from a file. */
+abstract public class SkeletonLoader {
+	final AttachmentLoader attachmentLoader;
+	float scale = 1;
+	final Array<LinkedMesh> linkedMeshes = new Array();
+
+	/** Creates a skeleton loader that loads attachments using an {@link AtlasAttachmentLoader} with the specified atlas. */
+	public SkeletonLoader (TextureAtlas atlas) {
+		attachmentLoader = new AtlasAttachmentLoader(atlas);
+	}
+
+	/** Creates a skeleton loader that loads attachments using the specified attachment loader.
+	 * <p>
+	 * See <a href='http://esotericsoftware.com/spine-loading-skeleton-data#JSON-and-binary-data'>Loading skeleton data</a> in the
+	 * Spine Runtimes Guide. */
+	public SkeletonLoader (AttachmentLoader attachmentLoader) {
+		if (attachmentLoader == null) throw new IllegalArgumentException("attachmentLoader cannot be null.");
+		this.attachmentLoader = attachmentLoader;
+	}
+
+	/** Scales bone positions, image sizes, and translations as they are loaded. This allows different size images to be used at
+	 * runtime than were used in Spine.
+	 * <p>
+	 * See <a href="http://esotericsoftware.com/spine-loading-skeleton-data#Scaling">Scaling</a> in the Spine Runtimes Guide. */
+	public float getScale () {
+		return scale;
+	}
+
+	public void setScale (float scale) {
+		if (scale == 0) throw new IllegalArgumentException("scale cannot be 0.");
+		this.scale = scale;
+	}
+
+	abstract public SkeletonData readSkeletonData (FileHandle file);
+}

+ 19 - 12
spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java

@@ -94,9 +94,10 @@ public class SkeletonViewer extends ApplicationAdapter {
 	static final float checkModifiedInterval = 0.250f;
 	static final float checkModifiedInterval = 0.250f;
 	static final float reloadDelay = 1;
 	static final float reloadDelay = 1;
 	static float uiScale = 1;
 	static float uiScale = 1;
+	static String[] startSuffixes = {"", "-pro", "-ess"};
 	static String[] dataSuffixes = {".json", ".skel"};
 	static String[] dataSuffixes = {".json", ".skel"};
-	static String[] atlasSuffixes = {".atlas", "-pro.atlas", "-ess.atlas"};
-	static String[] extraSuffixes = {"", ".txt", ".bytes"};
+	static String[] endSuffixes = {"", ".txt", ".bytes"};
+	static String[] atlasSuffixes = {".atlas", "-pma.atlas"};
 	static String[] args;
 	static String[] args;
 	static final String version = ""; // Replaced by build.
 	static final String version = ""; // Replaced by build.
 
 
@@ -146,12 +147,14 @@ public class SkeletonViewer extends ApplicationAdapter {
 
 
 	FileHandle atlasFile (FileHandle skeletonFile) {
 	FileHandle atlasFile (FileHandle skeletonFile) {
 		String baseName = skeletonFile.name();
 		String baseName = skeletonFile.name();
-		for (String extraSuffix : extraSuffixes) {
-			for (String dataSuffix : dataSuffixes) {
-				String suffix = dataSuffix + extraSuffix;
-				if (baseName.endsWith(suffix)) {
-					FileHandle file = atlasFile(skeletonFile, baseName.substring(0, baseName.length() - suffix.length()));
-					if (file != null) return file;
+		for (String startSuffix : startSuffixes) {
+			for (String endSuffix : endSuffixes) {
+				for (String dataSuffix : dataSuffixes) {
+					String suffix = startSuffix + dataSuffix + endSuffix;
+					if (baseName.endsWith(suffix)) {
+						FileHandle file = atlasFile(skeletonFile, baseName.substring(0, baseName.length() - suffix.length()));
+						if (file != null) return file;
+					}
 				}
 				}
 			}
 			}
 		}
 		}
@@ -159,10 +162,12 @@ public class SkeletonViewer extends ApplicationAdapter {
 	}
 	}
 
 
 	private FileHandle atlasFile (FileHandle skeletonFile, String baseName) {
 	private FileHandle atlasFile (FileHandle skeletonFile, String baseName) {
-		for (String extraSuffix : extraSuffixes) {
-			for (String suffix : atlasSuffixes) {
-				FileHandle file = skeletonFile.sibling(baseName + suffix + extraSuffix);
-				if (file.exists()) return file;
+		for (String startSuffix : startSuffixes) {
+			for (String endSuffix : endSuffixes) {
+				for (String suffix : atlasSuffixes) {
+					FileHandle file = skeletonFile.sibling(baseName + startSuffix + suffix + endSuffix);
+					if (file.exists()) return file;
+				}
 			}
 			}
 		}
 		}
 		return null;
 		return null;
@@ -238,6 +243,7 @@ public class SkeletonViewer extends ApplicationAdapter {
 
 
 		state = new AnimationState(new AnimationStateData(skeletonData));
 		state = new AnimationState(new AnimationStateData(skeletonData));
 		state.addListener(new AnimationStateAdapter() {
 		state.addListener(new AnimationStateAdapter() {
+
 			public void event (TrackEntry entry, Event event) {
 			public void event (TrackEntry entry, Event event) {
 				ui.toast(event.getData().getName());
 				ui.toast(event.getData().getName());
 			}
 			}
@@ -254,6 +260,7 @@ public class SkeletonViewer extends ApplicationAdapter {
 
 
 		ui.window.getTitleLabel().setText(skeletonFile.name());
 		ui.window.getTitleLabel().setText(skeletonFile.name());
 		{
 		{
+
 			Array<String> items = new Array();
 			Array<String> items = new Array();
 			for (Skin skin : skeletonData.getSkins())
 			for (Skin skin : skeletonData.getSkins())
 				items.add(skin.getName());
 				items.add(skin.getName());