Răsfoiți Sursa

Merge branch '3.7-beta' into 3.7-beta-cpp

badlogic 7 ani în urmă
părinte
comite
fbe0692c4c

+ 2 - 2
spine-cpp/spine-cpp/src/spine/AtlasAttachmentLoader.cpp

@@ -53,7 +53,7 @@ RegionAttachment *AtlasAttachmentLoader::newRegionAttachment(Skin &skin, const S
 	SP_UNUSED(skin);
 
 	AtlasRegion *regionP = findRegion(path);
-	assert(regionP != NULL);
+	if (!regionP) return NULL;
 
 	AtlasRegion &region = *regionP;
 
@@ -75,7 +75,7 @@ MeshAttachment *AtlasAttachmentLoader::newMeshAttachment(Skin &skin, const Strin
 	SP_UNUSED(skin);
 
 	AtlasRegion *regionP = findRegion(path);
-	assert(regionP != NULL);
+	if (!regionP) return NULL;
 
 	AtlasRegion &region = *regionP;
 

+ 6 - 0
spine-cpp/spine-cpp/src/spine/SkeletonJson.cpp

@@ -506,6 +506,12 @@ SkeletonData *SkeletonJson::readSkeletonData(const char *json) {
 						case AttachmentType_Linkedmesh: {
 							attachment = _attachmentLoader->newMeshAttachment(*skin, attachmentName, attachmentPath);
 
+							if (!attachment) {
+								delete skeletonData;
+								setError(root, "Error reading attachment: ", skinAttachmentName);
+								return NULL;
+							}
+
 							MeshAttachment *mesh = static_cast<MeshAttachment *>(attachment);
 							mesh->_path = attachmentPath;
 

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

@@ -50,7 +50,7 @@ public class TestHarness extends ApplicationAdapter {
 		
 		skeleton = new Skeleton(skeletonData);		
 		skeleton.setPosition(320, 590);
-		skeleton.flipY = true;
+		skeleton.setScaleY(-1);
 
 		AnimationStateData stateData = new AnimationStateData(skeletonData);		
 		state = new AnimationState(stateData);		

+ 16 - 39
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java

@@ -30,13 +30,12 @@
 
 package com.esotericsoftware.spine;
 
-import static com.esotericsoftware.spine.utils.SpineUtils.*;
 import static com.badlogic.gdx.math.Matrix3.*;
+import static com.esotericsoftware.spine.utils.SpineUtils.*;
 
 import com.badlogic.gdx.math.Matrix3;
 import com.badlogic.gdx.math.Vector2;
 import com.badlogic.gdx.utils.Array;
-import com.esotericsoftware.spine.BoneData.TransformMode;
 
 /** Stores a bone's current pose.
  * <p>
@@ -111,28 +110,14 @@ public class Bone implements Updatable {
 
 		Bone parent = this.parent;
 		if (parent == null) { // Root bone.
-			float rotationY = rotation + 90 + shearY;
-			float la = cosDeg(rotation + shearX) * scaleX;
-			float lb = cosDeg(rotationY) * scaleY;
-			float lc = sinDeg(rotation + shearX) * scaleX;
-			float ld = sinDeg(rotationY) * scaleY;
 			Skeleton skeleton = this.skeleton;
-			if (skeleton.flipX) {
-				x = -x;
-				la = -la;
-				lb = -lb;
-			}
-			if (skeleton.flipY) {
-				y = -y;
-				lc = -lc;
-				ld = -ld;
-			}
-			a = la;
-			b = lb;
-			c = lc;
-			d = ld;
-			worldX = x + skeleton.x;
-			worldY = y + skeleton.y;
+			float rotationY = rotation + 90 + shearY, sx = skeleton.scaleX, sy = skeleton.scaleY;
+			a = cosDeg(rotation + shearX) * scaleX * sx;
+			b = cosDeg(rotationY) * scaleY * sy;
+			c = sinDeg(rotation + shearX) * scaleX * sx;
+			d = sinDeg(rotationY) * scaleY * sy;
+			worldX = x * sx + skeleton.x;
+			worldY = y * sy + skeleton.y;
 			return;
 		}
 
@@ -188,8 +173,8 @@ public class Bone implements Updatable {
 		case noScale:
 		case noScaleOrReflection: {
 			float cos = cosDeg(rotation), sin = sinDeg(rotation);
-			float za = pa * cos + pb * sin;
-			float zc = pc * cos + pd * sin;
+			float za = (pa * cos + pb * sin) / skeleton.scaleX;
+			float zc = (pc * cos + pd * sin) / skeleton.scaleY;
 			float s = (float)Math.sqrt(za * za + zc * zc);
 			if (s > 0.00001f) s = 1 / s;
 			za *= s;
@@ -201,26 +186,18 @@ public class Bone implements Updatable {
 			float la = cosDeg(shearX) * scaleX;
 			float lb = cosDeg(90 + shearY) * scaleY;
 			float lc = sinDeg(shearX) * scaleX;
-			float ld = sinDeg(90 + shearY) * scaleY;			
-			if (data.transformMode != TransformMode.noScaleOrReflection ? pa * pd - pb * pc < 0 : skeleton.flipX != skeleton.flipY) {
-			    zb = -zb;
-			    zd = -zd;
-			}			
+			float ld = sinDeg(90 + shearY) * scaleY;
 			a = za * la + zb * lc;
 			b = za * lb + zb * ld;
 			c = zc * la + zd * lc;
 			d = zc * lb + zd * ld;
-			return;
-		}
-		}
-		if (skeleton.flipX) {
-			a = -a;
-			b = -b;
+			break;
 		}
-		if (skeleton.flipY) {
-			c = -c;
-			d = -d;
 		}
+		a *= skeleton.scaleX;
+		b *= skeleton.scaleX;
+		c *= skeleton.scaleY;
+		d *= skeleton.scaleY;
 	}
 
 	/** Sets this bone's local transform to the setup pose. */

+ 29 - 38
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Skeleton.java

@@ -30,14 +30,14 @@
 
 package com.esotericsoftware.spine;
 
-import static com.esotericsoftware.spine.utils.SpineUtils.cosDeg;
-import static com.esotericsoftware.spine.utils.SpineUtils.sinDeg;
+import static com.esotericsoftware.spine.utils.SpineUtils.*;
 
 import com.badlogic.gdx.graphics.Color;
 import com.badlogic.gdx.math.Vector2;
 import com.badlogic.gdx.utils.Array;
 import com.badlogic.gdx.utils.FloatArray;
 import com.badlogic.gdx.utils.ObjectMap.Entry;
+
 import com.esotericsoftware.spine.Skin.Key;
 import com.esotericsoftware.spine.attachments.Attachment;
 import com.esotericsoftware.spine.attachments.MeshAttachment;
@@ -61,7 +61,7 @@ public class Skeleton {
 	Skin skin;
 	final Color color;
 	float time;
-	boolean flipX, flipY;
+	float scaleX = 1, scaleY = 1;
 	float x, y;
 
 	public Skeleton (SkeletonData data) {
@@ -150,8 +150,8 @@ public class Skeleton {
 		skin = skeleton.skin;
 		color = new Color(skeleton.color);
 		time = skeleton.time;
-		flipX = skeleton.flipX;
-		flipY = skeleton.flipY;
+		scaleX = skeleton.scaleX;
+		scaleY = skeleton.scaleY;
 
 		updateCache();
 	}
@@ -331,9 +331,9 @@ public class Skeleton {
 		for (int i = 0, n = updateCache.size; i < n; i++)
 			updateCache.get(i).update();
 	}
-	
-	/** Updates the world transform for each bone and applies all constraints. The 
-	 *  root bone will be temporarily parented to the specified bone.
+
+	/** Updates the world transform for each bone and applies all constraints. The root bone will be temporarily parented to the
+	 * specified bone.
 	 * <p>
 	 * See <a href="http://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
 	 * Runtimes Guide. */
@@ -353,7 +353,7 @@ public class Skeleton {
 			bone.ashearY = bone.shearY;
 			bone.appliedValid = true;
 		}
-		
+
 		// Apply the parent bone transform to the root bone. The root bone
 		// always inherits scale, rotation and reflection.
 		Bone rootBone = getRootBone();
@@ -366,20 +366,11 @@ public class Skeleton {
 		float lb = cosDeg(rotationY) * rootBone.scaleY;
 		float lc = sinDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX;
 		float ld = sinDeg(rotationY) * rootBone.scaleY;
-		rootBone.a = pa * la + pb * lc;
-		rootBone.b = pa * lb + pb * ld;
-		rootBone.c = pc * la + pd * lc;
-		rootBone.d = pc * lb + pd * ld;
-		
-		if (flipY) {
-			rootBone.a = -rootBone.a;
-			rootBone.b = -rootBone.b;
-		}
-		if (flipX) {
-			rootBone.c = -rootBone.c;
-			rootBone.d = -rootBone.d;
-		}
-		
+		rootBone.a = (pa * la + pb * lc) * scaleX;
+		rootBone.b = (pa * lb + pb * ld) * scaleX;
+		rootBone.c = (pc * la + pd * lc) * scaleY;
+		rootBone.d = (pc * lb + pd * ld) * scaleY;
+
 		// Update everything except root bone.
 		Array<Updatable> updateCache = this.updateCache;
 		for (int i = 0, n = updateCache.size; i < n; i++) {
@@ -685,29 +676,29 @@ public class Skeleton {
 		this.color.set(color);
 	}
 
-	/** If true, the entire skeleton is flipped over the Y axis. This affects all bones, even if the bone's transform mode
-	 * disallows scale inheritance. */
-	public boolean getFlipX () {
-		return flipX;
+	/** Scales the entire skeleton on the X axis. This affects all bones, even if the bone's transform mode disallows scale
+	 * inheritance. */
+	public float getScaleX () {
+		return scaleX;
 	}
 
-	public void setFlipX (boolean flipX) {
-		this.flipX = flipX;
+	public void setScaleX (float scaleX) {
+		this.scaleX = scaleX;
 	}
 
-	/** If true, the entire skeleton is flipped over the X axis. This affects all bones, even if the bone's transform mode
-	 * disallows scale inheritance. */
-	public boolean getFlipY () {
-		return flipY;
+	/** Scales the entire skeleton on the Y axis. This affects all bones, even if the bone's transform mode disallows scale
+	 * inheritance. */
+	public float getScaleY () {
+		return scaleY;
 	}
 
-	public void setFlipY (boolean flipY) {
-		this.flipY = flipY;
+	public void setScaleY (float scaleY) {
+		this.scaleY = scaleY;
 	}
 
-	public void setFlip (boolean flipX, boolean flipY) {
-		this.flipX = flipX;
-		this.flipY = flipY;
+	public void setScale (float scaleX, float scaleY) {
+		this.scaleX = scaleX;
+		this.scaleY = scaleY;
 	}
 
 	/** Sets the skeleton X position, which is added to the root bone worldX position. */

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

@@ -70,7 +70,7 @@ public class SkeletonActorPool extends Pool<SkeletonActor> {
 
 			protected void reset (Skeleton skeleton) {
 				skeleton.setColor(Color.WHITE);
-				skeleton.setFlip(false, false);
+				skeleton.setScale(1, 1);
 				skeleton.setSkin((Skin)null);
 				skeleton.setSkin(SkeletonActorPool.this.skeletonData.getDefaultSkin());
 				skeleton.setToSetupPose();

+ 90 - 38
spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java

@@ -192,11 +192,11 @@ public class SkeletonViewer extends ApplicationAdapter {
 			String extension = skeletonFile.extension();
 			if (extension.equalsIgnoreCase("json") || extension.equalsIgnoreCase("txt")) {
 				SkeletonJson json = new SkeletonJson(atlas);
-				json.setScale(ui.scaleSlider.getValue());
+				json.setScale(ui.loadScaleSlider.getValue());
 				skeletonData = json.readSkeletonData(skeletonFile);
 			} else {
 				SkeletonBinary binary = new SkeletonBinary(atlas);
-				binary.setScale(ui.scaleSlider.getValue());
+				binary.setScale(ui.loadScaleSlider.getValue());
 				skeletonData = binary.readSkeletonData(skeletonFile);
 				if (skeletonData.getBones().size == 0) throw new Exception("No bones in skeleton data.");
 			}
@@ -311,7 +311,10 @@ public class SkeletonViewer extends ApplicationAdapter {
 			renderer.setPremultipliedAlpha(ui.premultipliedCheckbox.isChecked());
 			batch.setPremultipliedAlpha(ui.premultipliedCheckbox.isChecked());
 
-			skeleton.setFlip(ui.flipXCheckbox.isChecked(), ui.flipYCheckbox.isChecked());
+			float scaleX = ui.xScaleSlider.getValue(), scaleY = ui.yScaleSlider.getValue();
+			if (skeleton.scaleX == 0) skeleton.scaleX = 0.01f;
+			if (skeleton.scaleY == 0) skeleton.scaleY = 0.01f;
+			skeleton.setScale(scaleX, scaleY);
 
 			delta = Math.min(delta, 0.032f) * ui.speedSlider.getValue();
 			skeleton.update(delta);
@@ -412,16 +415,21 @@ public class SkeletonViewer extends ApplicationAdapter {
 		TextButton openButton = new TextButton("Open", skin);
 		TextButton minimizeButton = new TextButton("-", skin);
 
-		Slider scaleSlider = new Slider(0.1f, 3, 0.01f, false, skin);
-		Label scaleLabel = new Label("1.0", skin);
-		TextButton scaleResetButton = new TextButton("Reset", skin);
+		Slider loadScaleSlider = new Slider(0.1f, 3, 0.01f, false, skin);
+		Label loadScaleLabel = new Label("100%", skin);
+		TextButton loadScaleResetButton = new TextButton("Reset", skin);
 
 		Slider zoomSlider = new Slider(0.01f, 10, 0.01f, false, skin);
-		Label zoomLabel = new Label("1.0", skin);
+		Label zoomLabel = new Label("100%", skin);
 		TextButton zoomResetButton = new TextButton("Reset", skin);
 
-		CheckBox flipXCheckbox = new CheckBox("X", skin);
-		CheckBox flipYCheckbox = new CheckBox("Y", skin);
+		Slider xScaleSlider = new Slider(-2, 2, 0.01f, false, skin);
+		Label xScaleLabel = new Label("100%", skin);
+		TextButton xScaleResetButton = new TextButton("Reset", skin);
+
+		Slider yScaleSlider = new Slider(-2, 2, 0.01f, false, skin);
+		Label yScaleLabel = new Label("100%", skin);
+		TextButton yScaleResetButton = new TextButton("Reset", skin);
 
 		CheckBox debugBonesCheckbox = new CheckBox("Bones", skin);
 		CheckBox debugRegionsCheckbox = new CheckBox("Regions", skin);
@@ -448,17 +456,17 @@ public class SkeletonViewer extends ApplicationAdapter {
 		CheckBox addCheckbox = new CheckBox("Add", skin);
 
 		Slider alphaSlider = new Slider(0, 1, 0.01f, false, skin);
-		Label alphaLabel = new Label("1.0", skin);
+		Label alphaLabel = new Label("100%", skin);
 
 		List<String> animationList = new List(skin);
 		ScrollPane animationScroll = new ScrollPane(animationList, skin, "bg");
 
 		Slider speedSlider = new Slider(0, 3, 0.01f, false, skin);
-		Label speedLabel = new Label("1.0", skin);
+		Label speedLabel = new Label("1.0x", skin);
 		TextButton speedResetButton = new TextButton("Reset", skin);
 
 		Slider mixSlider = new Slider(0, 4, 0.01f, false, skin);
-		Label mixLabel = new Label("0.3", skin);
+		Label mixLabel = new Label("0.3s", skin);
 
 		Label statusLabel = new Label("", skin);
 		WidgetGroup toasts = new WidgetGroup();
@@ -483,21 +491,27 @@ public class SkeletonViewer extends ApplicationAdapter {
 
 			loopCheckbox.setChecked(true);
 
-			scaleSlider.setValue(1);
-			scaleSlider.setSnapToValues(new float[] {0.5f, 1, 1.5f, 2, 2.5f, 3, 3.5f}, 0.01f);
+			loadScaleSlider.setValue(1);
+			loadScaleSlider.setSnapToValues(new float[] {0.5f, 1, 1.5f, 2, 2.5f}, 0.09f);
 
 			zoomSlider.setValue(1);
-			zoomSlider.setSnapToValues(new float[] {0.5f, 1, 1.5f, 2, 2.5f, 3, 3.5f}, 0.01f);
+			zoomSlider.setSnapToValues(new float[] {1, 2}, 0.30f);
+
+			xScaleSlider.setValue(1);
+			xScaleSlider.setSnapToValues(new float[] {-1.5f, -1, -0.5f, 0.5f, 1, 1.5f}, 0.12f);
+
+			yScaleSlider.setValue(1);
+			yScaleSlider.setSnapToValues(new float[] {-1.5f, -1, -0.5f, 0.5f, 1, 1.5f}, 0.12f);
 
 			mixSlider.setValue(0.3f);
-			mixSlider.setSnapToValues(new float[] {1, 1.5f, 2, 2.5f, 3, 3.5f}, 0.1f);
+			mixSlider.setSnapToValues(new float[] {1, 1.5f, 2, 2.5f, 3, 3.5f}, 0.12f);
 
 			speedSlider.setValue(1);
-			speedSlider.setSnapToValues(new float[] {0.5f, 0.75f, 1, 1.25f, 1.5f, 2, 2.5f}, 0.01f);
+			speedSlider.setSnapToValues(new float[] {0.5f, 0.75f, 1, 1.25f, 1.5f, 2, 2.5f}, 0.09f);
 
 			alphaSlider.setValue(1);
 			alphaSlider.setDisabled(true);
-			
+
 			addCheckbox.setDisabled(true);
 
 			window.setMovable(false);
@@ -519,12 +533,12 @@ public class SkeletonViewer extends ApplicationAdapter {
 			root.defaults().space(6);
 			root.columnDefaults(0).top().right().padTop(3);
 			root.columnDefaults(1).left();
-			root.add("Scale:");
+			root.add("Load scale:");
 			{
 				Table table = table();
-				table.add(scaleLabel).width(29);
-				table.add(scaleSlider).growX();
-				table.add(scaleResetButton);
+				table.add(loadScaleLabel).width(29);
+				table.add(loadScaleSlider).growX();
+				table.add(loadScaleResetButton);
 				root.add(table).fill().row();
 			}
 			root.add("Zoom:");
@@ -535,8 +549,22 @@ public class SkeletonViewer extends ApplicationAdapter {
 				table.add(zoomResetButton);
 				root.add(table).fill().row();
 			}
-			root.add("Flip:");
-			root.add(table(flipXCheckbox, flipYCheckbox)).row();
+			root.add("Scale X:");
+			{
+				Table table = table();
+				table.add(xScaleLabel).width(29);
+				table.add(xScaleSlider).growX();
+				table.add(xScaleResetButton).row();
+				root.add(table).fill().row();
+			}
+			root.add("Scale Y:");
+			{
+				Table table = table();
+				table.add(yScaleLabel).width(29);
+				table.add(yScaleSlider).growX();
+				table.add(yScaleResetButton);
+				root.add(table).fill().row();
+			}
 			root.add("Debug:");
 			root.add(table(debugBonesCheckbox, debugRegionsCheckbox, debugBoundingBoxesCheckbox)).row();
 			root.add();
@@ -677,25 +705,25 @@ public class SkeletonViewer extends ApplicationAdapter {
 				}
 			});
 
-			scaleSlider.addListener(new ChangeListener() {
+			loadScaleSlider.addListener(new ChangeListener() {
 				public void changed (ChangeEvent event, Actor actor) {
-					scaleLabel.setText(Float.toString((int)(scaleSlider.getValue() * 100) / 100f));
-					if (!scaleSlider.isDragging()) loadSkeleton(skeletonFile);
+					loadScaleLabel.setText(Integer.toString((int)(loadScaleSlider.getValue() * 100)) + "%");
+					if (!loadScaleSlider.isDragging()) loadSkeleton(skeletonFile);
 				}
 			});
-			scaleResetButton.addListener(new ChangeListener() {
+			loadScaleResetButton.addListener(new ChangeListener() {
 				public void changed (ChangeEvent event, Actor actor) {
 					resetCameraPosition();
-					if (scaleSlider.getValue() == 1)
+					if (loadScaleSlider.getValue() == 1)
 						loadSkeleton(skeletonFile);
 					else
-						scaleSlider.setValue(1);
+						loadScaleSlider.setValue(1);
 				}
 			});
 
 			zoomSlider.addListener(new ChangeListener() {
 				public void changed (ChangeEvent event, Actor actor) {
-					zoomLabel.setText(Float.toString((int)(zoomSlider.getValue() * 100) / 100f));
+					zoomLabel.setText(Integer.toString((int)(zoomSlider.getValue() * 100)) + "%");
 					float newZoom = 1 / zoomSlider.getValue();
 					camera.position.x -= window.getWidth() / 2 * (newZoom - camera.zoom);
 					camera.zoom = newZoom;
@@ -710,9 +738,33 @@ public class SkeletonViewer extends ApplicationAdapter {
 				}
 			});
 
+			xScaleSlider.addListener(new ChangeListener() {
+				public void changed (ChangeEvent event, Actor actor) {
+					if (xScaleSlider.getValue() == 0) xScaleSlider.setValue(0.01f);
+					xScaleLabel.setText(Integer.toString((int)(xScaleSlider.getValue() * 100)) + "%");
+				}
+			});
+			xScaleResetButton.addListener(new ChangeListener() {
+				public void changed (ChangeEvent event, Actor actor) {
+					xScaleSlider.setValue(1);
+				}
+			});
+
+			yScaleSlider.addListener(new ChangeListener() {
+				public void changed (ChangeEvent event, Actor actor) {
+					if (yScaleSlider.getValue() == 0) yScaleSlider.setValue(0.01f);
+					yScaleLabel.setText(Integer.toString((int)(yScaleSlider.getValue() * 100)) + "%");
+				}
+			});
+			yScaleResetButton.addListener(new ChangeListener() {
+				public void changed (ChangeEvent event, Actor actor) {
+					yScaleSlider.setValue(1);
+				}
+			});
+
 			speedSlider.addListener(new ChangeListener() {
 				public void changed (ChangeEvent event, Actor actor) {
-					speedLabel.setText(Float.toString((int)(speedSlider.getValue() * 100) / 100f));
+					speedLabel.setText(Float.toString((int)(speedSlider.getValue() * 100) / 100f) + "x");
 				}
 			});
 			speedResetButton.addListener(new ChangeListener() {
@@ -723,7 +775,7 @@ public class SkeletonViewer extends ApplicationAdapter {
 
 			alphaSlider.addListener(new ChangeListener() {
 				public void changed (ChangeEvent event, Actor actor) {
-					alphaLabel.setText(Float.toString((int)(alphaSlider.getValue() * 100) / 100f));
+					alphaLabel.setText(Integer.toString((int)(alphaSlider.getValue() * 100)) + "%");
 					int track = trackButtons.getCheckedIndex();
 					if (track > 0) {
 						TrackEntry current = state.getCurrent(track);
@@ -737,7 +789,7 @@ public class SkeletonViewer extends ApplicationAdapter {
 
 			mixSlider.addListener(new ChangeListener() {
 				public void changed (ChangeEvent event, Actor actor) {
-					mixLabel.setText(Float.toString((int)(mixSlider.getValue() * 100) / 100f));
+					mixLabel.setText(Float.toString((int)(mixSlider.getValue() * 100) / 100f) + "s");
 					if (state != null) state.getData().setDefaultMix(mixSlider.getValue());
 				}
 			});
@@ -878,8 +930,8 @@ public class SkeletonViewer extends ApplicationAdapter {
 			speedSlider.addListener(savePrefsListener);
 			speedResetButton.addListener(savePrefsListener);
 			mixSlider.addListener(savePrefsListener);
-			scaleSlider.addListener(savePrefsListener);
-			scaleResetButton.addListener(savePrefsListener);
+			loadScaleSlider.addListener(savePrefsListener);
+			loadScaleResetButton.addListener(savePrefsListener);
 			zoomSlider.addListener(savePrefsListener);
 			zoomResetButton.addListener(savePrefsListener);
 			animationList.addListener(savePrefsListener);
@@ -942,7 +994,7 @@ public class SkeletonViewer extends ApplicationAdapter {
 			prefs.putBoolean("add", addCheckbox.isChecked());
 			prefs.putFloat("speed", speedSlider.getValue());
 			prefs.putFloat("mix", mixSlider.getValue());
-			prefs.putFloat("scale", scaleSlider.getValue());
+			prefs.putFloat("scale", loadScaleSlider.getValue());
 			prefs.putFloat("zoom", zoomSlider.getValue());
 			prefs.putFloat("x", camera.position.x);
 			prefs.putFloat("y", camera.position.y);
@@ -978,7 +1030,7 @@ public class SkeletonViewer extends ApplicationAdapter {
 				camera.position.x = prefs.getFloat("x", 0);
 				camera.position.y = prefs.getFloat("y", 0);
 
-				scaleSlider.setValue(prefs.getFloat("scale", 1));
+				loadScaleSlider.setValue(prefs.getFloat("scale", 1));
 				animationList.setSelected(prefs.getString("animationName", null));
 				skinList.setSelected(prefs.getString("skinName", null));
 			} catch (Exception ex) {

+ 2 - 0
spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpinePlugin.cpp

@@ -31,6 +31,8 @@
 #include "SpinePluginPrivatePCH.h"
 #include "spine/Extension.h"
 
+DEFINE_LOG_CATEGORY(SpineLog);
+
 class FSpinePlugin : public SpinePlugin {
 	virtual void StartupModule() override;
 	virtual void ShutdownModule() override;

+ 8 - 6
spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineSkeletonAnimationComponent.cpp

@@ -109,12 +109,14 @@ void USpineSkeletonAnimationComponent::CheckState () {
 		
 		if (Atlas && SkeletonData) {
 			spine::SkeletonData *data = SkeletonData->GetSkeletonData(Atlas->GetAtlas(false), false);
-			skeleton = new (__FILE__, __LINE__) Skeleton(data);
-			AnimationStateData* stateData = SkeletonData->GetAnimationStateData(Atlas->GetAtlas(false));
-			state = new (__FILE__, __LINE__) AnimationState(stateData);
-			state->setRendererObject((void*)this);
-			state->setListener(callback);
-			trackEntries.Empty();
+			if (data) {
+				skeleton = new (__FILE__, __LINE__) Skeleton(data);
+				AnimationStateData* stateData = SkeletonData->GetAnimationStateData(Atlas->GetAtlas(false));
+				state = new (__FILE__, __LINE__) AnimationState(stateData);
+				state->setRendererObject((void*)this);
+				state->setListener(callback);
+				trackEntries.Empty();
+			}
 		}
 		
 		lastAtlas = Atlas;

+ 13 - 0
spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineSkeletonDataAsset.cpp

@@ -33,6 +33,7 @@
 #include <string.h>
 #include <string>
 #include <stdlib.h>
+#include "Runtime/Core/Public/Misc/MessageDialog.h"
 
 #define LOCTEXT_NAMESPACE "Spine"
 
@@ -105,10 +106,22 @@ SkeletonData* USpineSkeletonDataAsset::GetSkeletonData (Atlas* Atlas, bool Force
 		if (skeletonDataFileName.GetPlainNameString().Contains(TEXT(".json"))) {
 			SkeletonJson* json = new (__FILE__, __LINE__) SkeletonJson(Atlas);
 			this->skeletonData = json->readSkeletonData((const char*)rawData.GetData());
+			if (!skeletonData) {
+#if WITH_EDITORONLY_DATA
+				FMessageDialog::Debugf(FText::FromString(UTF8_TO_TCHAR(json->getError().buffer())));
+#endif
+				UE_LOG(SpineLog, Error, TEXT("Couldn't load skeleton data and atlas: %s"), UTF8_TO_TCHAR(json->getError().buffer()));
+			}
 			delete json;
 		} else {
 			SkeletonBinary* binary = new (__FILE__, __LINE__) SkeletonBinary(Atlas);
 			this->skeletonData = binary->readSkeletonData((const unsigned char*)rawData.GetData(), (int)rawData.Num());
+			if (!skeletonData) {
+#if WITH_EDITORONLY_DATA
+				FMessageDialog::Debugf(FText::FromString(UTF8_TO_TCHAR(binary->getError().buffer())));
+#endif
+				UE_LOG(SpineLog, Error, TEXT("Couldn't load skeleton data and atlas: %s"), UTF8_TO_TCHAR(binary->getError().buffer()));
+			}
 			delete binary;
 		}
 		if (animationStateData) {

+ 2 - 0
spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpinePlugin.h

@@ -32,6 +32,8 @@
 
 #include "ModuleManager.h"
 
+DECLARE_LOG_CATEGORY_EXTERN(SpineLog, Log, All);
+
 class SPINEPLUGIN_API SpinePlugin : public IModuleInterface {
 
 public:

+ 26 - 15
spine-unity/Assets/Spine/Runtime/spine-unity/Modules/AttachmentTools/AttachmentTools.cs

@@ -211,6 +211,8 @@ namespace Spine.Unity.Modules.AttachmentTools {
 		internal const bool UseMipMaps = false;
 		internal const float DefaultScale = 0.01f;
 
+		const int NonrenderingRegion = -1;
+
 		public static AtlasRegion ToAtlasRegion (this Texture2D t, Material materialPropertySource, float scale = DefaultScale) {
 			return t.ToAtlasRegion(materialPropertySource.shader, scale, materialPropertySource);
 		}
@@ -395,7 +397,7 @@ namespace Spine.Unity.Modules.AttachmentTools {
 		/// <param name = "outputAttachments">The List(Attachment) to populate with the newly created Attachment objects.</param>
 		/// 
 		/// <param name="materialPropertySource">May be null. If no Material property source is provided, no special </param>
-		public static void GetRepackedAttachments (List<Attachment> sourceAttachments, List<Attachment> outputAttachments, Material materialPropertySource, out Material outputMaterial, out Texture2D outputTexture, int maxAtlasSize = 1024, int padding = 2, TextureFormat textureFormat = SpineTextureFormat, bool mipmaps = UseMipMaps, string newAssetName = "Repacked Attachments", bool clearCache = false) {
+		public static void GetRepackedAttachments (List<Attachment> sourceAttachments, List<Attachment> outputAttachments, Material materialPropertySource, out Material outputMaterial, out Texture2D outputTexture, int maxAtlasSize = 1024, int padding = 2, TextureFormat textureFormat = SpineTextureFormat, bool mipmaps = UseMipMaps, string newAssetName = "Repacked Attachments", bool clearCache = false, bool useOriginalNonrenderables = true) {
 			if (sourceAttachments == null) throw new System.ArgumentNullException("sourceAttachments");
 			if (outputAttachments == null) throw new System.ArgumentNullException("outputAttachments");
 
@@ -411,9 +413,9 @@ namespace Spine.Unity.Modules.AttachmentTools {
 			int newRegionIndex = 0;
 			for (int i = 0, n = sourceAttachments.Count; i < n; i++) {
 				var originalAttachment = sourceAttachments[i];
-				var newAttachment = originalAttachment.GetClone(true);
-				if (IsRenderable(newAttachment)) {
-
+				
+				if (IsRenderable(originalAttachment)) {
+					var newAttachment = originalAttachment.GetClone(true);
 					var region = newAttachment.GetRegion();
 					int existingIndex;
 					if (existingRegions.TryGetValue(region, out existingIndex)) {
@@ -427,6 +429,9 @@ namespace Spine.Unity.Modules.AttachmentTools {
 					}
 
 					outputAttachments[i] = newAttachment;
+				} else {
+					outputAttachments[i] = useOriginalNonrenderables ? originalAttachment : originalAttachment.GetClone(true);
+					regionIndexes.Add(NonrenderingRegion); // Output attachments pairs with regionIndexes list 1:1. Pad with a sentinel if the attachment doesn't have a region.
 				}
 			}
 
@@ -460,7 +465,8 @@ namespace Spine.Unity.Modules.AttachmentTools {
 			// Map the cloned attachments to the repacked atlas.
 			for (int i = 0, n = outputAttachments.Count; i < n; i++) {
 				var a = outputAttachments[i];
-				a.SetRegion(repackedRegions[regionIndexes[i]]);
+				if (IsRenderable(a))
+					a.SetRegion(repackedRegions[regionIndexes[i]]);
 			}
 
 			// Clean up.
@@ -474,14 +480,14 @@ namespace Spine.Unity.Modules.AttachmentTools {
 		/// <summary>
 		/// Creates and populates a duplicate skin with cloned attachments that are backed by a new packed texture atlas comprised of all the regions from the original skin.</summary>
 		/// <remarks>No Spine.Atlas object is created so there is no way to find AtlasRegions except through the Attachments using them.</remarks>
-		public static Skin GetRepackedSkin (this Skin o, string newName, Material materialPropertySource, out Material outputMaterial, out Texture2D outputTexture, int maxAtlasSize = 1024, int padding = 2, TextureFormat textureFormat = SpineTextureFormat, bool mipmaps = UseMipMaps) {
-			return GetRepackedSkin(o, newName, materialPropertySource.shader, out outputMaterial, out outputTexture, maxAtlasSize, padding, textureFormat, mipmaps, materialPropertySource);
+		public static Skin GetRepackedSkin (this Skin o, string newName, Material materialPropertySource, out Material outputMaterial, out Texture2D outputTexture, int maxAtlasSize = 1024, int padding = 2, TextureFormat textureFormat = SpineTextureFormat, bool mipmaps = UseMipMaps, bool useOriginalNonrenderables = true) {
+			return GetRepackedSkin(o, newName, materialPropertySource.shader, out outputMaterial, out outputTexture, maxAtlasSize, padding, textureFormat, mipmaps, materialPropertySource, useOriginalNonrenderables);
 		}
 
 		/// <summary>
 		/// Creates and populates a duplicate skin with cloned attachments that are backed by a new packed texture atlas comprised of all the regions from the original skin.</summary>
 		/// <remarks>No Spine.Atlas object is created so there is no way to find AtlasRegions except through the Attachments using them.</remarks>
-		public static Skin GetRepackedSkin (this Skin o, string newName, Shader shader, out Material outputMaterial, out Texture2D outputTexture, int maxAtlasSize = 1024, int padding = 2, TextureFormat textureFormat = SpineTextureFormat, bool mipmaps = UseMipMaps, Material materialPropertySource = null, bool clearCache = false) {
+		public static Skin GetRepackedSkin (this Skin o, string newName, Shader shader, out Material outputMaterial, out Texture2D outputTexture, int maxAtlasSize = 1024, int padding = 2, TextureFormat textureFormat = SpineTextureFormat, bool mipmaps = UseMipMaps, Material materialPropertySource = null, bool clearCache = false, bool useOriginalNonrenderables = true) {
 			if (o == null) throw new System.NullReferenceException("Skin was null");
 			var skinAttachments = o.Attachments;
 			var newSkin = new Skin(newName);
@@ -495,10 +501,13 @@ namespace Spine.Unity.Modules.AttachmentTools {
 			var texturesToPack = new List<Texture2D>();
 			var originalRegions = new List<AtlasRegion>();
 			int newRegionIndex = 0;
-			foreach (var kvp in skinAttachments) {
-				var newAttachment = kvp.Value.GetClone(true);
-				if (IsRenderable(newAttachment)) {
+			foreach (var skinEntry in skinAttachments) {
+				var originalKey = skinEntry.Key;
+				var originalAttachment = skinEntry.Value;
 
+				Attachment newAttachment;
+				if (IsRenderable(originalAttachment)) {
+					newAttachment = originalAttachment.GetClone(true);
 					var region = newAttachment.GetRegion();
 					int existingIndex;
 					if (existingRegions.TryGetValue(region, out existingIndex)) {
@@ -512,9 +521,10 @@ namespace Spine.Unity.Modules.AttachmentTools {
 					}
 
 					repackedAttachments.Add(newAttachment);
-				}
-				var key = kvp.Key;
-				newSkin.AddAttachment(key.slotIndex, key.name, newAttachment);
+					newSkin.AddAttachment(originalKey.slotIndex, originalKey.name, newAttachment);
+				} else {
+					newSkin.AddAttachment(originalKey.slotIndex, originalKey.name, useOriginalNonrenderables ? originalAttachment : originalAttachment.GetClone(true));
+				}	
 			}
 
 			// Fill a new texture with the collected attachment textures.
@@ -546,7 +556,8 @@ namespace Spine.Unity.Modules.AttachmentTools {
 			// Map the cloned attachments to the repacked atlas.
 			for (int i = 0, n = repackedAttachments.Count; i < n; i++) {
 				var a = repackedAttachments[i];
-				a.SetRegion(repackedRegions[regionIndexes[i]]);
+				if (IsRenderable(a))
+					a.SetRegion(repackedRegions[regionIndexes[i]]);
 			}
 
 			// Clean up.