Browse Source

Updated for Spine 3.0.00.

NathanSweet 9 years ago
parent
commit
37f2f9e880
34 changed files with 1323 additions and 689 deletions
  1. 4 4
      LICENSE
  2. 2 2
      spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/BonePlotting.java
  3. 1 1
      spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/Box2DExample.java
  4. 2 2
      spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/SimpleTest3.java
  5. 4 4
      spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/SkeletonAttachmentTest.java
  6. 0 58
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java
  7. 13 1
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java
  8. 147 109
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java
  9. 1 20
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java
  10. 133 54
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java
  11. 69 71
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java
  12. 0 19
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java
  13. 23 5
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonData.java
  14. 0 20
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java
  15. 108 0
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonMeshRenderer.java
  16. 10 77
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java
  17. 3 4
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRendererDebug.java
  18. 76 0
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraint.java
  19. 61 0
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraintData.java
  20. 6 0
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Updatable.java
  21. 4 4
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/BoundingBoxAttachment.java
  22. 4 2
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/MeshAttachment.java
  23. 4 2
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionAttachment.java
  24. 2 2
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionSequenceAttachment.java
  25. 7 5
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/SkinnedMeshAttachment.java
  26. 68 0
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/SkeletonActor.java
  27. 102 0
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/SkeletonActorPool.java
  28. 28 0
      spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/SkeletonPool.java
  29. 206 0
      spine-libgdx/spine-skeletonviewer/assets/skin/font-calibri-12.fnt
  30. BIN
      spine-libgdx/spine-skeletonviewer/assets/skin/font-calibri-12.png
  31. 124 130
      spine-libgdx/spine-skeletonviewer/assets/skin/skin.atlas
  32. 49 42
      spine-libgdx/spine-skeletonviewer/assets/skin/skin.json
  33. BIN
      spine-libgdx/spine-skeletonviewer/assets/skin/skin.png
  34. 62 51
      spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java

+ 4 - 4
LICENSE

@@ -1,7 +1,7 @@
 Spine Runtimes Software License
-Version 2.3
+Version 2.4
 
-Copyright (c) 2013-2015, Esoteric Software
+Copyright (c) 2013-2016, Esoteric Software
 All rights reserved.
 
 You are granted a perpetual, non-exclusive, non-sublicensable and
@@ -9,8 +9,8 @@ non-transferable license to use, install, execute and perform the Spine
 Runtimes Software (the "Software") and derivative works solely for personal
 or internal use. Without the written permission of Esoteric Software (see
 Section 2 of the Spine Software License Agreement), you may not (a) modify,
-translate, adapt or otherwise create derivative works, improvements of the
-Software or develop new applications using the Software or (b) remove,
+translate, adapt or otherwise create derivative works, improvements of
+the Software or develop new applications using the Software or (b) remove,
 delete, alter or obscure any trademarks or any copyright, trademark, patent
 or other intellectual property or proprietary rights notices on or in the
 Software, including any copy thereof. Redistributions in binary or source

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

@@ -67,8 +67,8 @@ public class BonePlotting {
 			while (time < animation.getDuration()) {
 				animation.apply(skeleton, time, time, false, null);
 				skeleton.updateWorldTransform();
-				System.out.println(animation.getName() + "," + bone.getWorldX() + "," + bone.getWorldY() + ","
-					+ bone.getWorldRotation() + "," + bone.getWorldScaleX() + "," + bone.getWorldScaleY());
+				System.out
+					.println(animation.getName() + "," + bone.getWorldX() + "," + bone.getWorldY() + "," + bone.getWorldRotationX());
 				time += fps;
 			}
 		}

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

@@ -160,7 +160,7 @@ public class Box2DExample extends ApplicationAdapter {
 			if (attachment.body == null) continue;
 			float x = skeleton.x + slot.getBone().getWorldX();
 			float y = skeleton.y + slot.getBone().getWorldY();
-			float rotation = slot.getBone().getWorldRotation();
+			float rotation = slot.getBone().getWorldRotationX();
 			attachment.body.setTransform(x, y, rotation * MathUtils.degRad);
 		}
 

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

@@ -42,7 +42,7 @@ import com.badlogic.gdx.graphics.g2d.TextureAtlas;
 public class SimpleTest3 extends ApplicationAdapter {
 	OrthographicCamera camera;
 	PolygonSpriteBatch batch;
-	SkeletonRenderer renderer;
+	SkeletonMeshRenderer renderer;
 	SkeletonRendererDebug debugRenderer;
 
 	TextureAtlas atlas;
@@ -52,7 +52,7 @@ public class SimpleTest3 extends ApplicationAdapter {
 	public void create () {
 		camera = new OrthographicCamera();
 		batch = new PolygonSpriteBatch(); // Required to render meshes. SpriteBatch can't render meshes.
-		renderer = new SkeletonRenderer();
+		renderer = new SkeletonMeshRenderer();
 		renderer.setPremultipliedAlpha(true);
 		debugRenderer = new SkeletonRendererDebug();
 		debugRenderer.setMeshTriangles(false);

+ 4 - 4
spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/SkeletonAttachmentTest.java

@@ -45,7 +45,7 @@ import com.badlogic.gdx.graphics.g2d.TextureAtlas;
 public class SkeletonAttachmentTest extends ApplicationAdapter {
 	OrthographicCamera camera;
 	PolygonSpriteBatch batch;
-	SkeletonRenderer renderer;
+	SkeletonMeshRenderer renderer;
 
 	Skeleton spineboy, goblin;
 	AnimationState spineboyState, goblinState;
@@ -53,7 +53,7 @@ public class SkeletonAttachmentTest extends ApplicationAdapter {
 	public void create () {
 		camera = new OrthographicCamera();
 		batch = new PolygonSpriteBatch();
-		renderer = new SkeletonRenderer();
+		renderer = new SkeletonMeshRenderer();
 		renderer.setPremultipliedAlpha(true);
 
 		{
@@ -77,9 +77,9 @@ public class SkeletonAttachmentTest extends ApplicationAdapter {
 		}
 
 		{
-			TextureAtlas atlas = new TextureAtlas(Gdx.files.internal("goblins/goblins-ffd.atlas"));
+			TextureAtlas atlas = new TextureAtlas(Gdx.files.internal("goblins/goblins-mesh.atlas"));
 			SkeletonJson json = new SkeletonJson(atlas);
-			SkeletonData skeletonData = json.readSkeletonData(Gdx.files.internal("goblins/goblins-ffd.json"));
+			SkeletonData skeletonData = json.readSkeletonData(Gdx.files.internal("goblins/goblins-mesh.json"));
 			goblin = new Skeleton(skeletonData);
 			goblin.setSkin("goblin");
 			goblin.setSlotsToSetupPose();

+ 0 - 58
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Animation.java

@@ -777,62 +777,4 @@ public class Animation {
 			ikConstraint.bendDirection = (int)frames[frameIndex + PREV_FRAME_BEND_DIRECTION];
 		}
 	}
-
-	static public class FlipXTimeline implements Timeline {
-		int boneIndex;
-		final float[] frames; // time, flip, ...
-
-		public FlipXTimeline (int frameCount) {
-			frames = new float[frameCount << 1];
-		}
-
-		public void setBoneIndex (int boneIndex) {
-			this.boneIndex = boneIndex;
-		}
-
-		public int getBoneIndex () {
-			return boneIndex;
-		}
-
-		public int getFrameCount () {
-			return frames.length >> 1;
-		}
-
-		public float[] getFrames () {
-			return frames;
-		}
-
-		/** Sets the time and value of the specified keyframe. */
-		public void setFrame (int frameIndex, float time, boolean flip) {
-			frameIndex *= 2;
-			frames[frameIndex] = time;
-			frames[frameIndex + 1] = flip ? 1 : 0;
-		}
-
-		public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> events, float alpha) {
-			float[] frames = this.frames;
-			if (time < frames[0]) {
-				if (lastTime > time) apply(skeleton, lastTime, Integer.MAX_VALUE, null, 0);
-				return;
-			} else if (lastTime > time) //
-				lastTime = -1;
-			int frameIndex = (time >= frames[frames.length - 2] ? frames.length : binarySearch(frames, time, 2)) - 2;
-			if (frames[frameIndex] < lastTime) return;
-			setFlip(skeleton.bones.get(boneIndex), frames[frameIndex + 1] != 0);
-		}
-
-		protected void setFlip (Bone bone, boolean flip) {
-			bone.setFlipX(flip);
-		}
-	}
-
-	static public class FlipYTimeline extends FlipXTimeline {
-		public FlipYTimeline (int frameCount) {
-			super(frameCount);
-		}
-
-		protected void setFlip (Bone bone, boolean flip) {
-			bone.setFlipY(flip);
-		}
-	}
 }

+ 13 - 1
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java

@@ -37,7 +37,7 @@ import com.badlogic.gdx.utils.Pool.Poolable;
 
 /** Stores state for an animation and automatically mixes between animations. */
 public class AnimationState {
-	private final AnimationStateData data;
+	private AnimationStateData data;
 	private Array<TrackEntry> tracks = new Array();
 	private final Array<Event> events = new Array();
 	private final Array<AnimationStateListener> listeners = new Array();
@@ -49,6 +49,10 @@ public class AnimationState {
 		}
 	};
 
+	/** Creates an uninitialized AnimationState. The animation state data must be set. */
+	public AnimationState () {
+	}
+
 	public AnimationState (AnimationStateData data) {
 		if (data == null) throw new IllegalArgumentException("data cannot be null.");
 		this.data = data;
@@ -269,6 +273,10 @@ public class AnimationState {
 		listeners.removeValue(listener, true);
 	}
 
+	public void clearListeners () {
+		listeners.clear();
+	}
+
 	public float getTimeScale () {
 		return timeScale;
 	}
@@ -281,6 +289,10 @@ public class AnimationState {
 		return data;
 	}
 
+	public void setData (AnimationStateData data) {
+		this.data = data;
+	}
+
 	/** Returns the list of tracks that have animations, which may contain nulls. */
 	public Array<TrackEntry> getTracks () {
 		return tracks;

+ 147 - 109
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java

@@ -31,26 +31,23 @@
 
 package com.esotericsoftware.spine;
 
+import static com.badlogic.gdx.math.MathUtils.*;
 import static com.badlogic.gdx.math.Matrix3.*;
 
 import com.badlogic.gdx.math.MathUtils;
 import com.badlogic.gdx.math.Matrix3;
 import com.badlogic.gdx.math.Vector2;
 
-public class Bone {
+public class Bone implements Updatable {
 	final BoneData data;
 	final Skeleton skeleton;
 	final Bone parent;
-	float x, y;
-	float rotation, rotationIK;
-	float scaleX, scaleY;
-	boolean flipX, flipY;
+	float x, y, rotation, scaleX, scaleY;
+	float appliedRotation, appliedScaleX, appliedScaleY;
 
-	float m00, m01, worldX; // a b x
-	float m10, m11, worldY; // c d y
-	float worldRotation;
-	float worldScaleX, worldScaleY;
-	boolean worldFlipX, worldFlipY;
+	float a, b, worldX;
+	float c, d, worldY;
+	float worldSignX, worldSignY;
 
 	Bone (BoneData data) {
 		this.data = data;
@@ -78,69 +75,135 @@ public class Bone {
 		x = bone.x;
 		y = bone.y;
 		rotation = bone.rotation;
-		rotationIK = bone.rotationIK;
 		scaleX = bone.scaleX;
 		scaleY = bone.scaleY;
-		flipX = bone.flipX;
-		flipY = bone.flipY;
 	}
 
-	/** Computes the world SRT using the parent bone and the local SRT. */
+	/** Computes the world SRT using the parent bone and this bone's local SRT. */
 	public void updateWorldTransform () {
-		Skeleton skeleton = this.skeleton;
+		updateWorldTransform(x, y, rotation, scaleX, scaleY);
+	}
+
+	/** Computes the world SRT using the parent bone and the specified local SRT. */
+	public void updateWorldTransform (float x, float y, float rotation, float scaleX, float scaleY) {
+		appliedRotation = rotation;
+		appliedScaleX = scaleX;
+		appliedScaleY = scaleY;
+
+		float cos = MathUtils.cosDeg(rotation), sin = MathUtils.sinDeg(rotation);
+		float la = cos * scaleX, lb = -sin * scaleY, lc = sin * scaleX, ld = cos * scaleY;
 		Bone parent = this.parent;
-		float x = this.x, y = this.y;
-		if (parent != null) {
-			worldX = x * parent.m00 + y * parent.m01 + parent.worldX;
-			worldY = x * parent.m10 + y * parent.m11 + parent.worldY;
-			if (data.inheritScale) {
-				worldScaleX = parent.worldScaleX * scaleX;
-				worldScaleY = parent.worldScaleY * scaleY;
-			} else {
-				worldScaleX = scaleX;
-				worldScaleY = scaleY;
-			}
-			worldRotation = data.inheritRotation ? parent.worldRotation + rotationIK : rotationIK;
-			worldFlipX = parent.worldFlipX ^ flipX;
-			worldFlipY = parent.worldFlipY ^ flipY;
-		} else {
+		if (parent == null) { // Root bone.
+			Skeleton skeleton = this.skeleton;
 			boolean skeletonFlipX = skeleton.flipX, skeletonFlipY = skeleton.flipY;
-			worldX = skeletonFlipX ? -x : x;
-			worldY = skeletonFlipY ? -y : y;
-			worldScaleX = scaleX;
-			worldScaleY = scaleY;
-			worldRotation = rotationIK;
-			worldFlipX = skeletonFlipX ^ flipX;
-			worldFlipY = skeletonFlipY ^ flipY;
-		}
-		float cos = MathUtils.cosDeg(worldRotation);
-		float sin = MathUtils.sinDeg(worldRotation);
-		if (worldFlipX) {
-			m00 = -cos * worldScaleX;
-			m01 = sin * worldScaleY;
-		} else {
-			m00 = cos * worldScaleX;
-			m01 = -sin * worldScaleY;
+			if (skeletonFlipX) {
+				scaleX = -scaleX;
+				x = -x;
+			}
+			if (skeletonFlipY) {
+				scaleY = -scaleY;
+				y = -y;
+			}
+			a = la;
+			b = lb;
+			c = lc;
+			d = ld;
+			worldX = x;
+			worldY = y;
+			worldSignX = Math.signum(scaleX);
+			worldSignY = Math.signum(scaleY);
+			return;
 		}
-		if (worldFlipY) {
-			m10 = -sin * worldScaleX;
-			m11 = -cos * worldScaleY;
+
+		float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
+		worldX = pa * x + pb * y + parent.worldX;
+		worldY = pc * x + pd * y + parent.worldY;
+		worldSignX = parent.worldSignX * Math.signum(scaleX);
+		worldSignY = parent.worldSignY * Math.signum(scaleY);
+
+		if (data.inheritRotation && data.inheritScale) {
+			a = pa * la + pb * lc;
+			b = pa * lb + pb * ld;
+			c = pc * la + pd * lc;
+			d = pc * lb + pd * ld;
+		} else if (data.inheritRotation) { // No scale inheritance.
+			Bone p = parent;
+			pa = 1;
+			pb = 0;
+			pc = 0;
+			pd = 1;
+			while (p != null) {
+				cos = MathUtils.cosDeg(p.appliedRotation);
+				sin = MathUtils.sinDeg(p.appliedRotation);
+				float a = pa * cos + pb * sin;
+				float b = pa * -sin + pb * cos;
+				float c = pc * cos + pd * sin;
+				float d = pc * -sin + pd * cos;
+				pa = a;
+				pb = b;
+				pc = c;
+				pd = d;
+				p = p.parent;
+			}
+			a = pa * la + pb * lc;
+			b = pa * lb + pb * ld;
+			c = pc * la + pd * lc;
+			d = pc * lb + pd * ld;
+		} else if (data.inheritScale) { // No rotation inheritance.
+			Bone p = parent;
+			pa = 1;
+			pb = 0;
+			pc = 0;
+			pd = 1;
+			while (p != null) {
+				float r = p.rotation;
+				cos = MathUtils.cosDeg(r);
+				sin = MathUtils.sinDeg(r);
+				float psx = p.appliedScaleX, psy = p.appliedScaleY;
+				float za = cos * psx, zb = -sin * psy, zc = sin * psx, zd = cos * psy;
+				float temp = pa * za + pb * zc;
+				pb = pa * zb + pb * zd;
+				pa = temp;
+				temp = pc * za + pd * zc;
+				pd = pc * zb + pd * zd;
+				pc = temp;
+
+				if (psx < 0) r = PI - r;
+				cos = MathUtils.cosDeg(-r);
+				sin = MathUtils.sinDeg(-r);
+				temp = pa * cos + pb * sin;
+				pb = pa * -sin + pb * cos;
+				pa = temp;
+				temp = pc * cos + pd * sin;
+				pd = pc * -sin + pd * cos;
+				pc = temp;
+
+				p = p.parent;
+			}
+			a = pa * la + pb * lc;
+			b = pa * lb + pb * ld;
+			c = pc * la + pd * lc;
+			d = pc * lb + pd * ld;
 		} else {
-			m10 = sin * worldScaleX;
-			m11 = cos * worldScaleY;
+			a = la;
+			b = lb;
+			c = lc;
+			d = ld;
 		}
 	}
 
+	/** Same as {@link #updateWorldTransform()}. This method exists for Bone to implement {@link Updatable}. */
+	public void update () {
+		updateWorldTransform(x, y, rotation, scaleX, scaleY);
+	}
+
 	public void setToSetupPose () {
 		BoneData data = this.data;
 		x = data.x;
 		y = data.y;
 		rotation = data.rotation;
-		rotationIK = rotation;
 		scaleX = data.scaleX;
 		scaleY = data.scaleY;
-		flipX = data.flipX;
-		flipY = data.flipY;
 	}
 
 	public BoneData getData () {
@@ -185,15 +248,6 @@ public class Bone {
 		this.rotation = rotation;
 	}
 
-	/** Returns the inverse kinetics rotation, as calculated by any IK constraints. */
-	public float getRotationIK () {
-		return rotationIK;
-	}
-
-	public void setRotationIK (float rotationIK) {
-		this.rotationIK = rotationIK;
-	}
-
 	public float getScaleX () {
 		return scaleX;
 	}
@@ -220,36 +274,20 @@ public class Bone {
 		scaleY = scale;
 	}
 
-	public boolean getFlipX () {
-		return flipX;
-	}
-
-	public void setFlipX (boolean flipX) {
-		this.flipX = flipX;
-	}
-
-	public boolean getFlipY () {
-		return flipY;
+	public float getA () {
+		return a;
 	}
 
-	public void setFlipY (boolean flipY) {
-		this.flipY = flipY;
+	public float getB () {
+		return b;
 	}
 
-	public float getM00 () {
-		return m00;
+	public float getC () {
+		return c;
 	}
 
-	public float getM01 () {
-		return m01;
-	}
-
-	public float getM10 () {
-		return m10;
-	}
-
-	public float getM11 () {
-		return m11;
+	public float getD () {
+		return d;
 	}
 
 	public float getWorldX () {
@@ -260,33 +298,37 @@ public class Bone {
 		return worldY;
 	}
 
-	public float getWorldRotation () {
-		return worldRotation;
+	public float getWorldRotationX () {
+		return (float)Math.atan2(c, a) * MathUtils.radDeg;
+	}
+
+	public float getWorldRotationY () {
+		return (float)Math.atan2(d, b) * MathUtils.radDeg;
 	}
 
 	public float getWorldScaleX () {
-		return worldScaleX;
+		return (float)Math.sqrt(a * a + b * b) * worldSignX;
 	}
 
 	public float getWorldScaleY () {
-		return worldScaleY;
+		return (float)Math.sqrt(c * c + d * d) * worldSignY;
 	}
 
-	public boolean getWorldFlipX () {
-		return worldFlipX;
+	public float getWorldSignX () {
+		return worldSignX;
 	}
 
-	public boolean getWorldFlipY () {
-		return worldFlipY;
+	public float getWorldSignY () {
+		return worldSignY;
 	}
 
 	public Matrix3 getWorldTransform (Matrix3 worldTransform) {
 		if (worldTransform == null) throw new IllegalArgumentException("worldTransform cannot be null.");
 		float[] val = worldTransform.val;
-		val[M00] = m00;
-		val[M01] = m01;
-		val[M10] = m10;
-		val[M11] = m11;
+		val[M00] = a;
+		val[M01] = b;
+		val[M10] = c;
+		val[M11] = d;
 		val[M02] = worldX;
 		val[M12] = worldY;
 		val[M20] = 0;
@@ -297,21 +339,17 @@ public class Bone {
 
 	public Vector2 worldToLocal (Vector2 world) {
 		float x = world.x - worldX, y = world.y - worldY;
-		float m00 = this.m00, m10 = this.m10, m01 = this.m01, m11 = this.m11;
-		if (worldFlipX != worldFlipY) {
-			m00 = -m00;
-			m11 = -m11;
-		}
-		float invDet = 1 / (m00 * m11 - m01 * m10);
-		world.x = (x * m00 * invDet - y * m01 * invDet);
-		world.y = (y * m11 * invDet - x * m10 * invDet);
+		float a = this.a, b = this.b, c = this.c, d = this.d;
+		float invDet = 1 / (a * d - b * c);
+		world.x = (x * a * invDet - y * b * invDet);
+		world.y = (y * d * invDet - x * c * invDet);
 		return world;
 	}
 
 	public Vector2 localToWorld (Vector2 local) {
 		float x = local.x, y = local.y;
-		local.x = x * m00 + y * m01 + worldX;
-		local.y = x * m10 + y * m11 + worldY;
+		local.x = x * a + y * b + worldX;
+		local.y = x * c + y * d + worldY;
 		return local;
 	}
 

+ 1 - 20
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/BoneData.java

@@ -40,8 +40,7 @@ public class BoneData {
 	float x, y;
 	float rotation;
 	float scaleX = 1, scaleY = 1;
-	boolean flipX, flipY;
-	boolean inheritScale = true, inheritRotation = true;
+	boolean inheritScale, inheritRotation;
 
 	// Nonessential.
 	final Color color = new Color(0.61f, 0.61f, 0.61f, 1);
@@ -65,8 +64,6 @@ public class BoneData {
 		rotation = bone.rotation;
 		scaleX = bone.scaleX;
 		scaleY = bone.scaleY;
-		flipX = bone.flipX;
-		flipY = bone.flipY;
 	}
 
 	/** @return May be null. */
@@ -136,22 +133,6 @@ public class BoneData {
 		this.scaleY = scaleY;
 	}
 
-	public boolean getFlipX () {
-		return flipX;
-	}
-
-	public void setFlipX (boolean flipX) {
-		this.flipX = flipX;
-	}
-
-	public boolean getFlipY () {
-		return flipY;
-	}
-
-	public void setFlipY (boolean flipY) {
-		this.flipY = flipY;
-	}
-
 	public boolean getInheritScale () {
 		return inheritScale;
 	}

+ 133 - 54
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/IkConstraint.java

@@ -33,12 +33,9 @@ package com.esotericsoftware.spine;
 
 import static com.badlogic.gdx.math.MathUtils.*;
 
-import com.badlogic.gdx.math.Vector2;
 import com.badlogic.gdx.utils.Array;
 
-public class IkConstraint {
-	static private final Vector2 temp = new Vector2();
-
+public class IkConstraint implements Updatable {
 	final IkConstraintData data;
 	final Array<Bone> bones;
 	Bone target;
@@ -59,15 +56,21 @@ public class IkConstraint {
 	}
 
 	/** Copy constructor. */
-	public IkConstraint (IkConstraint ikConstraint, Array<Bone> bones, Bone target) {
+	public IkConstraint (IkConstraint ikConstraint, Skeleton skeleton) {
 		data = ikConstraint.data;
-		this.bones = bones;
-		this.target = target;
+		bones = new Array(ikConstraint.bones.size);
+		for (Bone bone : ikConstraint.bones)
+			bones.add(skeleton.bones.get(bone.skeleton.bones.indexOf(bone, true)));
+		target = skeleton.bones.get(ikConstraint.target.skeleton.bones.indexOf(ikConstraint.target, true));
 		mix = ikConstraint.mix;
 		bendDirection = ikConstraint.bendDirection;
 	}
 
 	public void apply () {
+		update();
+	}
+
+	public void update () {
 		Bone target = this.target;
 		Array<Bone> bones = this.bones;
 		switch (bones.size) {
@@ -113,67 +116,143 @@ public class IkConstraint {
 	}
 
 	public String toString () {
-		return data.name;
+		return data.name + " CONSTRAINT";
 	}
 
 	/** Adjusts the bone rotation so the tip is as close to the target position as possible. The target is specified in the world
 	 * coordinate system. */
 	static public void apply (Bone bone, float targetX, float targetY, float alpha) {
-		float parentRotation = (!bone.data.inheritRotation || bone.parent == null) ? 0 : bone.parent.worldRotation;
+		float parentRotation = bone.parent == null ? 0 : bone.parent.getWorldRotationX();
 		float rotation = bone.rotation;
-		float rotationIK = (float)Math.atan2(targetY - bone.worldY, targetX - bone.worldX) * radDeg - parentRotation;
-		bone.rotationIK = rotation + (rotationIK - rotation) * alpha;
+		float rotationIK = atan2(targetY - bone.worldY, targetX - bone.worldX) * radDeg - parentRotation;
+		if (rotationIK > 180)
+			rotationIK -= 360;
+		else if (rotationIK < -180) rotationIK += 360;
+		bone.updateWorldTransform(bone.x, bone.y, rotation + (rotationIK - rotation) * alpha, bone.scaleX, bone.scaleY);
 	}
 
 	/** Adjusts the parent and child bone rotations so the tip of the child is as close to the target position as possible. The
 	 * target is specified in the world coordinate system.
 	 * @param child Any descendant bone of the parent. */
-	static public void apply (Bone parent, Bone child, float targetX, float targetY, int bendDirection, float alpha) {
-		float childRotation = child.rotation, parentRotation = parent.rotation;
-		if (alpha == 0) {
-			child.rotationIK = childRotation;
-			parent.rotationIK = parentRotation;
-			return;
+	static public void apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, float alpha) {
+		if (alpha == 0) return;
+		float px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX, cy = child.y;
+		int offset1, offset2, sign2;
+		if (psx < 0) {
+			psx = -psx;
+			offset1 = 180;
+			sign2 = -1;
+		} else {
+			offset1 = 0;
+			sign2 = 1;
+		}
+		if (psy < 0) {
+			psy = -psy;
+			sign2 = -sign2;
 		}
-		Vector2 position = temp;
-		Bone parentParent = parent.parent;
-		if (parentParent != null) {
-			parentParent.worldToLocal(position.set(targetX, targetY));
-			targetX = (position.x - parent.x) * parentParent.worldScaleX;
-			targetY = (position.y - parent.y) * parentParent.worldScaleY;
+		if (csx < 0) {
+			csx = -csx;
+			offset2 = 180;
+		} else
+			offset2 = 0;
+		Bone pp = parent.parent;
+		float tx, ty, dx, dy;
+		if (pp == null) {
+			tx = targetX - px;
+			ty = targetY - py;
+			dx = child.worldX - px;
+			dy = child.worldY - py;
 		} else {
-			targetX -= parent.x;
-			targetY -= parent.y;
+			float a = pp.a, b = pp.b, c = pp.c, d = pp.d, invDet = 1 / (a * d - b * c);
+			float wx = pp.worldX, wy = pp.worldY, x = targetX - wx, y = targetY - wy;
+			tx = (x * d - y * b) * invDet - px;
+			ty = (y * a - x * c) * invDet - py;
+			x = child.worldX - wx;
+			y = child.worldY - wy;
+			dx = (x * d - y * b) * invDet - px;
+			dy = (y * a - x * c) * invDet - py;
 		}
-		if (child.parent == parent)
-			position.set(child.x, child.y);
-		else
-			parent.worldToLocal(child.parent.localToWorld(position.set(child.x, child.y)));
-		float childX = position.x * parent.worldScaleX, childY = position.y * parent.worldScaleY;
-		float offset = (float)Math.atan2(childY, childX);
-		float len1 = (float)Math.sqrt(childX * childX + childY * childY), len2 = child.data.length * child.worldScaleX;
-		// Based on code by Ryan Juckett with permission: Copyright (c) 2008-2009 Ryan Juckett, http://www.ryanjuckett.com/
-		float cosDenom = 2 * len1 * len2;
-		if (cosDenom < 0.0001f) {
-			child.rotationIK = childRotation + ((float)Math.atan2(targetY, targetX) * radDeg - parentRotation - childRotation)
-				* alpha;
-			return;
+		float l1 = (float)Math.sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2;
+		outer:
+		if (Math.abs(psx - psy) <= 0.0001f) {
+			l2 *= psx;
+			float cos = (tx * tx + ty * ty - l1 * l1 - l2 * l2) / (2 * l1 * l2);
+			if (cos < -1)
+				cos = -1;
+			else if (cos > 1) cos = 1;
+			a2 = (float)Math.acos(cos) * bendDir;
+			float a = l1 + l2 * cos, o = l2 * sin(a2);
+			a1 = atan2(ty * a - tx * o, tx * a + ty * o);
+		} else {
+			cy = 0;
+			float a = psx * l2, b = psy * l2, ta = atan2(ty, tx);
+			float aa = a * a, bb = b * b, ll = l1 * l1, dd = tx * tx + ty * ty;
+			float c0 = bb * ll + aa * dd - aa * bb, c1 = -2 * bb * l1, c2 = bb - aa;
+			float d = c1 * c1 - 4 * c2 * c0;
+			if (d >= 0) {
+				float q = (float)Math.sqrt(d);
+				if (c1 < 0) q = -q;
+				q = -(c1 + q) / 2;
+				float r0 = q / c2, r1 = c0 / q;
+				float r = Math.abs(r0) < Math.abs(r1) ? r0 : r1;
+				if (r * r <= dd) {
+					float y = (float)Math.sqrt(dd - r * r) * bendDir;
+					a1 = ta - atan2(y, r);
+					a2 = atan2(y / psy, (r - l1) / psx);
+					break outer;
+				}
+			}
+			float minAngle = 0, minDist = Float.MAX_VALUE, minX = 0, minY = 0;
+			float maxAngle = 0, maxDist = 0, maxX = 0, maxY = 0;
+			float x = l1 + a, dist = x * x;
+			if (dist > maxDist) {
+				maxAngle = 0;
+				maxDist = dist;
+				maxX = x;
+			}
+			x = l1 - a;
+			dist = x * x;
+			if (dist < minDist) {
+				minAngle = PI;
+				minDist = dist;
+				minX = x;
+			}
+			float angle = (float)Math.acos(-a * l1 / (aa - bb));
+			x = a * cos(angle) + l1;
+			float y = b * sin(angle);
+			dist = x * x + y * y;
+			if (dist < minDist) {
+				minAngle = angle;
+				minDist = dist;
+				minX = x;
+				minY = y;
+			}
+			if (dist > maxDist) {
+				maxAngle = angle;
+				maxDist = dist;
+				maxX = x;
+				maxY = y;
+			}
+			if (dd <= (minDist + maxDist) / 2) {
+				a1 = ta - atan2(minY * bendDir, minX);
+				a2 = minAngle * bendDir;
+			} else {
+				a1 = ta - atan2(maxY * bendDir, maxX);
+				a2 = maxAngle * bendDir;
+			}
 		}
-		float cos = clamp((targetX * targetX + targetY * targetY - len1 * len1 - len2 * len2) / cosDenom, -1, 1);
-		float childAngle = (float)Math.acos(cos) * bendDirection;
-		float adjacent = len1 + len2 * cos, opposite = len2 * sin(childAngle);
-		float parentAngle = (float)Math.atan2(targetY * adjacent - targetX * opposite, targetX * adjacent + targetY * opposite);
-		float rotation = (parentAngle - offset) * radDeg - parentRotation;
-		if (rotation > 180)
-			rotation -= 360;
-		else if (rotation < -180) //
-			rotation += 360;
-		parent.rotationIK = parentRotation + rotation * alpha;
-		rotation = (childAngle + offset) * radDeg - childRotation;
-		if (rotation > 180)
-			rotation -= 360;
-		else if (rotation < -180) //
-			rotation += 360;
-		child.rotationIK = childRotation + (rotation + parent.worldRotation - child.parent.worldRotation) * alpha;
+		float offset = atan2(cy, child.x) * sign2;
+		a1 = (a1 - offset) * radDeg + offset1;
+		a2 = (a2 + offset) * radDeg * sign2 + offset2;
+		if (a1 > 180)
+			a1 -= 360;
+		else if (a1 < -180) a1 += 360;
+		if (a2 > 180)
+			a2 -= 360;
+		else if (a2 < -180) a2 += 360;
+		float rotation = parent.rotation;
+		parent.updateWorldTransform(parent.x, parent.y, rotation + (a1 - rotation) * alpha, parent.scaleX, parent.scaleY);
+		rotation = child.rotation;
+		child.updateWorldTransform(child.x, cy, rotation + (a2 - rotation) * alpha, child.scaleX, child.scaleY);
 	}
 }

+ 69 - 71
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java

@@ -45,7 +45,8 @@ public class Skeleton {
 	final Array<Slot> slots;
 	Array<Slot> drawOrder;
 	final Array<IkConstraint> ikConstraints;
-	private final Array<Array<Bone>> boneCache = new Array();
+	final Array<TransformConstraint> transformConstraints;
+	private final Array<Updatable> updateCache = new Array();
 	Skin skin;
 	final Color color;
 	float time;
@@ -75,6 +76,10 @@ public class Skeleton {
 		for (IkConstraintData ikConstraintData : data.ikConstraints)
 			ikConstraints.add(new IkConstraint(ikConstraintData, this));
 
+		transformConstraints = new Array(data.transformConstraints.size);
+		for (TransformConstraintData transformConstraintData : data.transformConstraints)
+			transformConstraints.add(new TransformConstraint(transformConstraintData, this));
+
 		color = new Color(1, 1, 1, 1);
 
 		updateCache();
@@ -102,13 +107,12 @@ public class Skeleton {
 			drawOrder.add(slots.get(skeleton.slots.indexOf(slot, true)));
 
 		ikConstraints = new Array(skeleton.ikConstraints.size);
-		for (IkConstraint ikConstraint : skeleton.ikConstraints) {
-			Bone target = bones.get(skeleton.bones.indexOf(ikConstraint.target, true));
-			Array<Bone> ikBones = new Array(ikConstraint.bones.size);
-			for (Bone bone : ikConstraint.bones)
-				ikBones.add(bones.get(skeleton.bones.indexOf(bone, true)));
-			ikConstraints.add(new IkConstraint(ikConstraint, ikBones, target));
-		}
+		for (IkConstraint ikConstraint : skeleton.ikConstraints)
+			ikConstraints.add(new IkConstraint(ikConstraint, this));
+
+		transformConstraints = new Array(skeleton.transformConstraints.size);
+		for (TransformConstraint transformConstraint : skeleton.transformConstraints)
+			transformConstraints.add(new TransformConstraint(transformConstraint, this));
 
 		skin = skeleton.skin;
 		color = new Color(skeleton.color);
@@ -119,72 +123,49 @@ public class Skeleton {
 		updateCache();
 	}
 
-	/** Caches information about bones and IK constraints. Must be called if bones or IK constraints are added or removed. */
+	/** Caches information about bones and constraints. Must be called if bones or constraints are added or removed. */
 	public void updateCache () {
 		Array<Bone> bones = this.bones;
-		Array<Array<Bone>> boneCache = this.boneCache;
+		Array<Updatable> updateCache = this.updateCache;
 		Array<IkConstraint> ikConstraints = this.ikConstraints;
+		Array<TransformConstraint> transformConstraints = this.transformConstraints;
 		int ikConstraintsCount = ikConstraints.size;
-
-		int arrayCount = ikConstraintsCount + 1;
-		while (boneCache.size < arrayCount)
-			boneCache.add(new Array());
-		for (int i = 0; i < arrayCount; i++)
-			boneCache.get(i).clear();
-
-		Array<Bone> nonIkBones = boneCache.first();
-
-		outer:
+		int transformConstraintsCount = transformConstraints.size;
+		updateCache.clear();
 		for (int i = 0, n = bones.size; i < n; i++) {
 			Bone bone = bones.get(i);
-			Bone current = bone;
-			do {
-				for (int ii = 0; ii < ikConstraintsCount; ii++) {
-					IkConstraint ikConstraint = ikConstraints.get(ii);
-					Bone parent = ikConstraint.bones.first();
-					Bone child = ikConstraint.bones.peek();
-					while (true) {
-						if (current == child) {
-							boneCache.get(ii).add(bone);
-							boneCache.get(ii + 1).add(bone);
-							continue outer;
-						}
-						if (child == parent) break;
-						child = child.parent;
-					}
+			updateCache.add(bone);
+			for (int ii = 0; ii < transformConstraintsCount; ii++) {
+				TransformConstraint transformConstraint = transformConstraints.get(ii);
+				if (bone == transformConstraint.bone) {
+					updateCache.add(transformConstraint);
+					break;
 				}
-				current = current.parent;
-			} while (current != null);
-			nonIkBones.add(bone);
+			}
+			for (int ii = 0; ii < ikConstraintsCount; ii++) {
+				IkConstraint ikConstraint = ikConstraints.get(ii);
+				if (bone == ikConstraint.bones.peek()) {
+					updateCache.add(ikConstraint);
+					break;
+				}
+			}
 		}
 	}
 
-	/** Updates the world transform for each bone and applies IK constraints. */
+	/** Updates the world transform for each bone and applies constraints. */
 	public void updateWorldTransform () {
-		Array<Bone> bones = this.bones;
-		for (int i = 0, nn = bones.size; i < nn; i++) {
-			Bone bone = bones.get(i);
-			bone.rotationIK = bone.rotation;
-		}
-		Array<Array<Bone>> boneCache = this.boneCache;
-		Array<IkConstraint> ikConstraints = this.ikConstraints;
-		int i = 0, last = ikConstraints.size;
-		while (true) {
-			Array<Bone> updateBones = boneCache.get(i);
-			for (int ii = 0, nn = updateBones.size; ii < nn; ii++)
-				updateBones.get(ii).updateWorldTransform();
-			if (i == last) break;
-			ikConstraints.get(i).apply();
-			i++;
-		}
+		Array<Updatable> updateCache = this.updateCache;
+		for (int i = 0, n = updateCache.size; i < n; i++)
+			updateCache.get(i).update();
 	}
 
-	/** Sets the bones and slots to their setup pose values. */
+	/** Sets the bones, constraints, and slots to their setup pose values. */
 	public void setToSetupPose () {
 		setBonesToSetupPose();
 		setSlotsToSetupPose();
 	}
 
+	/** Sets the bones and constraints to their setup pose values. */
 	public void setBonesToSetupPose () {
 		Array<Bone> bones = this.bones;
 		for (int i = 0, n = bones.size; i < n; i++)
@@ -192,9 +173,17 @@ public class Skeleton {
 
 		Array<IkConstraint> ikConstraints = this.ikConstraints;
 		for (int i = 0, n = ikConstraints.size; i < n; i++) {
-			IkConstraint ikConstraint = ikConstraints.get(i);
-			ikConstraint.bendDirection = ikConstraint.data.bendDirection;
-			ikConstraint.mix = ikConstraint.data.mix;
+			IkConstraint constraint = ikConstraints.get(i);
+			constraint.bendDirection = constraint.data.bendDirection;
+			constraint.mix = constraint.data.mix;
+		}
+
+		Array<TransformConstraint> transformConstraints = this.transformConstraints;
+		for (int i = 0, n = transformConstraints.size; i < n; i++) {
+			TransformConstraint constraint = transformConstraints.get(i);
+			constraint.translateMix = constraint.data.translateMix;
+			constraint.x = constraint.data.x;
+			constraint.y = constraint.data.y;
 		}
 	}
 
@@ -350,12 +339,27 @@ public class Skeleton {
 	}
 
 	/** @return May be null. */
-	public IkConstraint findIkConstraint (String ikConstraintName) {
-		if (ikConstraintName == null) throw new IllegalArgumentException("ikConstraintName cannot be null.");
+	public IkConstraint findIkConstraint (String constraintName) {
+		if (constraintName == null) throw new IllegalArgumentException("constraintName cannot be null.");
 		Array<IkConstraint> ikConstraints = this.ikConstraints;
 		for (int i = 0, n = ikConstraints.size; i < n; i++) {
 			IkConstraint ikConstraint = ikConstraints.get(i);
-			if (ikConstraint.data.name.equals(ikConstraintName)) return ikConstraint;
+			if (ikConstraint.data.name.equals(constraintName)) return ikConstraint;
+		}
+		return null;
+	}
+
+	public Array<TransformConstraint> getTransformConstraints () {
+		return transformConstraints;
+	}
+
+	/** @return May be null. */
+	public TransformConstraint findTransformConstraint (String constraintName) {
+		if (constraintName == null) throw new IllegalArgumentException("constraintName cannot be null.");
+		Array<TransformConstraint> transformConstraints = this.transformConstraints;
+		for (int i = 0, n = transformConstraints.size; i < n; i++) {
+			TransformConstraint constraint = transformConstraints.get(i);
+			if (constraint.data.name.equals(constraintName)) return constraint;
 		}
 		return null;
 	}
@@ -371,19 +375,13 @@ public class Skeleton {
 			float[] vertices = null;
 			Attachment attachment = slot.attachment;
 			if (attachment instanceof RegionAttachment) {
-				RegionAttachment region = (RegionAttachment)attachment;
-				region.updateWorldVertices(slot, false);
-				vertices = region.getWorldVertices();
+				vertices = ((RegionAttachment)attachment).updateWorldVertices(slot, false);
 
 			} else if (attachment instanceof MeshAttachment) {
-				MeshAttachment mesh = (MeshAttachment)attachment;
-				mesh.updateWorldVertices(slot, true);
-				vertices = mesh.getWorldVertices();
+				vertices = ((MeshAttachment)attachment).updateWorldVertices(slot, true);
 
 			} else if (attachment instanceof SkinnedMeshAttachment) {
-				SkinnedMeshAttachment mesh = (SkinnedMeshAttachment)attachment;
-				mesh.updateWorldVertices(slot, true);
-				vertices = mesh.getWorldVertices();
+				vertices = ((SkinnedMeshAttachment)attachment).updateWorldVertices(slot, true);
 			}
 			if (vertices != null) {
 				for (int ii = 0, nn = vertices.length; ii < nn; ii += 5) {

+ 0 - 19
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java

@@ -47,8 +47,6 @@ 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.FlipXTimeline;
-import com.esotericsoftware.spine.Animation.FlipYTimeline;
 import com.esotericsoftware.spine.Animation.IkConstraintTimeline;
 import com.esotericsoftware.spine.Animation.RotateTimeline;
 import com.esotericsoftware.spine.Animation.ScaleTimeline;
@@ -69,8 +67,6 @@ public class SkeletonBinary {
 	static public final int TIMELINE_TRANSLATE = 2;
 	static public final int TIMELINE_ATTACHMENT = 3;
 	static public final int TIMELINE_COLOR = 4;
-	static public final int TIMELINE_FLIPX = 5;
-	static public final int TIMELINE_FLIPY = 6;
 
 	static public final int CURVE_LINEAR = 0;
 	static public final int CURVE_STEPPED = 1;
@@ -135,10 +131,6 @@ public class SkeletonBinary {
 				boneData.scaleY = input.readFloat();
 				boneData.rotation = input.readFloat();
 				boneData.length = input.readFloat() * scale;
-				boneData.flipX = input.readBoolean();
-				boneData.flipY = input.readBoolean();
-				boneData.inheritScale = input.readBoolean();
-				boneData.inheritRotation = input.readBoolean();
 				if (nonessential) Color.rgba8888ToColor(boneData.color, input.readInt());
 				skeletonData.bones.add(boneData);
 			}
@@ -419,17 +411,6 @@ public class SkeletonBinary {
 						duration = Math.max(duration, timeline.getFrames()[frameCount * 3 - 3]);
 						break;
 					}
-					case TIMELINE_FLIPX:
-					case TIMELINE_FLIPY: {
-						FlipXTimeline timeline = timelineType == TIMELINE_FLIPX ? new FlipXTimeline(frameCount) : new FlipYTimeline(
-							frameCount);
-						timeline.boneIndex = boneIndex;
-						for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
-							timeline.setFrame(frameIndex, input.readFloat(), input.readBoolean());
-						timelines.add(timeline);
-						duration = Math.max(duration, timeline.getFrames()[frameCount * 2 - 2]);
-						break;
-					}
 					}
 				}
 			}

+ 23 - 5
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonData.java

@@ -42,6 +42,7 @@ public class SkeletonData {
 	final Array<EventData> events = new Array();
 	final Array<Animation> animations = new Array();
 	final Array<IkConstraintData> ikConstraints = new Array();
+	final Array<TransformConstraintData> transformConstraints = new Array();
 	float width, height;
 	String version, hash, imagesPath;
 
@@ -153,19 +154,36 @@ public class SkeletonData {
 		return null;
 	}
 
-	// --- IK
+	// --- IK constraints
 
 	public Array<IkConstraintData> getIkConstraints () {
 		return ikConstraints;
 	}
 
 	/** @return May be null. */
-	public IkConstraintData findIkConstraint (String ikConstraintName) {
-		if (ikConstraintName == null) throw new IllegalArgumentException("ikConstraintName cannot be null.");
+	public IkConstraintData findIkConstraint (String constraintName) {
+		if (constraintName == null) throw new IllegalArgumentException("constraintName cannot be null.");
 		Array<IkConstraintData> ikConstraints = this.ikConstraints;
 		for (int i = 0, n = ikConstraints.size; i < n; i++) {
-			IkConstraintData ikConstraint = ikConstraints.get(i);
-			if (ikConstraint.name.equals(ikConstraintName)) return ikConstraint;
+			IkConstraintData constraint = ikConstraints.get(i);
+			if (constraint.name.equals(constraintName)) return constraint;
+		}
+		return null;
+	}
+
+	// --- Transform constraints
+
+	public Array<TransformConstraintData> getTransformConstraints () {
+		return transformConstraints;
+	}
+
+	/** @return May be null. */
+	public TransformConstraintData findTransformConstraint (String constraintName) {
+		if (constraintName == null) throw new IllegalArgumentException("constraintName cannot be null.");
+		Array<TransformConstraintData> transformConstraints = this.transformConstraints;
+		for (int i = 0, n = transformConstraints.size; i < n; i++) {
+			TransformConstraintData constraint = transformConstraints.get(i);
+			if (constraint.name.equals(constraintName)) return constraint;
 		}
 		return null;
 	}

+ 0 - 20
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java

@@ -46,8 +46,6 @@ 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.FlipXTimeline;
-import com.esotericsoftware.spine.Animation.FlipYTimeline;
 import com.esotericsoftware.spine.Animation.IkConstraintTimeline;
 import com.esotericsoftware.spine.Animation.RotateTimeline;
 import com.esotericsoftware.spine.Animation.ScaleTimeline;
@@ -118,10 +116,6 @@ public class SkeletonJson {
 			boneData.rotation = boneMap.getFloat("rotation", 0);
 			boneData.scaleX = boneMap.getFloat("scaleX", 1);
 			boneData.scaleY = boneMap.getFloat("scaleY", 1);
-			boneData.flipX = boneMap.getBoolean("flipX", false);
-			boneData.flipY = boneMap.getBoolean("flipY", false);
-			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));
@@ -389,20 +383,6 @@ public class SkeletonJson {
 					timelines.add(timeline);
 					duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() * 3 - 3]);
 
-				} else if (timelineName.equals("flipX") || timelineName.equals("flipY")) {
-					boolean x = timelineName.equals("flipX");
-					FlipXTimeline timeline = x ? new FlipXTimeline(timelineMap.size) : new FlipYTimeline(timelineMap.size);
-					timeline.boneIndex = boneIndex;
-
-					String field = x ? "x" : "y";
-					int frameIndex = 0;
-					for (JsonValue valueMap = timelineMap.child; valueMap != null; valueMap = valueMap.next) {
-						timeline.setFrame(frameIndex, valueMap.getFloat("time"), valueMap.getBoolean(field, false));
-						frameIndex++;
-					}
-					timelines.add(timeline);
-					duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() * 2 - 2]);
-
 				} else
 					throw new RuntimeException("Invalid timeline type for a bone: " + timelineName + " (" + boneMap.name + ")");
 			}

+ 108 - 0
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonMeshRenderer.java

@@ -0,0 +1,108 @@
+/******************************************************************************
+ * Spine Runtimes Software License
+ * Version 2.3
+ * 
+ * Copyright (c) 2013-2015, Esoteric Software
+ * All rights reserved.
+ * 
+ * You are granted a perpetual, non-exclusive, non-sublicensable and
+ * non-transferable license to use, install, execute and perform the Spine
+ * Runtimes Software (the "Software") and derivative works solely for personal
+ * or internal use. Without the written permission of Esoteric Software (see
+ * Section 2 of the Spine Software License Agreement), you may not (a) modify,
+ * translate, adapt or otherwise create derivative works, improvements of the
+ * Software or develop new applications using the Software or (b) remove,
+ * delete, alter or obscure any trademarks or any copyright, trademark, patent
+ * or other intellectual property or proprietary rights notices on or in the
+ * Software, including any copy thereof. Redistributions in binary or source
+ * form must include this license and terms.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+package com.esotericsoftware.spine;
+
+import com.badlogic.gdx.graphics.Texture;
+import com.badlogic.gdx.graphics.g2d.PolygonSpriteBatch;
+import com.badlogic.gdx.utils.Array;
+import com.esotericsoftware.spine.attachments.Attachment;
+import com.esotericsoftware.spine.attachments.MeshAttachment;
+import com.esotericsoftware.spine.attachments.RegionAttachment;
+import com.esotericsoftware.spine.attachments.SkeletonAttachment;
+import com.esotericsoftware.spine.attachments.SkinnedMeshAttachment;
+
+public class SkeletonMeshRenderer extends SkeletonRenderer<PolygonSpriteBatch> {
+	static private final short[] quadTriangles = {0, 1, 2, 2, 3, 0};
+
+	@SuppressWarnings("null")
+	public void draw (PolygonSpriteBatch batch, Skeleton skeleton) {
+		boolean premultipliedAlpha = this.premultipliedAlpha;
+		BlendMode blendMode = null;
+
+		float[] vertices = null;
+		short[] triangles = null;
+		Array<Slot> drawOrder = skeleton.drawOrder;
+		for (int i = 0, n = drawOrder.size; i < n; i++) {
+			Slot slot = drawOrder.get(i);
+			Attachment attachment = slot.attachment;
+			Texture texture = null;
+			if (attachment instanceof RegionAttachment) {
+				RegionAttachment region = (RegionAttachment)attachment;
+				vertices = region.updateWorldVertices(slot, premultipliedAlpha);
+				triangles = quadTriangles;
+				texture = region.getRegion().getTexture();
+
+			} else if (attachment instanceof MeshAttachment) {
+				MeshAttachment mesh = (MeshAttachment)attachment;
+				vertices = mesh.updateWorldVertices(slot, premultipliedAlpha);
+				triangles = mesh.getTriangles();
+				texture = mesh.getRegion().getTexture();
+
+			} else if (attachment instanceof SkinnedMeshAttachment) {
+				SkinnedMeshAttachment mesh = (SkinnedMeshAttachment)attachment;
+				vertices = mesh.updateWorldVertices(slot, premultipliedAlpha);
+				triangles = mesh.getTriangles();
+				texture = mesh.getRegion().getTexture();
+
+			} else if (attachment instanceof SkeletonAttachment) {
+				Skeleton attachmentSkeleton = ((SkeletonAttachment)attachment).getSkeleton();
+				if (attachmentSkeleton == null) continue;
+				Bone bone = slot.getBone();
+				Bone rootBone = attachmentSkeleton.getRootBone();
+				float oldScaleX = rootBone.getScaleX();
+				float oldScaleY = rootBone.getScaleY();
+				float oldRotation = rootBone.getRotation();
+				attachmentSkeleton.setPosition(skeleton.getX() + bone.getWorldX(), skeleton.getY() + bone.getWorldY());
+				// rootBone.setScaleX(1 + bone.getWorldScaleX() - oldScaleX);
+				// rootBone.setScaleY(1 + bone.getWorldScaleY() - oldScaleY);
+				rootBone.setRotation(oldRotation + bone.getWorldRotationX());
+				attachmentSkeleton.updateWorldTransform();
+
+				draw(batch, attachmentSkeleton);
+
+				attachmentSkeleton.setPosition(0, 0);
+				rootBone.setScaleX(oldScaleX);
+				rootBone.setScaleY(oldScaleY);
+				rootBone.setRotation(oldRotation);
+			}
+
+			if (texture != null) {
+				BlendMode slotBlendMode = slot.data.getBlendMode();
+				if (slotBlendMode != blendMode) {
+					blendMode = slotBlendMode;
+					batch.setBlendFunction(blendMode.getSource(premultipliedAlpha), blendMode.getDest());
+				}
+				batch.draw(texture, vertices, 0, vertices.length, triangles, 0, triangles.length);
+			}
+		}
+	}
+}

+ 10 - 77
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java

@@ -31,9 +31,7 @@
 
 package com.esotericsoftware.spine;
 
-import com.badlogic.gdx.graphics.Texture;
 import com.badlogic.gdx.graphics.g2d.Batch;
-import com.badlogic.gdx.graphics.g2d.PolygonSpriteBatch;
 import com.badlogic.gdx.utils.Array;
 import com.esotericsoftware.spine.attachments.Attachment;
 import com.esotericsoftware.spine.attachments.MeshAttachment;
@@ -41,78 +39,14 @@ import com.esotericsoftware.spine.attachments.RegionAttachment;
 import com.esotericsoftware.spine.attachments.SkeletonAttachment;
 import com.esotericsoftware.spine.attachments.SkinnedMeshAttachment;
 
-public class SkeletonRenderer {
-	static private final short[] quadTriangles = {0, 1, 2, 2, 3, 0};
+public class SkeletonRenderer<T extends Batch> {
+	boolean premultipliedAlpha;
 
-	private boolean premultipliedAlpha;
-
-	@SuppressWarnings("null")
-	public void draw (PolygonSpriteBatch batch, Skeleton skeleton) {
-		boolean premultipliedAlpha = this.premultipliedAlpha;
-		BlendMode blendMode = null;
-
-		float[] vertices = null;
-		short[] triangles = null;
-		Array<Slot> drawOrder = skeleton.drawOrder;
-		for (int i = 0, n = drawOrder.size; i < n; i++) {
-			Slot slot = drawOrder.get(i);
-			Attachment attachment = slot.attachment;
-			Texture texture = null;
-			if (attachment instanceof RegionAttachment) {
-				RegionAttachment region = (RegionAttachment)attachment;
-				region.updateWorldVertices(slot, premultipliedAlpha);
-				vertices = region.getWorldVertices();
-				triangles = quadTriangles;
-				texture = region.getRegion().getTexture();
-
-			} else if (attachment instanceof MeshAttachment) {
-				MeshAttachment mesh = (MeshAttachment)attachment;
-				mesh.updateWorldVertices(slot, premultipliedAlpha);
-				vertices = mesh.getWorldVertices();
-				triangles = mesh.getTriangles();
-				texture = mesh.getRegion().getTexture();
-
-			} else if (attachment instanceof SkinnedMeshAttachment) {
-				SkinnedMeshAttachment mesh = (SkinnedMeshAttachment)attachment;
-				mesh.updateWorldVertices(slot, premultipliedAlpha);
-				vertices = mesh.getWorldVertices();
-				triangles = mesh.getTriangles();
-				texture = mesh.getRegion().getTexture();
-
-			} else if (attachment instanceof SkeletonAttachment) {
-				Skeleton attachmentSkeleton = ((SkeletonAttachment)attachment).getSkeleton();
-				if (attachmentSkeleton == null) continue;
-				Bone bone = slot.getBone();
-				Bone rootBone = attachmentSkeleton.getRootBone();
-				float oldScaleX = rootBone.getScaleX();
-				float oldScaleY = rootBone.getScaleY();
-				float oldRotation = rootBone.getRotation();
-				attachmentSkeleton.setPosition(skeleton.getX() + bone.getWorldX(), skeleton.getY() + bone.getWorldY());
-				rootBone.setScaleX(1 + bone.getWorldScaleX() - oldScaleX);
-				rootBone.setScaleY(1 + bone.getWorldScaleY() - oldScaleY);
-				rootBone.setRotation(oldRotation + bone.getWorldRotation());
-				attachmentSkeleton.updateWorldTransform();
-
-				draw(batch, attachmentSkeleton);
-
-				attachmentSkeleton.setPosition(0, 0);
-				rootBone.setScaleX(oldScaleX);
-				rootBone.setScaleY(oldScaleY);
-				rootBone.setRotation(oldRotation);
-			}
-
-			if (texture != null) {
-				BlendMode slotBlendMode = slot.data.getBlendMode();
-				if (slotBlendMode != blendMode) {
-					blendMode = slotBlendMode;
-					batch.setBlendFunction(blendMode.getSource(premultipliedAlpha), blendMode.getDest());
-				}
-				batch.draw(texture, vertices, 0, vertices.length, triangles, 0, triangles.length);
-			}
-		}
+	public SkeletonRenderer () {
+		super();
 	}
 
-	public void draw (Batch batch, Skeleton skeleton) {
+	public void draw (T batch, Skeleton skeleton) {
 		boolean premultipliedAlpha = this.premultipliedAlpha;
 		BlendMode blendMode = null;
 
@@ -122,8 +56,7 @@ public class SkeletonRenderer {
 			Attachment attachment = slot.attachment;
 			if (attachment instanceof RegionAttachment) {
 				RegionAttachment regionAttachment = (RegionAttachment)attachment;
-				regionAttachment.updateWorldVertices(slot, premultipliedAlpha);
-				float[] vertices = regionAttachment.getWorldVertices();
+				float[] vertices = regionAttachment.updateWorldVertices(slot, premultipliedAlpha);
 				BlendMode slotBlendMode = slot.data.getBlendMode();
 				if (slotBlendMode != blendMode) {
 					blendMode = slotBlendMode;
@@ -132,7 +65,7 @@ public class SkeletonRenderer {
 				batch.draw(regionAttachment.getRegion().getTexture(), vertices, 0, 20);
 
 			} else if (attachment instanceof MeshAttachment || attachment instanceof SkinnedMeshAttachment) {
-				throw new RuntimeException("PolygonSpriteBatch is required to render meshes.");
+				throw new RuntimeException("SkeletonMeshRenderer is required to render meshes.");
 
 			} else if (attachment instanceof SkeletonAttachment) {
 				Skeleton attachmentSkeleton = ((SkeletonAttachment)attachment).getSkeleton();
@@ -143,9 +76,9 @@ public class SkeletonRenderer {
 				float oldScaleY = rootBone.getScaleY();
 				float oldRotation = rootBone.getRotation();
 				attachmentSkeleton.setPosition(skeleton.getX() + bone.getWorldX(), skeleton.getY() + bone.getWorldY());
-				rootBone.setScaleX(1 + bone.getWorldScaleX() - oldScaleX);
-				rootBone.setScaleY(1 + bone.getWorldScaleY() - oldScaleY);
-				rootBone.setRotation(oldRotation + bone.getWorldRotation());
+				// rootBone.setScaleX(1 + bone.getWorldScaleX() - oldScaleX);
+				// rootBone.setScaleY(1 + bone.getWorldScaleY() - oldScaleY);
+				rootBone.setRotation(oldRotation + bone.getWorldRotationX());
 				attachmentSkeleton.updateWorldTransform();
 
 				draw(batch, attachmentSkeleton);

+ 3 - 4
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRendererDebug.java

@@ -87,8 +87,8 @@ public class SkeletonRendererDebug {
 			for (int i = 0, n = bones.size; i < n; i++) {
 				Bone bone = bones.get(i);
 				if (bone.parent == null) continue;
-				float x = skeletonX + bone.data.length * bone.m00 + bone.worldX;
-				float y = skeletonY + bone.data.length * bone.m10 + bone.worldY;
+				float x = skeletonX + bone.data.length * bone.a + bone.worldX;
+				float y = skeletonY + bone.data.length * bone.c + bone.worldY;
 				shapes.rectLine(skeletonX + bone.worldX, skeletonY + bone.worldY, x, y, boneWidth * scale);
 			}
 			shapes.end();
@@ -105,8 +105,7 @@ public class SkeletonRendererDebug {
 				Attachment attachment = slot.attachment;
 				if (attachment instanceof RegionAttachment) {
 					RegionAttachment regionAttachment = (RegionAttachment)attachment;
-					regionAttachment.updateWorldVertices(slot, false);
-					float[] vertices = regionAttachment.getWorldVertices();
+					float[] vertices = regionAttachment.updateWorldVertices(slot, false);
 					shapes.line(vertices[X1], vertices[Y1], vertices[X2], vertices[Y2]);
 					shapes.line(vertices[X2], vertices[Y2], vertices[X3], vertices[Y3]);
 					shapes.line(vertices[X3], vertices[Y3], vertices[X4], vertices[Y4]);

+ 76 - 0
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraint.java

@@ -0,0 +1,76 @@
+
+package com.esotericsoftware.spine;
+
+import com.badlogic.gdx.math.Vector2;
+
+public class TransformConstraint implements Updatable {
+	final TransformConstraintData data;
+	Bone bone, target;
+	float translateMix, x, y;
+	final Vector2 temp = new Vector2();
+
+	public TransformConstraint (TransformConstraintData data, Skeleton skeleton) {
+		this.data = data;
+		translateMix = data.translateMix;
+
+		if (skeleton != null) {
+			bone = skeleton.findBone(data.bone.name);
+			target = skeleton.findBone(data.target.name);
+		}
+	}
+
+	/** Copy constructor. */
+	public TransformConstraint (TransformConstraint constraint, Skeleton skeleton) {
+		data = constraint.data;
+		translateMix = data.translateMix;
+		bone = skeleton.bones.get(constraint.bone.skeleton.bones.indexOf(constraint.target, true));
+		target = skeleton.bones.get(constraint.target.skeleton.bones.indexOf(constraint.target, true));
+	}
+
+	public void apply () {
+		update();
+	}
+
+	public void update () {
+		float translateMix = this.translateMix;
+		if (translateMix > 0) {
+			Bone bone = this.bone;
+			Vector2 temp = this.temp;
+			target.localToWorld(temp.set(x, y));
+			bone.worldX += (temp.x - bone.worldX) * translateMix;
+			bone.worldY += (temp.y - bone.worldY) * translateMix;
+		}
+	}
+
+	public Bone getBone () {
+		return bone;
+	}
+
+	public void setBone (Bone bone) {
+		this.bone = bone;
+	}
+
+	public Bone getTarget () {
+		return target;
+	}
+
+	public void setTarget (Bone target) {
+		this.target = target;
+	}
+
+	public float getTranslateMix () {
+		return translateMix;
+	}
+
+	public void setTranslateMix (float translateMix) {
+		this.translateMix = translateMix;
+	}
+
+	public TransformConstraintData getData () {
+		return data;
+	}
+
+	public String toString () {
+		return data.name;
+	}
+}

+ 61 - 0
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/TransformConstraintData.java

@@ -0,0 +1,61 @@
+
+package com.esotericsoftware.spine;
+
+public class TransformConstraintData {
+	final String name;
+	BoneData bone, target;
+	float translateMix;
+	float x, y;
+
+	public TransformConstraintData (String name) {
+		this.name = name;
+	}
+
+	public String getName () {
+		return name;
+	}
+
+	public BoneData getBone () {
+		return bone;
+	}
+
+	public void setBone (BoneData bone) {
+		this.bone = bone;
+	}
+
+	public BoneData getTarget () {
+		return target;
+	}
+
+	public void setTarget (BoneData target) {
+		this.target = target;
+	}
+
+	public float getTranslateMix () {
+		return translateMix;
+	}
+
+	public void setTranslateMix (float translateMix) {
+		this.translateMix = translateMix;
+	}
+
+	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 String toString () {
+		return name;
+	}
+}

+ 6 - 0
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Updatable.java

@@ -0,0 +1,6 @@
+
+package com.esotericsoftware.spine;
+
+public interface Updatable {
+	public void update ();
+}

+ 4 - 4
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/BoundingBoxAttachment.java

@@ -44,10 +44,10 @@ public class BoundingBoxAttachment extends Attachment {
 	public void computeWorldVertices (Bone bone, float[] worldVertices) {
 		Skeleton skeleton = bone.getSkeleton();
 		float x = skeleton.getX() + bone.getWorldX(), y = skeleton.getY() + bone.getWorldY();
-		float m00 = bone.getM00();
-		float m01 = bone.getM01();
-		float m10 = bone.getM10();
-		float m11 = bone.getM11();
+		float m00 = bone.getA();
+		float m01 = bone.getB();
+		float m10 = bone.getC();
+		float m11 = bone.getD();
 		float[] vertices = this.vertices;
 		for (int i = 0, n = vertices.length; i < n; i += 2) {
 			float px = vertices[i];

+ 4 - 2
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/MeshAttachment.java

@@ -98,7 +98,8 @@ public class MeshAttachment extends Attachment {
 		}
 	}
 
-	public void updateWorldVertices (Slot slot, boolean premultipliedAlpha) {
+	/** @return The updated world vertices. */
+	public float[] updateWorldVertices (Slot slot, boolean premultipliedAlpha) {
 		Skeleton skeleton = slot.getSkeleton();
 		Color skeletonColor = skeleton.getColor();
 		Color slotColor = slot.getColor();
@@ -117,7 +118,7 @@ public class MeshAttachment extends Attachment {
 		if (slotVertices.size == vertices.length) vertices = slotVertices.items;
 		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();
+		float m00 = bone.getA(), m01 = bone.getB(), m10 = bone.getC(), m11 = bone.getD();
 		for (int v = 0, w = 0, n = worldVertices.length; w < n; v += 2, w += 5) {
 			float vx = vertices[v];
 			float vy = vertices[v + 1];
@@ -125,6 +126,7 @@ public class MeshAttachment extends Attachment {
 			worldVertices[w + 1] = vx * m10 + vy * m11 + y;
 			worldVertices[w + 2] = color;
 		}
+		return worldVertices;
 	}
 
 	public float[] getWorldVertices () {

+ 4 - 2
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionAttachment.java

@@ -146,7 +146,8 @@ public class RegionAttachment extends Attachment {
 		return region;
 	}
 
-	public void updateWorldVertices (Slot slot, boolean premultipliedAlpha) {
+	/** @return The updated world vertices. */
+	public float[] updateWorldVertices (Slot slot, boolean premultipliedAlpha) {
 		Skeleton skeleton = slot.getSkeleton();
 		Color skeletonColor = skeleton.getColor();
 		Color slotColor = slot.getColor();
@@ -163,7 +164,7 @@ public class RegionAttachment extends Attachment {
 		float[] offset = this.offset;
 		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();
+		float m00 = bone.getA(), m01 = bone.getB(), m10 = bone.getC(), m11 = bone.getD();
 		float offsetX, offsetY;
 
 		offsetX = offset[BRX];
@@ -189,6 +190,7 @@ public class RegionAttachment extends Attachment {
 		vertices[X4] = offsetX * m00 + offsetY * m01 + x; // ur
 		vertices[Y4] = offsetX * m10 + offsetY * m11 + y;
 		vertices[C4] = color;
+		return vertices;
 	}
 
 	public float[] getWorldVertices () {

+ 2 - 2
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/RegionSequenceAttachment.java

@@ -46,7 +46,7 @@ public class RegionSequenceAttachment extends RegionAttachment {
 		super(name);
 	}
 
-	public void updateWorldVertices (Slot slot, boolean premultipliedAlpha) {
+	public float[] updateWorldVertices (Slot slot, boolean premultipliedAlpha) {
 		if (regions == null) throw new IllegalStateException("Regions have not been set: " + this);
 
 		int frameIndex = (int)(slot.getAttachmentTime() / frameTime);
@@ -74,7 +74,7 @@ public class RegionSequenceAttachment extends RegionAttachment {
 		}
 		setRegion(regions[frameIndex]);
 
-		super.updateWorldVertices(slot, premultipliedAlpha);
+		return super.updateWorldVertices(slot, premultipliedAlpha);
 	}
 
 	public TextureRegion[] getRegions () {

+ 7 - 5
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/attachments/SkinnedMeshAttachment.java

@@ -99,7 +99,8 @@ public class SkinnedMeshAttachment extends Attachment {
 		}
 	}
 
-	public void updateWorldVertices (Slot slot, boolean premultipliedAlpha) {
+	/** @return The updated world vertices. */
+	public float[] updateWorldVertices (Slot slot, boolean premultipliedAlpha) {
 		Skeleton skeleton = slot.getSkeleton();
 		Color skeletonColor = skeleton.getColor();
 		Color meshColor = slot.getColor();
@@ -126,8 +127,8 @@ public class SkinnedMeshAttachment extends Attachment {
 				for (; v < nn; v++, b += 3) {
 					Bone bone = (Bone)skeletonBones[bones[v]];
 					float vx = weights[b], vy = weights[b + 1], weight = weights[b + 2];
-					wx += (vx * bone.getM00() + vy * bone.getM01() + bone.getWorldX()) * weight;
-					wy += (vx * bone.getM10() + vy * bone.getM11() + bone.getWorldY()) * weight;
+					wx += (vx * bone.getA() + vy * bone.getB() + bone.getWorldX()) * weight;
+					wy += (vx * bone.getC() + vy * bone.getD() + bone.getWorldY()) * weight;
 				}
 				worldVertices[w] = wx + x;
 				worldVertices[w + 1] = wy + y;
@@ -141,14 +142,15 @@ public class SkinnedMeshAttachment extends Attachment {
 				for (; v < nn; v++, b += 3, f += 2) {
 					Bone bone = (Bone)skeletonBones[bones[v]];
 					float vx = weights[b] + ffd[f], vy = weights[b + 1] + ffd[f + 1], weight = weights[b + 2];
-					wx += (vx * bone.getM00() + vy * bone.getM01() + bone.getWorldX()) * weight;
-					wy += (vx * bone.getM10() + vy * bone.getM11() + bone.getWorldY()) * weight;
+					wx += (vx * bone.getA() + vy * bone.getB() + bone.getWorldX()) * weight;
+					wy += (vx * bone.getC() + vy * bone.getD() + bone.getWorldY()) * weight;
 				}
 				worldVertices[w] = wx + x;
 				worldVertices[w + 1] = wy + y;
 				worldVertices[w + 2] = color;
 			}
 		}
+		return worldVertices;
 	}
 
 	public float[] getWorldVertices () {

+ 68 - 0
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/SkeletonActor.java

@@ -0,0 +1,68 @@
+
+package com.esotericsoftware.spine.utils;
+
+import com.badlogic.gdx.graphics.Color;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.scenes.scene2d.Actor;
+import com.esotericsoftware.spine.AnimationState;
+import com.esotericsoftware.spine.Skeleton;
+import com.esotericsoftware.spine.SkeletonRenderer;
+
+/** A scene2d actor that draws a skeleton. */
+public class SkeletonActor extends Actor {
+	private SkeletonRenderer renderer;
+	private Skeleton skeleton;
+	AnimationState state;
+
+	/** Creates an uninitialized SkeletonActor. The renderer, skeleton, and animation state must be set before use. */
+	public SkeletonActor () {
+	}
+
+	public SkeletonActor (SkeletonRenderer renderer, Skeleton skeleton, AnimationState state) {
+		this.renderer = renderer;
+		this.skeleton = skeleton;
+		this.state = state;
+	}
+
+	public void act (float delta) {
+		state.update(delta);
+		state.apply(skeleton);
+		skeleton.updateWorldTransform();
+		super.act(delta);
+	}
+
+	public void draw (Batch batch, float parentAlpha) {
+		Color color = skeleton.getColor();
+		float oldAlpha = color.a;
+		skeleton.getColor().a *= parentAlpha;
+
+		skeleton.setPosition(getX(), getY());
+		renderer.draw(batch, skeleton);
+
+		color.a = oldAlpha;
+	}
+
+	public SkeletonRenderer getRenderer () {
+		return renderer;
+	}
+
+	public void setRenderer (SkeletonRenderer renderer) {
+		this.renderer = renderer;
+	}
+
+	public Skeleton getSkeleton () {
+		return skeleton;
+	}
+
+	public void setSkeleton (Skeleton skeleton) {
+		this.skeleton = skeleton;
+	}
+
+	public AnimationState getAnimationState () {
+		return state;
+	}
+
+	public void setAnimationState (AnimationState state) {
+		this.state = state;
+	}
+}

+ 102 - 0
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/SkeletonActorPool.java

@@ -0,0 +1,102 @@
+
+package com.esotericsoftware.spine.utils;
+
+import com.badlogic.gdx.graphics.Color;
+import com.badlogic.gdx.utils.Array;
+import com.badlogic.gdx.utils.Pool;
+import com.esotericsoftware.spine.AnimationState;
+import com.esotericsoftware.spine.AnimationState.TrackEntry;
+import com.esotericsoftware.spine.AnimationStateData;
+import com.esotericsoftware.spine.Skeleton;
+import com.esotericsoftware.spine.SkeletonData;
+import com.esotericsoftware.spine.SkeletonRenderer;
+import com.esotericsoftware.spine.Skin;
+
+public class SkeletonActorPool extends Pool<SkeletonActor> {
+	private SkeletonRenderer renderer;
+	SkeletonData skeletonData;
+	AnimationStateData stateData;
+	private final Pool<Skeleton> skeletonPool;
+	private final Pool<AnimationState> statePool;
+	private final Array<SkeletonActor> obtained;
+
+	public SkeletonActorPool (SkeletonRenderer renderer, SkeletonData skeletonData, AnimationStateData stateData) {
+		this(renderer, skeletonData, stateData, 16, Integer.MAX_VALUE);
+	}
+
+	public SkeletonActorPool (SkeletonRenderer renderer, SkeletonData skeletonData, AnimationStateData stateData,
+		int initialCapacity, int max) {
+		super(initialCapacity, max);
+
+		this.renderer = renderer;
+		this.skeletonData = skeletonData;
+		this.stateData = stateData;
+
+		obtained = new Array(false, initialCapacity);
+
+		skeletonPool = new Pool<Skeleton>(initialCapacity, max) {
+			protected Skeleton newObject () {
+				return new Skeleton(SkeletonActorPool.this.skeletonData);
+			}
+
+			protected void reset (Skeleton skeleton) {
+				skeleton.setColor(Color.WHITE);
+				skeleton.setFlip(false, false);
+				skeleton.setSkin((Skin)null);
+				skeleton.setSkin(SkeletonActorPool.this.skeletonData.getDefaultSkin());
+				skeleton.setToSetupPose();
+			}
+		};
+
+		statePool = new Pool<AnimationState>(initialCapacity, max) {
+			protected AnimationState newObject () {
+				return new AnimationState(SkeletonActorPool.this.stateData);
+			}
+
+			protected void reset (AnimationState state) {
+				state.clearTracks();
+				state.clearListeners();
+			}
+		};
+	}
+
+	/** Each obtained skeleton actor that is no longer playing an animation is removed from the stage and returned to the pool. */
+	public void freeComplete () {
+		Array<SkeletonActor> obtained = this.obtained;
+		outer:
+		for (int i = obtained.size - 1; i >= 0; i--) {
+			SkeletonActor actor = obtained.get(i);
+			Array<TrackEntry> tracks = actor.state.getTracks();
+			for (int ii = 0, nn = tracks.size; ii < nn; ii++)
+				if (tracks.get(ii) != null) continue outer;
+			free(actor);
+		}
+	}
+
+	protected SkeletonActor newObject () {
+		SkeletonActor actor = new SkeletonActor();
+		actor.setRenderer(renderer);
+		return actor;
+	}
+
+	/** This pool keeps a reference to the obtained instance, so it should be returned to the pool via {@link #free(SkeletonActor)}
+	 * , {@link #freeAll(Array)}, or {@link #freeComplete()} to avoid leaking memory. */
+	public SkeletonActor obtain () {
+		SkeletonActor actor = super.obtain();
+		actor.setSkeleton(skeletonPool.obtain());
+		actor.setAnimationState(statePool.obtain());
+		obtained.add(actor);
+		return actor;
+	}
+
+	protected void reset (SkeletonActor actor) {
+		actor.remove();
+		obtained.removeValue(actor, true);
+		skeletonPool.free(actor.getSkeleton());
+		statePool.free(actor.getAnimationState());
+	}
+
+	public Array<SkeletonActor> getObtained () {
+		return obtained;
+	}
+}

+ 28 - 0
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/SkeletonPool.java

@@ -0,0 +1,28 @@
+
+package com.esotericsoftware.spine.utils;
+
+import com.badlogic.gdx.utils.Pool;
+import com.esotericsoftware.spine.Skeleton;
+import com.esotericsoftware.spine.SkeletonData;
+
+public class SkeletonPool extends Pool<Skeleton> {
+	private SkeletonData skeletonData;
+
+	public SkeletonPool (SkeletonData skeletonData) {
+		this.skeletonData = skeletonData;
+	}
+
+	public SkeletonPool (SkeletonData skeletonData, int initialCapacity) {
+		super(initialCapacity);
+		this.skeletonData = skeletonData;
+	}
+
+	public SkeletonPool (SkeletonData skeletonData, int initialCapacity, int max) {
+		super(initialCapacity, max);
+		this.skeletonData = skeletonData;
+	}
+
+	protected Skeleton newObject () {
+		return new Skeleton(skeletonData);
+	}
+}

+ 206 - 0
spine-libgdx/spine-skeletonviewer/assets/skin/font-calibri-12.fnt

@@ -0,0 +1,206 @@
+info face="Calibri" size=-12 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=0 aa=1 padding=1,1,2,1 spacing=1,1 outline=0
+common lineHeight=14 base=11 scaleW=512 scaleH=512 pages=1 packed=0 alphaChnl=0 redChnl=4 greenChnl=4 blueChnl=4
+page id=0 file="font-calibri-12_0.png"
+chars count=95
+char id=32   x=0     y=0     width=0     height=0     xoffset=0     yoffset=0     xadvance=3     page=0  chnl=15
+char id=33   x=494   y=0     width=3     height=11    xoffset=0     yoffset=2     xadvance=4     page=0  chnl=15
+char id=34   x=157   y=12    width=5     height=6     xoffset=0     yoffset=2     xadvance=5     page=0  chnl=15
+char id=35   x=162   y=0     width=8     height=11    xoffset=-1    yoffset=2     xadvance=6     page=0  chnl=15
+char id=36   x=16    y=0     width=6     height=13    xoffset=0     yoffset=1     xadvance=6     page=0  chnl=15
+char id=37   x=101   y=0     width=10    height=11    xoffset=-1    yoffset=2     xadvance=9     page=0  chnl=15
+char id=38   x=123   y=0     width=9     height=11    xoffset=0     yoffset=2     xadvance=8     page=0  chnl=15
+char id=39   x=168   y=12    width=3     height=6     xoffset=0     yoffset=2     xadvance=3     page=0  chnl=15
+char id=40   x=51    y=0     width=4     height=13    xoffset=0     yoffset=2     xadvance=4     page=0  chnl=15
+char id=41   x=41    y=0     width=4     height=13    xoffset=0     yoffset=2     xadvance=4     page=0  chnl=15
+char id=42   x=117   y=12    width=7     height=8     xoffset=-1    yoffset=2     xadvance=6     page=0  chnl=15
+char id=43   x=109   y=12    width=7     height=8     xoffset=-1    yoffset=4     xadvance=6     page=0  chnl=15
+char id=44   x=163   y=12    width=4     height=6     xoffset=-1    yoffset=9     xadvance=3     page=0  chnl=15
+char id=45   x=194   y=12    width=5     height=4     xoffset=-1    yoffset=6     xadvance=4     page=0  chnl=15
+char id=46   x=204   y=12    width=3     height=4     xoffset=0     yoffset=9     xadvance=3     page=0  chnl=15
+char id=47   x=0     y=0     width=7     height=14    xoffset=-1    yoffset=1     xadvance=5     page=0  chnl=15
+char id=48   x=362   y=0     width=7     height=11    xoffset=-1    yoffset=2     xadvance=6     page=0  chnl=15
+char id=49   x=370   y=0     width=7     height=11    xoffset=-1    yoffset=2     xadvance=6     page=0  chnl=15
+char id=50   x=402   y=0     width=7     height=11    xoffset=-1    yoffset=2     xadvance=6     page=0  chnl=15
+char id=51   x=410   y=0     width=7     height=11    xoffset=-1    yoffset=2     xadvance=6     page=0  chnl=15
+char id=52   x=234   y=0     width=7     height=11    xoffset=-1    yoffset=2     xadvance=6     page=0  chnl=15
+char id=53   x=242   y=0     width=7     height=11    xoffset=-1    yoffset=2     xadvance=6     page=0  chnl=15
+char id=54   x=330   y=0     width=7     height=11    xoffset=-1    yoffset=2     xadvance=6     page=0  chnl=15
+char id=55   x=258   y=0     width=7     height=11    xoffset=-1    yoffset=2     xadvance=6     page=0  chnl=15
+char id=56   x=266   y=0     width=7     height=11    xoffset=-1    yoffset=2     xadvance=6     page=0  chnl=15
+char id=57   x=274   y=0     width=7     height=11    xoffset=-1    yoffset=2     xadvance=6     page=0  chnl=15
+char id=58   x=105   y=12    width=3     height=9     xoffset=0     yoffset=4     xadvance=3     page=0  chnl=15
+char id=59   x=481   y=0     width=4     height=11    xoffset=-1    yoffset=4     xadvance=3     page=0  chnl=15
+char id=60   x=133   y=12    width=7     height=8     xoffset=-1    yoffset=4     xadvance=6     page=0  chnl=15
+char id=61   x=149   y=12    width=7     height=6     xoffset=-1    yoffset=5     xadvance=6     page=0  chnl=15
+char id=62   x=125   y=12    width=7     height=8     xoffset=-1    yoffset=4     xadvance=6     page=0  chnl=15
+char id=63   x=468   y=0     width=6     height=11    xoffset=0     yoffset=2     xadvance=6     page=0  chnl=15
+char id=64   x=65    y=0     width=11    height=12    xoffset=0     yoffset=2     xadvance=11    page=0  chnl=15
+char id=65   x=143   y=0     width=9     height=11    xoffset=-1    yoffset=2     xadvance=7     page=0  chnl=15
+char id=66   x=290   y=0     width=7     height=11    xoffset=0     yoffset=2     xadvance=7     page=0  chnl=15
+char id=67   x=298   y=0     width=7     height=11    xoffset=0     yoffset=2     xadvance=7     page=0  chnl=15
+char id=68   x=171   y=0     width=8     height=11    xoffset=0     yoffset=2     xadvance=8     page=0  chnl=15
+char id=69   x=461   y=0     width=6     height=11    xoffset=0     yoffset=2     xadvance=6     page=0  chnl=15
+char id=70   x=433   y=0     width=6     height=11    xoffset=0     yoffset=2     xadvance=6     page=0  chnl=15
+char id=71   x=180   y=0     width=8     height=11    xoffset=0     yoffset=2     xadvance=8     page=0  chnl=15
+char id=72   x=216   y=0     width=8     height=11    xoffset=0     yoffset=2     xadvance=8     page=0  chnl=15
+char id=73   x=498   y=0     width=3     height=11    xoffset=0     yoffset=2     xadvance=3     page=0  chnl=15
+char id=74   x=475   y=0     width=5     height=11    xoffset=-1    yoffset=2     xadvance=4     page=0  chnl=15
+char id=75   x=314   y=0     width=7     height=11    xoffset=0     yoffset=2     xadvance=7     page=0  chnl=15
+char id=76   x=440   y=0     width=6     height=11    xoffset=0     yoffset=2     xadvance=6     page=0  chnl=15
+char id=77   x=112   y=0     width=10    height=11    xoffset=0     yoffset=2     xadvance=10    page=0  chnl=15
+char id=78   x=225   y=0     width=8     height=11    xoffset=0     yoffset=2     xadvance=8     page=0  chnl=15
+char id=79   x=198   y=0     width=8     height=11    xoffset=0     yoffset=2     xadvance=8     page=0  chnl=15
+char id=80   x=282   y=0     width=7     height=11    xoffset=0     yoffset=2     xadvance=7     page=0  chnl=15
+char id=81   x=77    y=0     width=9     height=12    xoffset=0     yoffset=2     xadvance=8     page=0  chnl=15
+char id=82   x=306   y=0     width=7     height=11    xoffset=0     yoffset=2     xadvance=7     page=0  chnl=15
+char id=83   x=447   y=0     width=6     height=11    xoffset=0     yoffset=2     xadvance=6     page=0  chnl=15
+char id=84   x=250   y=0     width=7     height=11    xoffset=-1    yoffset=2     xadvance=6     page=0  chnl=15
+char id=85   x=207   y=0     width=8     height=11    xoffset=0     yoffset=2     xadvance=8     page=0  chnl=15
+char id=86   x=133   y=0     width=9     height=11    xoffset=-1    yoffset=2     xadvance=8     page=0  chnl=15
+char id=87   x=87    y=0     width=13    height=11    xoffset=-1    yoffset=2     xadvance=12    page=0  chnl=15
+char id=88   x=189   y=0     width=8     height=11    xoffset=-1    yoffset=2     xadvance=7     page=0  chnl=15
+char id=89   x=322   y=0     width=7     height=11    xoffset=-1    yoffset=2     xadvance=6     page=0  chnl=15
+char id=90   x=153   y=0     width=8     height=11    xoffset=-1    yoffset=2     xadvance=7     page=0  chnl=15
+char id=91   x=46    y=0     width=4     height=13    xoffset=0     yoffset=2     xadvance=4     page=0  chnl=15
+char id=92   x=8     y=0     width=7     height=14    xoffset=-1    yoffset=1     xadvance=5     page=0  chnl=15
+char id=93   x=56    y=0     width=4     height=13    xoffset=0     yoffset=2     xadvance=4     page=0  chnl=15
+char id=94   x=141   y=12    width=7     height=7     xoffset=-1    yoffset=2     xadvance=6     page=0  chnl=15
+char id=95   x=185   y=12    width=8     height=4     xoffset=-1    yoffset=11    xadvance=6     page=0  chnl=15
+char id=96   x=180   y=12    width=4     height=5     xoffset=0     yoffset=1     xadvance=4     page=0  chnl=15
+char id=97   x=71    y=13    width=7     height=9     xoffset=0     yoffset=4     xadvance=7     page=0  chnl=15
+char id=98   x=338   y=0     width=7     height=11    xoffset=0     yoffset=2     xadvance=7     page=0  chnl=15
+char id=99   x=79    y=13    width=6     height=9     xoffset=0     yoffset=4     xadvance=6     page=0  chnl=15
+char id=100  x=346   y=0     width=7     height=11    xoffset=0     yoffset=2     xadvance=7     page=0  chnl=15
+char id=101  x=23    y=14    width=7     height=9     xoffset=0     yoffset=4     xadvance=7     page=0  chnl=15
+char id=102  x=426   y=0     width=6     height=11    xoffset=-1    yoffset=2     xadvance=4     page=0  chnl=15
+char id=103  x=354   y=0     width=7     height=11    xoffset=0     yoffset=4     xadvance=6     page=0  chnl=15
+char id=104  x=386   y=0     width=7     height=11    xoffset=0     yoffset=2     xadvance=7     page=0  chnl=15
+char id=105  x=486   y=0     width=3     height=11    xoffset=0     yoffset=2     xadvance=3     page=0  chnl=15
+char id=106  x=29    y=0     width=5     height=13    xoffset=-2    yoffset=2     xadvance=2     page=0  chnl=15
+char id=107  x=454   y=0     width=6     height=11    xoffset=0     yoffset=2     xadvance=6     page=0  chnl=15
+char id=108  x=490   y=0     width=3     height=11    xoffset=0     yoffset=2     xadvance=3     page=0  chnl=15
+char id=109  x=12    y=15    width=10    height=9     xoffset=0     yoffset=4     xadvance=10    page=0  chnl=15
+char id=110  x=31    y=14    width=7     height=9     xoffset=0     yoffset=4     xadvance=7     page=0  chnl=15
+char id=111  x=39    y=14    width=7     height=9     xoffset=0     yoffset=4     xadvance=7     page=0  chnl=15
+char id=112  x=378   y=0     width=7     height=11    xoffset=0     yoffset=4     xadvance=7     page=0  chnl=15
+char id=113  x=418   y=0     width=7     height=11    xoffset=0     yoffset=4     xadvance=7     page=0  chnl=15
+char id=114  x=93    y=12    width=5     height=9     xoffset=0     yoffset=4     xadvance=4     page=0  chnl=15
+char id=115  x=86    y=13    width=6     height=9     xoffset=0     yoffset=4     xadvance=6     page=0  chnl=15
+char id=116  x=502   y=0     width=6     height=10    xoffset=-1    yoffset=3     xadvance=5     page=0  chnl=15
+char id=117  x=47    y=14    width=7     height=9     xoffset=0     yoffset=4     xadvance=7     page=0  chnl=15
+char id=118  x=55    y=14    width=7     height=9     xoffset=-1    yoffset=4     xadvance=6     page=0  chnl=15
+char id=119  x=0     y=15    width=11    height=9     xoffset=-1    yoffset=4     xadvance=10     page=0  chnl=15
+char id=120  x=63    y=14    width=7     height=9     xoffset=-1    yoffset=4     xadvance=6     page=0  chnl=15
+char id=121  x=394   y=0     width=7     height=11    xoffset=-1    yoffset=4     xadvance=5     page=0  chnl=15
+char id=122  x=99    y=12    width=5     height=9     xoffset=0     yoffset=4     xadvance=5     page=0  chnl=15
+char id=123  x=35    y=0     width=5     height=13    xoffset=-1    yoffset=2     xadvance=4     page=0  chnl=15
+char id=124  x=61    y=0     width=3     height=13    xoffset=1     yoffset=2     xadvance=6     page=0  chnl=15
+char id=125  x=23    y=0     width=5     height=13    xoffset=0     yoffset=2     xadvance=4     page=0  chnl=15
+char id=126  x=172   y=12    width=7     height=5     xoffset=-1    yoffset=4     xadvance=6     page=0  chnl=15
+kernings count=104
+kerning first=65  second=84  amount=-1  
+kerning first=65  second=86  amount=-1  
+kerning first=65  second=89  amount=-1  
+kerning first=70  second=65  amount=-1  
+kerning first=70  second=74  amount=-1  
+kerning first=70  second=44  amount=-1  
+kerning first=70  second=46  amount=-1  
+kerning first=75  second=79  amount=-1  
+kerning first=75  second=81  amount=-1  
+kerning first=75  second=118 amount=-1  
+kerning first=75  second=119 amount=-1  
+kerning first=76  second=84  amount=-1  
+kerning first=76  second=86  amount=-1  
+kerning first=76  second=87  amount=-1  
+kerning first=76  second=89  amount=-1  
+kerning first=76  second=101 amount=-1  
+kerning first=80  second=65  amount=-1  
+kerning first=80  second=74  amount=-1  
+kerning first=80  second=44  amount=-1  
+kerning first=80  second=46  amount=-2  
+kerning first=80  second=47  amount=-1  
+kerning first=81  second=44  amount=1   
+kerning first=81  second=47  amount=1   
+kerning first=84  second=65  amount=-1  
+kerning first=84  second=97  amount=-1  
+kerning first=84  second=99  amount=-1  
+kerning first=84  second=100 amount=-1  
+kerning first=84  second=101 amount=-1  
+kerning first=84  second=103 amount=-1  
+kerning first=84  second=109 amount=-1  
+kerning first=84  second=110 amount=-1  
+kerning first=84  second=111 amount=-1  
+kerning first=84  second=112 amount=-1  
+kerning first=84  second=113 amount=-1  
+kerning first=84  second=114 amount=-1  
+kerning first=84  second=115 amount=-1  
+kerning first=84  second=117 amount=-1  
+kerning first=84  second=118 amount=-1  
+kerning first=84  second=119 amount=-1  
+kerning first=84  second=120 amount=-1  
+kerning first=84  second=121 amount=-1  
+kerning first=84  second=122 amount=-1  
+kerning first=84  second=44  amount=-1  
+kerning first=84  second=59  amount=-1  
+kerning first=84  second=58  amount=-1  
+kerning first=84  second=46  amount=-1  
+kerning first=84  second=47  amount=-1  
+kerning first=86  second=65  amount=-1  
+kerning first=86  second=97  amount=-1  
+kerning first=86  second=99  amount=-1  
+kerning first=86  second=100 amount=-1  
+kerning first=86  second=101 amount=-1  
+kerning first=86  second=103 amount=-1  
+kerning first=86  second=111 amount=-1  
+kerning first=86  second=113 amount=-1  
+kerning first=86  second=115 amount=-1  
+kerning first=86  second=44  amount=-1  
+kerning first=86  second=59  amount=-1  
+kerning first=86  second=46  amount=-1  
+kerning first=86  second=47  amount=-1  
+kerning first=87  second=65  amount=-1  
+kerning first=87  second=74  amount=-1  
+kerning first=87  second=111 amount=-1  
+kerning first=87  second=44  amount=-1  
+kerning first=87  second=59  amount=-1  
+kerning first=87  second=46  amount=-1  
+kerning first=89  second=65  amount=-1  
+kerning first=89  second=74  amount=-1  
+kerning first=89  second=97  amount=-1  
+kerning first=89  second=99  amount=-1  
+kerning first=89  second=100 amount=-1  
+kerning first=89  second=101 amount=-1  
+kerning first=89  second=103 amount=-1  
+kerning first=89  second=109 amount=-1  
+kerning first=89  second=110 amount=-1  
+kerning first=89  second=111 amount=-1  
+kerning first=89  second=112 amount=-1  
+kerning first=89  second=113 amount=-1  
+kerning first=89  second=114 amount=-1  
+kerning first=89  second=115 amount=-1  
+kerning first=89  second=117 amount=-1  
+kerning first=89  second=122 amount=-1  
+kerning first=89  second=44  amount=-1  
+kerning first=89  second=59  amount=-1  
+kerning first=89  second=58  amount=-1  
+kerning first=89  second=46  amount=-1  
+kerning first=89  second=47  amount=-1  
+kerning first=102 second=44  amount=-1  
+kerning first=102 second=46  amount=-1  
+kerning first=102 second=116 amount=1  
+kerning first=114 second=44  amount=-1  
+kerning first=114 second=46  amount=-1  
+kerning first=118 second=44  amount=-1  
+kerning first=118 second=46  amount=-1  
+kerning first=119 second=44  amount=-1  
+kerning first=119 second=46  amount=-1  
+kerning first=121 second=44  amount=-1  
+kerning first=121 second=46  amount=-1  
+kerning first=44  second=84  amount=-1  
+kerning first=44  second=86  amount=-1  
+kerning first=44  second=87  amount=-1  
+kerning first=44  second=89  amount=-1  
+kerning first=46  second=84  amount=-1  
+kerning first=46  second=86  amount=-1  
+kerning first=46  second=87  amount=-1  
+kerning first=46  second=89  amount=-1  

BIN
spine-libgdx/spine-skeletonviewer/assets/skin/font-calibri-12.png


+ 124 - 130
spine-libgdx/spine-skeletonviewer/assets/skin/skin.atlas

@@ -1,200 +1,194 @@
 
 skin.png
-size: 256,128
+size: 93,139
 format: RGBA8888
 filter: Linear,Linear
 repeat: none
-check-off
+button-disabled
   rotate: false
-  xy: 11, 5
-  size: 14, 14
-  orig: 14, 14
+  xy: 75, 73
+  size: 17, 17
+  split: 6, 6, 5, 5
+  pad: -1, -1, 3, 3
+  orig: 17, 17
   offset: 0, 0
   index: -1
-textfield
+button-down
   rotate: false
-  xy: 11, 5
-  size: 14, 14
-  split: 3, 3, 3, 3
-  orig: 14, 14
+  xy: 75, 54
+  size: 17, 17
+  split: 6, 6, 5, 5
+  pad: -1, -1, 3, 3
+  orig: 17, 17
   offset: 0, 0
   index: -1
-check-on
+button-over
   rotate: false
-  xy: 125, 35
-  size: 14, 14
-  orig: 14, 14
+  xy: 66, 35
+  size: 17, 17
+  split: 6, 6, 5, 5
+  pad: -1, -1, 3, 3
+  orig: 17, 17
   offset: 0, 0
   index: -1
-cursor
+button-up
   rotate: false
-  xy: 23, 1
-  size: 3, 3
-  split: 1, 1, 1, 1
-  orig: 3, 3
+  xy: 24, 30
+  size: 17, 17
+  split: 6, 6, 5, 5
+  pad: -1, -1, 3, 3
+  orig: 17, 17
   offset: 0, 0
   index: -1
-default
+checkbox-off
   rotate: false
-  xy: 1, 50
-  size: 253, 77
-  orig: 254, 77
-  offset: 1, 0
+  xy: 66, 18
+  size: 15, 15
+  orig: 19, 18
+  offset: 0, 2
   index: -1
-default-pane
+checkbox-offDisabled
   rotate: false
-  xy: 11, 1
-  size: 5, 3
-  split: 1, 1, 1, 1
-  orig: 5, 3
-  offset: 0, 0
+  xy: 24, 13
+  size: 15, 15
+  orig: 19, 18
+  offset: 0, 2
   index: -1
-default-rect-pad
+checkbox-on
   rotate: false
-  xy: 11, 1
-  size: 5, 3
-  split: 1, 1, 1, 1
-  orig: 5, 3
-  offset: 0, 0
-  index: -1
-default-pane-noborder
-  rotate: false
-  xy: 129, 33
-  size: 1, 1
-  split: 0, 0, 0, 0
-  orig: 1, 1
-  offset: 0, 0
-  index: -1
-default-rect
-  rotate: false
-  xy: 38, 25
-  size: 3, 3
-  split: 1, 1, 1, 1
-  orig: 3, 3
-  offset: 0, 0
+  xy: 1, 5
+  size: 15, 15
+  orig: 19, 18
+  offset: 0, 2
   index: -1
-default-rect-down
+checkbox-onDisabled
   rotate: false
-  xy: 170, 46
-  size: 3, 3
-  split: 1, 1, 1, 1
-  orig: 3, 3
-  offset: 0, 0
+  xy: 66, 1
+  size: 15, 15
+  orig: 19, 18
+  offset: 0, 2
   index: -1
-default-round
+group
   rotate: false
-  xy: 112, 29
-  size: 12, 20
-  split: 5, 5, 5, 4
-  pad: 4, 4, 1, 1
-  orig: 12, 20
+  xy: 27, 72
+  size: 23, 23
+  split: 6, 6, 8, 8
+  pad: 11, 11, 11, 11
+  orig: 23, 23
   offset: 0, 0
   index: -1
-default-round-down
+list-selection
   rotate: false
-  xy: 99, 29
-  size: 12, 20
-  split: 5, 5, 5, 4
-  pad: 4, 4, 1, 1
-  orig: 12, 20
+  xy: 18, 8
+  size: 11, 3
+  split: 5, 5, 1, 1
+  orig: 11, 3
   offset: 0, 0
   index: -1
-default-round-large
+loading
   rotate: false
-  xy: 57, 29
-  size: 20, 20
-  split: 5, 5, 5, 4
-  orig: 20, 20
+  xy: 61, 92
+  size: 30, 31
+  orig: 30, 31
   offset: 0, 0
   index: -1
-default-scroll
+scrollpane-horiz
   rotate: false
-  xy: 78, 29
-  size: 20, 20
-  split: 2, 2, 2, 2
-  orig: 20, 20
+  xy: 1, 120
+  size: 58, 18
+  split: 3, 51, 0, 18
+  orig: 58, 18
   offset: 0, 0
   index: -1
-default-select
+scrollpane-vert
   rotate: false
-  xy: 29, 29
-  size: 27, 20
-  split: 4, 14, 4, 4
-  orig: 27, 20
+  xy: 46, 9
+  size: 18, 58
+  split: 0, 18, 51, 3
+  orig: 18, 58
   offset: 0, 0
   index: -1
-default-select-selection
+selectBox-closed
   rotate: false
-  xy: 26, 16
-  size: 3, 3
-  split: 1, 1, 1, 1
-  orig: 3, 3
+  xy: 1, 97
+  size: 24, 21
+  split: 6, 14, 5, 13
+  pad: 5, 13, 3, 3
+  orig: 24, 21
   offset: 0, 0
   index: -1
-default-slider
+selectBox-list
   rotate: false
-  xy: 29, 20
-  size: 8, 8
-  split: 2, 2, 2, 2
-  orig: 8, 8
+  xy: 24, 49
+  size: 20, 21
+  split: 5, 12, 5, 13
+  orig: 20, 21
   offset: 0, 0
   index: -1
-default-slider-knob
+selectBox-open
   rotate: false
-  xy: 1, 1
-  size: 9, 18
-  orig: 9, 18
+  xy: 27, 97
+  size: 24, 21
+  split: 6, 14, 5, 13
+  pad: 5, 13, 3, 3
+  orig: 24, 21
   offset: 0, 0
   index: -1
-default-splitpane
+selectBox-over
   rotate: false
-  xy: 17, 1
-  size: 5, 3
-  split: 0, 5, 0, 0
-  orig: 5, 3
+  xy: 1, 74
+  size: 24, 21
+  split: 6, 14, 5, 13
+  pad: 5, 13, 3, 3
+  orig: 24, 21
   offset: 0, 0
   index: -1
-default-splitpane-vertical
+slider-bg
   rotate: false
-  xy: 125, 29
-  size: 3, 5
-  split: 0, 0, 0, 5
-  orig: 3, 5
+  xy: 61, 125
+  size: 31, 13
+  split: 7, 6, 0, 13
+  pad: 3, 2, -1, -1
+  orig: 31, 13
   offset: 0, 0
   index: -1
-default-window
+slider-handle
   rotate: false
-  xy: 1, 20
-  size: 27, 29
-  split: 4, 3, 20, 3
-  orig: 27, 29
+  xy: 53, 101
+  size: 6, 17
+  orig: 6, 17
   offset: 0, 0
   index: -1
-selection
+textField-cursor
   rotate: false
-  xy: 170, 44
-  size: 1, 1
-  orig: 1, 1
+  xy: 53, 96
+  size: 3, 3
+  split: 1, 1, 1, 1
+  orig: 3, 3
   offset: 0, 0
   index: -1
-tree-minus
+textField-round
   rotate: false
-  xy: 140, 35
-  size: 14, 14
-  orig: 14, 14
+  xy: 52, 69
+  size: 21, 21
+  split: 6, 5, 5, 5
+  pad: -1, -1, 3, 3
+  orig: 21, 21
   offset: 0, 0
   index: -1
-tree-plus
+white
   rotate: false
-  xy: 155, 35
-  size: 14, 14
-  orig: 14, 14
+  xy: 66, 64
+  size: 3, 3
+  orig: 3, 3
   offset: 0, 0
   index: -1
-white
+window
   rotate: false
-  xy: 174, 48
-  size: 1, 1
-  orig: 1, 1
+  xy: 1, 22
+  size: 21, 50
+  split: 10, 10, 36, 11
+  pad: 9, 9, 41, 8
+  orig: 21, 50
   offset: 0, 0
   index: -1

+ 49 - 42
spine-libgdx/spine-skeletonviewer/assets/skin/skin.json

@@ -1,59 +1,66 @@
 {
-com.badlogic.gdx.graphics.g2d.BitmapFont: { default-font: { file: com/badlogic/gdx/utils/arial-15.fnt } },
+com.badlogic.gdx.graphics.g2d.BitmapFont: {
+	default: { file: font-calibri-12.fnt }
+}
 com.badlogic.gdx.graphics.Color: {
-	green: { a: 1, b: 0, g: 1, r: 0 },
-	white: { a: 1, b: 1, g: 1, r: 1 },
-	red: { a: 1, b: 0, g: 0, r: 1 },
-	black: { a: 1, b: 0, g: 0, r: 0 }
+	white: { r: 1, g: 1, b: 1 },
+	black: {},
+	disabled: { r: 0.53, g: 0.53, b: 0.53 },
+	selection: { r: 0.77, g: 1, b: 1, a: 0.25 },
 },
 com.badlogic.gdx.scenes.scene2d.ui.Skin$TintedDrawable: {
-	dialogDim: { name: white, color: { r: 0, g: 0, b: 0, a: 0.45 } }
-},
-com.badlogic.gdx.scenes.scene2d.ui.Button$ButtonStyle: {
-	default: { down: default-round-down, up: default-round },
-	toggle: { down: default-round-down, checked: default-round-down, up: default-round }
+	selection: { name: white, color: selection },
+	dim: { name: white, color: { r: 0, g: 0, b: 0, a: 0.3 } },
+	list-selection: { name: list-selection, color: selection },
 },
 com.badlogic.gdx.scenes.scene2d.ui.TextButton$TextButtonStyle: {
-	default: { down: default-round-down, up: default-round, font: default-font, fontColor: white },
-	toggle: { down: default-round-down, up: default-round, checked: default-round-down, font: default-font, fontColor: white, downFontColor: red }
-},
-com.badlogic.gdx.scenes.scene2d.ui.ScrollPane$ScrollPaneStyle: {
-	default: { vScroll: default-scroll, hScrollKnob: default-round-large, background: default-rect, hScroll: default-scroll, vScrollKnob: default-round-large }
-},
-com.badlogic.gdx.scenes.scene2d.ui.SelectBox$SelectBoxStyle: {
 	default: {
-		font: default-font, fontColor: white, background: default-select,
-		scrollStyle: default,
-		listStyle: { font: default-font, selection: default-select-selection }
-	}
-},
-com.badlogic.gdx.scenes.scene2d.ui.SplitPane$SplitPaneStyle: {
-	default-vertical: { handle: default-splitpane-vertical },
-	default-horizontal: { handle: default-splitpane }
+		up: button-up, down: button-down, over: button-over, disabled: button-disabled,
+		font: default, fontColor: white, disabledFontColor: disabled, pressedOffsetY: -1
+	},
+	toggle: {
+		up: button-up, down: button-down, over: button-over, disabled: button-disabled, checked: button-down,
+		font: default, fontColor: white, disabledFontColor: disabled, pressedOffsetY: -1
+	},
 },
+com.badlogic.gdx.scenes.scene2d.ui.Label$LabelStyle: {
+	default: { font: default, fontColor: white },
+	title: { font: default, fontColor: { hex: 00ffccff } },
+}
+com.badlogic.gdx.scenes.scene2d.ui.TextField$TextFieldStyle: {
+	default: { background: textField-round, font: default, fontColor: white, disabledFontColor: disabled, 
+		selection: selection, cursor: textField-cursor },
+}
 com.badlogic.gdx.scenes.scene2d.ui.Window$WindowStyle: {
-	default: { titleFont: default-font, background: default-window, titleFontColor: white },
-	dialog: { titleFont: default-font, background: default-window, titleFontColor: white, stageBackground: dialogDim }
+	default: { titleFont: default, titleFontColor: white, background: window },
 },
-com.badlogic.gdx.scenes.scene2d.ui.Slider$SliderStyle: {
-	default-horizontal: { background: default-slider, knob: default-slider-knob }
+com.badlogic.gdx.scenes.scene2d.ui.ScrollPane$ScrollPaneStyle: {
+	default: { hScrollKnob: scrollpane-horiz, vScrollKnob: scrollpane-vert },
+	bg: { hScrollKnob: scrollpane-horiz, vScrollKnob: scrollpane-vert, background: textField-round },
 },
-com.badlogic.gdx.scenes.scene2d.ui.Label$LabelStyle: {
-	default: { font: default-font, fontColor: white }
+com.badlogic.gdx.scenes.scene2d.ui.List$ListStyle: {
+	default: { font: default, selection: list-selection }
 },
-com.badlogic.gdx.scenes.scene2d.ui.TextField$TextFieldStyle: {
-	default: { selection: selection, background: textfield, font: default-font, fontColor: white, cursor: cursor }
+com.badlogic.gdx.scenes.scene2d.ui.SelectBox$SelectBoxStyle: {
+	default: {
+		background: selectBox-closed, backgroundOver: selectBox-over, backgroundOpen: selectBox-open, font: default, fontColor: white,
+		scrollStyle: { background: selectBox-list, hScrollKnob: scrollpane-horiz, vScrollKnob: scrollpane-vert },
+		listStyle: { font: default, selection: list-selection },
+	},
 },
-com.badlogic.gdx.scenes.scene2d.ui.CheckBox$CheckBoxStyle: {
-	default: { checkboxOn: check-on, checkboxOff: check-off, font: default-font, fontColor: white }
+com.badlogic.gdx.scenes.scene2d.ui.TextTooltip$TextTooltipStyle: {
+	default: {
+		label: { font: default, fontColor: white },
+		background: group
+	},
 },
-com.badlogic.gdx.scenes.scene2d.ui.List$ListStyle: {
-	default: { fontColorUnselected: white, selection: selection, fontColorSelected: white, font: default-font }
+com.badlogic.gdx.scenes.scene2d.ui.CheckBox$CheckBoxStyle: {
+	default: {
+		checkboxOn: checkbox-on, checkboxOff: checkbox-off, checkboxOffDisabled: checkbox-offDisabled,
+		checkboxOnDisabled: checkbox-onDisabled, font: default, fontColor: white, disabledFontColor: disabled
+	},
 },
-com.badlogic.gdx.scenes.scene2d.ui.Touchpad$TouchpadStyle: {
-	default: { background: default-pane, knob: default-round-large }
+com.badlogic.gdx.scenes.scene2d.ui.Slider$SliderStyle: {
+	default-horizontal: { background: slider-bg, knob: slider-handle },
 },
-com.badlogic.gdx.scenes.scene2d.ui.Tree$TreeStyle: {
-	default: { minus: tree-minus, plus: tree-plus, selection: default-select-selection }
-}
 }

BIN
spine-libgdx/spine-skeletonviewer/assets/skin/skin.png


+ 62 - 51
spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java

@@ -50,6 +50,7 @@ import com.badlogic.gdx.graphics.GL20;
 import com.badlogic.gdx.graphics.Pixmap;
 import com.badlogic.gdx.graphics.Pixmap.Format;
 import com.badlogic.gdx.graphics.Texture;
+import com.badlogic.gdx.graphics.Texture.TextureFilter;
 import com.badlogic.gdx.graphics.g2d.PolygonSpriteBatch;
 import com.badlogic.gdx.graphics.g2d.TextureAtlas;
 import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
@@ -83,7 +84,7 @@ public class SkeletonViewer extends ApplicationAdapter {
 	UI ui;
 
 	PolygonSpriteBatch batch;
-	SkeletonRenderer renderer;
+	SkeletonMeshRenderer renderer;
 	SkeletonRendererDebug debugRenderer;
 	SkeletonData skeletonData;
 	Skeleton skeleton;
@@ -96,38 +97,49 @@ public class SkeletonViewer extends ApplicationAdapter {
 	public void create () {
 		ui = new UI();
 		batch = new PolygonSpriteBatch();
-		renderer = new SkeletonRenderer();
+		renderer = new SkeletonMeshRenderer();
 		debugRenderer = new SkeletonRendererDebug();
 		skeletonX = (int)(ui.window.getWidth() + (Gdx.graphics.getWidth() - ui.window.getWidth()) / 2);
 		skeletonY = Gdx.graphics.getHeight() / 4;
 
 		loadSkeleton(
-			Gdx.files.internal(Gdx.app.getPreferences("spine-skeletontest").getString("lastFile", "spineboy/spineboy.json")), false);
+			Gdx.files.internal(Gdx.app.getPreferences("spine-skeletonviewer").getString("lastFile", "spineboy/spineboy.json")),
+			false);
 	}
 
-	void loadSkeleton (FileHandle skeletonFile, boolean reload) {
+	void loadSkeleton (final FileHandle skeletonFile, boolean reload) {
 		if (skeletonFile == null) return;
 
-		// A regular texture atlas would normally usually be used. This returns a white image for images not found in the atlas.
-		Pixmap pixmap = new Pixmap(32, 32, Format.RGBA8888);
-		pixmap.setColor(new Color(1, 1, 1, 0.33f));
-		pixmap.fill();
-		final AtlasRegion fake = new AtlasRegion(new Texture(pixmap), 0, 0, 32, 32);
-		pixmap.dispose();
-
-		String atlasFileName = skeletonFile.nameWithoutExtension();
-		if (atlasFileName.endsWith(".json")) atlasFileName = new FileHandle(atlasFileName).nameWithoutExtension();
-		FileHandle atlasFile = skeletonFile.sibling(atlasFileName + ".atlas");
-		if (!atlasFile.exists()) atlasFile = skeletonFile.sibling(atlasFileName + ".atlas.txt");
-		TextureAtlasData data = !atlasFile.exists() ? null : new TextureAtlasData(atlasFile, atlasFile.parent(), false);
-		TextureAtlas atlas = new TextureAtlas(data) {
-			public AtlasRegion findRegion (String name) {
-				AtlasRegion region = super.findRegion(name);
-				return region != null ? region : fake;
-			}
-		};
-
 		try {
+			// A regular texture atlas would normally usually be used. This returns a white image for images not found in the atlas.
+			Pixmap pixmap = new Pixmap(32, 32, Format.RGBA8888);
+			pixmap.setColor(new Color(1, 1, 1, 0.33f));
+			pixmap.fill();
+			final AtlasRegion fake = new AtlasRegion(new Texture(pixmap), 0, 0, 32, 32);
+			pixmap.dispose();
+
+			String atlasFileName = skeletonFile.nameWithoutExtension();
+			if (atlasFileName.endsWith(".json")) atlasFileName = new FileHandle(atlasFileName).nameWithoutExtension();
+			FileHandle atlasFile = skeletonFile.sibling(atlasFileName + ".atlas");
+			if (!atlasFile.exists()) atlasFile = skeletonFile.sibling(atlasFileName + ".atlas.txt");
+			TextureAtlasData data = !atlasFile.exists() ? null : new TextureAtlasData(atlasFile, atlasFile.parent(), false);
+			TextureAtlas atlas = new TextureAtlas(data) {
+				public AtlasRegion findRegion (String name) {
+					AtlasRegion region = super.findRegion(name);
+					if (region == null) {
+						// Look for separate image file.
+						FileHandle file = skeletonFile.sibling(name + ".png");
+						if (file.exists()) {
+							Texture texture = new Texture(file);
+							texture.setFilter(TextureFilter.Linear, TextureFilter.Linear);
+							region = new AtlasRegion(texture, 0, 0, texture.getWidth(), texture.getHeight());
+							region.name = name;
+						}
+					}
+					return region != null ? region : fake;
+				}
+			};
+
 			String extension = skeletonFile.extension();
 			if (extension.equalsIgnoreCase("json") || extension.equalsIgnoreCase("txt")) {
 				SkeletonJson json = new SkeletonJson(atlas);
@@ -137,6 +149,7 @@ public class SkeletonViewer extends ApplicationAdapter {
 				SkeletonBinary binary = new SkeletonBinary(atlas);
 				binary.setScale(ui.scaleSlider.getValue());
 				skeletonData = binary.readSkeletonData(skeletonFile);
+				if (skeletonData.getBones().size == 0) throw new Exception("No bones in skeleton data.");
 			}
 		} catch (Exception ex) {
 			ex.printStackTrace();
@@ -153,7 +166,7 @@ public class SkeletonViewer extends ApplicationAdapter {
 		state = new AnimationState(new AnimationStateData(skeletonData));
 
 		this.skeletonFile = skeletonFile;
-		Preferences prefs = Gdx.app.getPreferences("spine-skeletontest");
+		Preferences prefs = Gdx.app.getPreferences("spine-skeletonviewer");
 		prefs.putString("lastFile", skeletonFile.path());
 		prefs.flush();
 		lastModified = skeletonFile.lastModified();
@@ -161,7 +174,7 @@ public class SkeletonViewer extends ApplicationAdapter {
 
 		// Populate UI.
 
-		ui.skeletonLabel.setText(skeletonFile.name());
+		ui.window.getTitleLabel().setText(skeletonFile.name());
 		{
 			Array<String> items = new Array();
 			for (Skin skin : skeletonData.getSkins())
@@ -177,8 +190,9 @@ public class SkeletonViewer extends ApplicationAdapter {
 
 		// Configure skeleton from UI.
 
-		skeleton.setSkin(ui.skinList.getSelected());
-		state.setAnimation(0, ui.animationList.getSelected(), ui.loopCheckbox.isChecked());
+		if (ui.skinList.getSelected() != null) skeleton.setSkin(ui.skinList.getSelected());
+		if (ui.animationList.getSelected() != null)
+			state.setAnimation(0, ui.animationList.getSelected(), ui.loopCheckbox.isChecked());
 
 		if (reload) ui.toast("Reloaded.");
 	}
@@ -217,6 +231,7 @@ public class SkeletonViewer extends ApplicationAdapter {
 			// skeleton.getRootBone().setY(skeletonY);
 			skeleton.updateWorldTransform();
 
+			batch.setColor(Color.WHITE);
 			batch.begin();
 			renderer.draw(batch, skeleton);
 			batch.end();
@@ -252,7 +267,7 @@ public class SkeletonViewer extends ApplicationAdapter {
 		batch.getProjectionMatrix().setToOrtho2D(0, 0, width, height);
 		debugRenderer.getShapeRenderer().setProjectionMatrix(batch.getProjectionMatrix());
 		ui.stage.getViewport().update(width, height, true);
-		if (!ui.minimizeButton.isChecked()) ui.window.setHeight(height);
+		if (!ui.minimizeButton.isChecked()) ui.window.setHeight(height + 8);
 	}
 
 	class UI {
@@ -262,8 +277,7 @@ public class SkeletonViewer extends ApplicationAdapter {
 
 		Window window = new Window("Skeleton", skin);
 		Table root = new Table(skin);
-		TextButton browseButton = new TextButton("Browse", skin);
-		Label skeletonLabel = new Label("", skin);
+		TextButton openButton = new TextButton("Open", skin);
 		List<String> animationList = new List(skin);
 		List<String> skinList = new List(skin);
 		CheckBox loopCheckbox = new CheckBox(" Loop", skin);
@@ -305,30 +319,24 @@ public class SkeletonViewer extends ApplicationAdapter {
 
 			window.setMovable(false);
 			window.setResizable(false);
+			window.setKeepWithinStage(false);
+			window.setX(-3);
+			window.setY(-2);
 
-			minimizeButton.padTop(-2).padLeft(5);
-			minimizeButton.getColor().a = 0.66f;
-			window.getTitleTable().add(minimizeButton).size(20, 20);
+			window.getTitleTable().add(openButton).space(3);
+			window.getTitleTable().add(minimizeButton).width(20);
 
-			ScrollPane skinScroll = new ScrollPane(skinList, skin);
+			ScrollPane skinScroll = new ScrollPane(skinList, skin, "bg");
 			skinScroll.setFadeScrollBars(false);
 
-			ScrollPane animationScroll = new ScrollPane(animationList, skin);
+			ScrollPane animationScroll = new ScrollPane(animationList, skin, "bg");
 			animationScroll.setFadeScrollBars(false);
 
 			// Layout.
 
-			root.pad(2, 4, 4, 4).defaults().space(6);
-			root.columnDefaults(0).top().right();
+			root.defaults().space(6);
+			root.columnDefaults(0).top().right().padTop(3);
 			root.columnDefaults(1).left();
-			root.row().padTop(6);
-			root.add("Skeleton:");
-			{
-				Table table = table();
-				table.add(skeletonLabel).fillX().expandX();
-				table.add(browseButton);
-				root.add(table).fill().row();
-			}
 			root.add("Scale:");
 			{
 				Table table = table();
@@ -378,7 +386,6 @@ public class SkeletonViewer extends ApplicationAdapter {
 				stage.addActor(table);
 				table.pad(10).bottom().right();
 				table.add(toasts);
-				table.debug();
 			}
 
 			// Events.
@@ -390,7 +397,7 @@ public class SkeletonViewer extends ApplicationAdapter {
 				}
 			});
 
-			browseButton.addListener(new ChangeListener() {
+			openButton.addListener(new ChangeListener() {
 				public void changed (ChangeEvent event, Actor actor) {
 					FileDialog fileDialog = new FileDialog((Frame)null, "Choose skeleton file");
 					fileDialog.setMode(FileDialog.LOAD);
@@ -427,11 +434,11 @@ public class SkeletonViewer extends ApplicationAdapter {
 				public void clicked (InputEvent event, float x, float y) {
 					if (minimizeButton.isChecked()) {
 						window.getCells().get(0).setActor(null);
-						window.setHeight(20);
+						window.setHeight(37);
 						minimizeButton.setText("+");
 					} else {
 						window.getCells().get(0).setActor(root);
-						ui.window.setHeight(Gdx.graphics.getHeight());
+						ui.window.setHeight(Gdx.graphics.getHeight() + 8);
 						minimizeButton.setText("-");
 					}
 				}
@@ -466,7 +473,11 @@ public class SkeletonViewer extends ApplicationAdapter {
 			skinList.addListener(new ChangeListener() {
 				public void changed (ChangeEvent event, Actor actor) {
 					if (skeleton != null) {
-						skeleton.setSkin(skinList.getSelected());
+						String skinName = skinList.getSelected();
+						if (skinName == null)
+							skeleton.setSkin((Skin)null);
+						else
+							skeleton.setSkin(skinName);
 						skeleton.setSlotsToSetupPose();
 					}
 				}
@@ -504,7 +515,7 @@ public class SkeletonViewer extends ApplicationAdapter {
 				delay(5f), //
 				parallel(moveBy(0, table.getHeight(), 0.3f), fadeOut(0.3f)), //
 				removeActor() //
-				));
+			));
 			for (Actor actor : toasts.getChildren())
 				actor.addAction(moveBy(0, table.getHeight(), 0.3f));
 			toasts.addActor(table);