Prechádzať zdrojové kódy

Added skinning, compatible with 1.8.19.

NathanSweet 11 rokov pred
rodič
commit
c757da504d

+ 27 - 13
spine-libgdx/src/com/esotericsoftware/spine/Animation.java

@@ -28,7 +28,7 @@
 
 package com.esotericsoftware.spine;
 
-import com.esotericsoftware.spine.attachments.MeshAttachment;
+import com.esotericsoftware.spine.attachments.Attachment;
 
 import com.badlogic.gdx.graphics.Color;
 import com.badlogic.gdx.math.MathUtils;
@@ -427,7 +427,10 @@ public class Animation {
 				float g = frames[i - 2];
 				float b = frames[i - 1];
 				float a = frames[i];
-				color.set(r, g, b, a);
+				if (alpha < 1)
+					color.add((r - color.r) * alpha, (g - color.g) * alpha, (b - color.b) * alpha, (a - color.a) * alpha);
+				else
+					color.set(r, g, b, a);
 				return;
 			}
 
@@ -614,7 +617,7 @@ public class Animation {
 		private final float[] frames; // time, ...
 		private final float[][] frameVertices;
 		int slotIndex;
-		MeshAttachment meshAttachment;
+		Attachment attachment;
 
 		public FfdTimeline (int frameCount) {
 			super(frameCount);
@@ -630,12 +633,12 @@ public class Animation {
 			return slotIndex;
 		}
 
-		public void setMeshAttachment (MeshAttachment attachment) {
-			this.meshAttachment = attachment;
+		public void setAttachment (Attachment attachment) {
+			this.attachment = attachment;
 		}
 
-		public MeshAttachment getMeshAttachment () {
-			return meshAttachment;
+		public Attachment getAttachment () {
+			return attachment;
 		}
 
 		public float[] getFrames () {
@@ -654,7 +657,7 @@ public class Animation {
 
 		public void apply (Skeleton skeleton, float lastTime, float time, Array<Event> firedEvents, float alpha) {
 			Slot slot = skeleton.slots.get(slotIndex);
-			if (slot.getAttachment() != meshAttachment) return;
+			if (slot.getAttachment() != attachment) return;
 
 			FloatArray verticesArray = slot.getAttachmentVertices();
 			verticesArray.size = 0;
@@ -669,7 +672,12 @@ public class Animation {
 			float[] vertices = verticesArray.items;
 
 			if (time >= frames[frames.length - 1]) { // Time is after last frame.
-				System.arraycopy(frameVertices[frames.length - 1], 0, vertices, 0, vertexCount);
+				float[] lastVertices = frameVertices[frames.length - 1];
+				if (alpha < 1) {
+					for (int i = 0; i < vertexCount; i++)
+						vertices[i] += (lastVertices[i] - vertices[i]) * alpha;
+				} else
+					System.arraycopy(lastVertices, 0, vertices, 0, vertexCount);
 				return;
 			}
 
@@ -682,10 +690,16 @@ public class Animation {
 			float[] prevVertices = frameVertices[frameIndex - 1];
 			float[] nextVertices = frameVertices[frameIndex];
 
-			// BOZO - FFD, use alpha for mixing?
-			for (int i = 0; i < vertexCount; i++) {
-				float prev = prevVertices[i];
-				vertices[i] = prev + (nextVertices[i] - prev) * percent;
+			if (alpha < 1) {
+				for (int i = 0; i < vertexCount; i++) {
+					float prev = prevVertices[i];
+					vertices[i] += (prev + (nextVertices[i] - prev) * percent - vertices[i]) * alpha;
+				}
+			} else {
+				for (int i = 0; i < vertexCount; i++) {
+					float prev = prevVertices[i];
+					vertices[i] = prev + (nextVertices[i] - prev) * percent;
+				}
 			}
 		}
 	}

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

@@ -40,7 +40,7 @@ public class BoneData {
 	boolean inheritScale = true, inheritRotation = true;
 
 	// Nonessential.
-	final Color color = new Color(1, 1, 1, 1);
+	final Color color = new Color(0.61f, 0.61f, 0.61f, 1);
 
 	/** @param parent May be null. */
 	public BoneData (String name, BoneData parent) {

+ 75 - 13
spine-libgdx/src/com/esotericsoftware/spine/SkeletonBinary.java

@@ -45,12 +45,15 @@ import com.esotericsoftware.spine.attachments.AttachmentType;
 import com.esotericsoftware.spine.attachments.BoundingBoxAttachment;
 import com.esotericsoftware.spine.attachments.MeshAttachment;
 import com.esotericsoftware.spine.attachments.RegionAttachment;
+import com.esotericsoftware.spine.attachments.SkinnedMeshAttachment;
 
 import com.badlogic.gdx.files.FileHandle;
 import com.badlogic.gdx.graphics.Color;
 import com.badlogic.gdx.graphics.g2d.TextureAtlas;
 import com.badlogic.gdx.utils.Array;
 import com.badlogic.gdx.utils.DataInput;
+import com.badlogic.gdx.utils.FloatArray;
+import com.badlogic.gdx.utils.IntArray;
 import com.badlogic.gdx.utils.SerializationException;
 
 import java.io.IOException;
@@ -218,17 +221,48 @@ public class SkeletonBinary {
 			String path = input.readString();
 			if (path == null) path = name;
 			MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, path);
-			float[] vertices = readFloatArray(input, scale);
+			float[] uvs = readFloatArray(input, 1);
 			short[] triangles = readShortArray(input);
+			float[] vertices = readFloatArray(input, scale);
+			mesh.setMesh(vertices, triangles, uvs);
+			Color.rgba8888ToColor(mesh.getColor(), input.readInt());
+			if (nonessential) {
+				mesh.setEdges(readIntArray(input));
+				mesh.setHullLength(input.readInt(true) * 2);
+				mesh.setWidth(input.readFloat() * scale);
+				mesh.setHeight(input.readFloat() * scale);
+			}
+			return mesh;
+		}
+		case skinnedmesh: {
+			String path = input.readString();
+			if (path == null) path = name;
+			SkinnedMeshAttachment mesh = attachmentLoader.newSkinnedMeshAttachment(skin, name, path);
 			float[] uvs = readFloatArray(input, 1);
+			short[] triangles = readShortArray(input);
+
+			int vertexCount = input.readInt(true);
+			FloatArray weights = new FloatArray(uvs.length * 3 * 3);
+			IntArray bones = new IntArray(uvs.length * 3);
+			for (int i = 0; i < vertexCount; i++) {
+				int boneCount = (int)input.readFloat();
+				bones.add(boneCount);
+				for (int nn = i + boneCount * 4; i < nn; i += 4) {
+					bones.add((int)input.readFloat());
+					weights.add(input.readFloat() * scale);
+					weights.add(input.readFloat() * scale);
+					weights.add(input.readFloat());
+				}
+			}
+			mesh.setMesh(bones.toArray(), weights.toArray(), uvs, triangles);
+
 			Color.rgba8888ToColor(mesh.getColor(), input.readInt());
 			if (nonessential) {
 				mesh.setEdges(readIntArray(input));
-				mesh.setHullLength(input.readInt(true));
+				mesh.setHullLength(input.readInt(true) * 2);
 				mesh.setWidth(input.readFloat() * scale);
 				mesh.setHeight(input.readFloat() * scale);
 			}
-			mesh.setMesh(vertices, triangles, uvs);
 			return mesh;
 		}
 		}
@@ -238,8 +272,13 @@ public class SkeletonBinary {
 	private float[] readFloatArray (DataInput input, float scale) throws IOException {
 		int n = input.readInt(true);
 		float[] array = new float[n];
-		for (int i = 0; i < n; i++)
-			array[i] = input.readFloat() * scale;
+		if (scale == 1) {
+			for (int i = 0; i < n; i++)
+				array[i] = input.readFloat();
+		} else {
+			for (int i = 0; i < n; i++)
+				array[i] = input.readFloat() * scale;
+		}
 		return array;
 	}
 
@@ -344,22 +383,45 @@ public class SkeletonBinary {
 				for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) {
 					int slotIndex = input.readInt(true);
 					for (int iii = 0, nnn = input.readInt(true); iii < nnn; iii++) {
-						MeshAttachment mesh = (MeshAttachment)skin.getAttachment(slotIndex, input.readString());
+						Attachment attachment = skin.getAttachment(slotIndex, input.readString());
 						int frameCount = input.readInt(true);
 						FfdTimeline timeline = new FfdTimeline(frameCount);
 						timeline.slotIndex = slotIndex;
-						timeline.meshAttachment = mesh;
+						timeline.attachment = attachment;
 						for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
 							float time = input.readFloat();
+
 							float[] vertices;
-							int vertexCount = input.readInt(true);
-							if (vertexCount == 0)
-								vertices = mesh.getVertices();
-							else {
+							int vertexCount;
+							if (attachment instanceof MeshAttachment)
+								vertexCount = ((MeshAttachment)attachment).getVertices().length;
+							else
+								vertexCount = ((SkinnedMeshAttachment)attachment).getWeights().length / 3 * 2;
+
+							int end = input.readInt(true);
+							if (end == 0) {
+								if (attachment instanceof MeshAttachment)
+									vertices = ((MeshAttachment)attachment).getVertices();
+								else
+									vertices = new float[vertexCount];
+							} else {
 								vertices = new float[vertexCount];
-								for (int vertex = 0; vertex < vertexCount; vertex++)
-									vertices[vertex] = input.readFloat() * scale;
+								int start = input.readInt(true);
+								end += start;
+								if (scale == 1) {
+									for (int v = start; v < end; v++)
+										vertices[v] = input.readFloat();
+								} else {
+									for (int v = start; v < end; v++)
+										vertices[v] = input.readFloat() * scale;
+								}
+								if (attachment instanceof MeshAttachment) {
+									float[] meshVertices = ((MeshAttachment)attachment).getVertices();
+									for (int v = 0, vn = vertices.length; v < vn; v++)
+										vertices[v] += meshVertices[v];
+								}
 							}
+
 							timeline.setFrame(frameIndex, time, vertices);
 							if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
 						}

+ 61 - 11
spine-libgdx/src/com/esotericsoftware/spine/SkeletonJson.java

@@ -45,11 +45,14 @@ import com.esotericsoftware.spine.attachments.AttachmentType;
 import com.esotericsoftware.spine.attachments.BoundingBoxAttachment;
 import com.esotericsoftware.spine.attachments.MeshAttachment;
 import com.esotericsoftware.spine.attachments.RegionAttachment;
+import com.esotericsoftware.spine.attachments.SkinnedMeshAttachment;
 
 import com.badlogic.gdx.files.FileHandle;
 import com.badlogic.gdx.graphics.Color;
 import com.badlogic.gdx.graphics.g2d.TextureAtlas;
 import com.badlogic.gdx.utils.Array;
+import com.badlogic.gdx.utils.FloatArray;
+import com.badlogic.gdx.utils.IntArray;
 import com.badlogic.gdx.utils.JsonReader;
 import com.badlogic.gdx.utils.JsonValue;
 import com.badlogic.gdx.utils.SerializationException;
@@ -194,15 +197,44 @@ public class SkeletonJson {
 		}
 		case mesh: {
 			MeshAttachment mesh = attachmentLoader.newMeshAttachment(skin, name, map.getString("path", name));
+			float[] uvs = map.require("uvs").asFloatArray();
+			short[] triangles = map.require("triangles").asShortArray();
+
 			float[] vertices = map.require("vertices").asFloatArray();
 			if (scale != 1) {
 				for (int i = 0, n = vertices.length; i < n; i++)
 					vertices[i] *= scale;
 			}
-			short[] triangles = map.require("triangles").asShortArray();
-			float[] uvs = map.require("uvs").asFloatArray();
 			mesh.setMesh(vertices, triangles, uvs);
-			if (map.has("hull")) mesh.setHullLength(map.require("hull").asInt());
+
+			if (map.has("hull")) mesh.setHullLength(map.require("hull").asInt() * 2);
+			if (map.has("edges")) mesh.setEdges(map.require("edges").asIntArray());
+			mesh.setWidth(map.getFloat("width", 0) * scale);
+			mesh.setHeight(map.getFloat("height", 0) * scale);
+			return mesh;
+		}
+		case skinnedmesh: {
+			SkinnedMeshAttachment mesh = attachmentLoader.newSkinnedMeshAttachment(skin, name, map.getString("path", name));
+			float[] uvs = map.require("uvs").asFloatArray();
+			short[] triangles = map.require("triangles").asShortArray();
+
+			float[] vertices = map.require("vertices").asFloatArray();
+			FloatArray weights = new FloatArray(uvs.length * 3 * 3);
+			IntArray bones = new IntArray(uvs.length * 3);
+			for (int i = 0, n = vertices.length; i < n;) {
+				int boneCount = (int)vertices[i++];
+				bones.add(boneCount);
+				for (int nn = i + boneCount * 4; i < nn;) {
+					bones.add((int)vertices[i]);
+					weights.add(vertices[i + 1] * scale);
+					weights.add(vertices[i + 2] * scale);
+					weights.add(vertices[i + 3]);
+					i += 4;
+				}
+			}
+			mesh.setMesh(bones.toArray(), weights.toArray(), uvs, triangles);
+
+			if (map.has("hull")) mesh.setHullLength(map.require("hull").asInt() * 2);
 			if (map.has("edges")) mesh.setEdges(map.require("edges").asIntArray());
 			mesh.setWidth(map.getFloat("width", 0) * scale);
 			mesh.setHeight(map.getFloat("height", 0) * scale);
@@ -222,6 +254,7 @@ public class SkeletonJson {
 	}
 
 	private void readAnimation (String name, JsonValue map, SkeletonData skeletonData) {
+		float scale = this.scale;
 		Array<Timeline> timelines = new Array();
 		float duration = 0;
 
@@ -315,24 +348,41 @@ public class SkeletonJson {
 				if (slotIndex == -1) throw new SerializationException("Slot not found: " + slotMap.name);
 				for (JsonValue meshMap = slotMap.child; meshMap != null; meshMap = meshMap.next) {
 					FfdTimeline timeline = new FfdTimeline(meshMap.size);
-					MeshAttachment mesh = (MeshAttachment)skin.getAttachment(slotIndex, meshMap.name);
-					if (mesh == null) throw new SerializationException("Mesh attachment not found: " + meshMap.name);
+					Attachment attachment = skin.getAttachment(slotIndex, meshMap.name);
+					if (attachment == null) throw new SerializationException("FFD attachment not found: " + meshMap.name);
 					timeline.slotIndex = slotIndex;
-					timeline.meshAttachment = mesh;
+					timeline.attachment = attachment;
 
 					int frameIndex = 0;
 					for (JsonValue valueMap = meshMap.child; valueMap != null; valueMap = valueMap.next) {
 						float[] vertices;
+						int vertexCount;
+						if (attachment instanceof MeshAttachment)
+							vertexCount = ((MeshAttachment)attachment).getVertices().length;
+						else
+							vertexCount = ((SkinnedMeshAttachment)attachment).getWeights().length / 3 * 2;
+
 						JsonValue verticesValue = valueMap.get("vertices");
-						if (verticesValue == null)
-							vertices = mesh.getVertices();
-						else {
-							vertices = verticesValue.asFloatArray();
+						if (verticesValue == null) {
+							if (attachment instanceof MeshAttachment)
+								vertices = ((MeshAttachment)attachment).getVertices();
+							else
+								vertices = new float[vertexCount];
+						} else {
+							vertices = new float[vertexCount];
+							int start = valueMap.getInt("offset", 0);
+							System.arraycopy(verticesValue.asFloatArray(), 0, vertices, start, verticesValue.size);
 							if (scale != 1) {
-								for (int i = 0, n = vertices.length; i < n; i++)
+								for (int i = start, n = i + verticesValue.size; i < n; i++)
 									vertices[i] *= scale;
 							}
+							if (attachment instanceof MeshAttachment) {
+								float[] meshVertices = ((MeshAttachment)attachment).getVertices();
+								for (int i = 0, n = vertices.length; i < n; i++)
+									vertices[i] += meshVertices[i];
+							}
 						}
+
 						timeline.setFrame(frameIndex, valueMap.getFloat("time"), vertices);
 						readCurve(timeline, frameIndex, valueMap);
 						frameIndex++;

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

@@ -32,6 +32,7 @@ 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;
 
 import com.badlogic.gdx.graphics.GL20;
 import com.badlogic.gdx.graphics.Texture;
@@ -83,6 +84,14 @@ public class SkeletonRenderer {
 				texture = mesh.getRegion().getTexture();
 				batch.draw(texture, vertices, 0, vertices.length, triangles, 0, triangles.length);
 
+			} else if (attachment instanceof SkinnedMeshAttachment) {
+				SkinnedMeshAttachment mesh = (SkinnedMeshAttachment)attachment;
+				mesh.updateWorldVertices(slot, true);
+				vertices = mesh.getWorldVertices();
+				triangles = mesh.getTriangles();
+				texture = mesh.getRegion().getTexture();
+				batch.draw(texture, vertices, 0, vertices.length, triangles, 0, triangles.length);
+
 			} else if (attachment instanceof SkeletonAttachment) {
 				Skeleton attachmentSkeleton = ((SkeletonAttachment)attachment).getSkeleton();
 				if (attachmentSkeleton == null) continue;

+ 56 - 2
spine-libgdx/src/com/esotericsoftware/spine/SkeletonRendererDebug.java

@@ -29,7 +29,9 @@
 package com.esotericsoftware.spine;
 
 import com.esotericsoftware.spine.attachments.Attachment;
+import com.esotericsoftware.spine.attachments.MeshAttachment;
 import com.esotericsoftware.spine.attachments.RegionAttachment;
+import com.esotericsoftware.spine.attachments.SkinnedMeshAttachment;
 
 import static com.badlogic.gdx.graphics.g2d.Batch.*;
 
@@ -44,12 +46,14 @@ import com.badlogic.gdx.utils.FloatArray;
 public class SkeletonRendererDebug {
 	static private final Color boneLineColor = Color.RED;
 	static private final Color boneOriginColor = Color.GREEN;
-	static private final Color regionAttachmentLineColor = new Color(0, 0, 1, 0.5f);
+	static private final Color attachmentLineColor = new Color(0, 0, 1, 0.5f);
+	static private final Color triangleLineColor = new Color(1, 0.64f, 0, 0.5f);
 	static private final Color boundingBoxColor = new Color(0, 1, 0, 0.8f);
 	static private final Color aabbColor = new Color(0, 1, 0, 0.5f);
 
 	private final ShapeRenderer renderer;
 	private boolean drawBones = true, drawRegionAttachments = true, drawBoundingBoxes = true;
+	private boolean drawMeshHull = true, drawMeshTriangles = true;
 	private final SkeletonBounds bounds = new SkeletonBounds();
 	private float scale = 1;
 
@@ -78,7 +82,7 @@ public class SkeletonRendererDebug {
 		}
 
 		if (drawRegionAttachments) {
-			renderer.setColor(regionAttachmentLineColor);
+			renderer.setColor(attachmentLineColor);
 			Array<Slot> slots = skeleton.getSlots();
 			for (int i = 0, n = slots.size; i < n; i++) {
 				Slot slot = slots.get(i);
@@ -95,6 +99,48 @@ public class SkeletonRendererDebug {
 			}
 		}
 
+		if (drawMeshHull || drawMeshTriangles) {
+			Array<Slot> slots = skeleton.getSlots();
+			for (int i = 0, n = slots.size; i < n; i++) {
+				Slot slot = slots.get(i);
+				Attachment attachment = slot.attachment;
+				float[] vertices = null;
+				short[] triangles = null;
+				if (attachment instanceof MeshAttachment) {
+					MeshAttachment mesh = (MeshAttachment)attachment;
+					mesh.updateWorldVertices(slot, false);
+					vertices = mesh.getWorldVertices();
+					triangles = mesh.getTriangles();
+				} else if (attachment instanceof SkinnedMeshAttachment) {
+					SkinnedMeshAttachment mesh = (SkinnedMeshAttachment)attachment;
+					mesh.updateWorldVertices(slot, false);
+					vertices = mesh.getWorldVertices();
+					triangles = mesh.getTriangles();
+				}
+				if (vertices == null || triangles == null) continue;
+				if (drawMeshTriangles) {
+					renderer.setColor(triangleLineColor);
+					for (int ii = 0, nn = triangles.length; ii < nn; ii += 3) {
+						int v1 = triangles[ii] * 5, v2 = triangles[ii + 1] * 5, v3 = triangles[ii + 2] * 5;
+						renderer.triangle(vertices[v1], vertices[v1 + 1], //
+							vertices[v2], vertices[v2 + 1], //
+							vertices[v3], vertices[v3 + 1] //
+							);
+					}
+				}
+				if (drawMeshHull) {
+					renderer.setColor(attachmentLineColor);
+					float lastX = vertices[vertices.length - 5], lastY = vertices[vertices.length - 4];
+					for (int ii = 0, nn = vertices.length; ii < nn; ii += 5) {
+						float x = vertices[ii], y = vertices[ii + 1];
+						renderer.line(x, y, lastX, lastY);
+						lastX = x;
+						lastY = y;
+					}
+				}
+			}
+		}
+
 		if (drawBoundingBoxes) {
 			SkeletonBounds bounds = this.bounds;
 			bounds.update(skeleton, true);
@@ -142,4 +188,12 @@ public class SkeletonRendererDebug {
 	public void setBoundingBoxes (boolean boundingBoxes) {
 		this.drawBoundingBoxes = boundingBoxes;
 	}
+
+	public void setMeshHull (boolean meshHull) {
+		this.drawMeshHull = meshHull;
+	}
+
+	public void setMeshTriangles (boolean meshTriangles) {
+		this.drawMeshTriangles = meshTriangles;
+	}
 }

+ 2 - 2
spine-libgdx/src/com/esotericsoftware/spine/Slot.java

@@ -100,7 +100,7 @@ public class Slot {
 		if (this.attachment == attachment) return;
 		this.attachment = attachment;
 		attachmentTime = skeleton.time;
-		attachmentVertices.clear();
+		//attachmentVertices.clear();
 	}
 
 	public void setAttachmentTime (float time) {
@@ -120,7 +120,7 @@ public class Slot {
 		color.set(data.color);
 		setAttachment(data.attachmentName == null ? null : skeleton.getAttachment(slotIndex, data.attachmentName));
 		// BOZO - Set mesh to setup pose.
-		// attachmentVertices.clear();
+		attachmentVertices.clear();
 	}
 
 	public void setToSetupPose () {

+ 11 - 1
spine-libgdx/src/com/esotericsoftware/spine/attachments/AtlasAttachmentLoader.java

@@ -56,7 +56,17 @@ public class AtlasAttachmentLoader implements AttachmentLoader {
 		attachment.setPath(path);
 		AtlasRegion region = atlas.findRegion(path);
 		if (region == null)
-			throw new RuntimeException("Region not found in atlas: " + attachment + " (region attachment: " + name + ")");
+			throw new RuntimeException("Region not found in atlas: " + attachment + " (mesh attachment: " + name + ")");
+		attachment.setRegion(region);
+		return attachment;
+	}
+
+	public SkinnedMeshAttachment newSkinnedMeshAttachment (Skin skin, String name, String path) {
+		SkinnedMeshAttachment attachment = new SkinnedMeshAttachment(name);
+		attachment.setPath(path);
+		AtlasRegion region = atlas.findRegion(path);
+		if (region == null)
+			throw new RuntimeException("Region not found in atlas: " + attachment + " (skinned mesh attachment: " + name + ")");
 		attachment.setRegion(region);
 		return attachment;
 	}

+ 3 - 0
spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentLoader.java

@@ -36,6 +36,9 @@ public interface AttachmentLoader {
 
 	/** @return May be null to not load any attachment. */
 	public MeshAttachment newMeshAttachment (Skin skin, String name, String path);
+	
+	/** @return May be null to not load any attachment. */
+	public SkinnedMeshAttachment newSkinnedMeshAttachment (Skin skin, String name, String path);
 
 	/** @return May be null to not load any attachment. */
 	public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name);

+ 1 - 1
spine-libgdx/src/com/esotericsoftware/spine/attachments/AttachmentType.java

@@ -29,5 +29,5 @@
 package com.esotericsoftware.spine.attachments;
 
 public enum AttachmentType {
-	region, boundingbox, mesh
+	region, boundingbox, mesh, skinnedmesh
 }

+ 211 - 0
spine-libgdx/src/com/esotericsoftware/spine/attachments/SkinnedMeshAttachment.java

@@ -0,0 +1,211 @@
+/******************************************************************************
+ * Spine Runtimes Software License
+ * Version 2
+ * 
+ * Copyright (c) 2013, Esoteric Software
+ * All rights reserved.
+ * 
+ * You are granted a perpetual, non-exclusive, non-sublicensable and
+ * non-transferable license to install, execute and perform the Spine Runtimes
+ * Software (the "Software") solely for internal use. Without the written
+ * permission of Esoteric Software, 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 SOFTARE 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.attachments;
+
+import com.esotericsoftware.spine.Bone;
+import com.esotericsoftware.spine.Skeleton;
+import com.esotericsoftware.spine.Slot;
+
+import com.badlogic.gdx.graphics.Color;
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+import com.badlogic.gdx.utils.FloatArray;
+import com.badlogic.gdx.utils.NumberUtils;
+
+/** Attachment that displays a texture region. */
+public class SkinnedMeshAttachment extends Attachment {
+	private TextureRegion region;
+	private String path;
+	private int[] bones;
+	private float[] weights;
+	private short[] triangles;
+	private float[] worldVertices;
+	private final Color color = new Color(1, 1, 1, 1);
+
+	// Nonessential.
+	private int[] edges;
+	private float width, height;
+	private int hullLength;
+
+	public SkinnedMeshAttachment (String name) {
+		super(name);
+	}
+
+	public void setRegion (TextureRegion region) {
+		if (region == null) throw new IllegalArgumentException("region cannot be null.");
+		this.region = region;
+	}
+
+	public TextureRegion getRegion () {
+		if (region == null) throw new IllegalStateException("Region has not been set: " + this);
+		return region;
+	}
+
+	public void updateWorldVertices (Slot slot, boolean premultipliedAlpha) {
+		Skeleton skeleton = slot.getSkeleton();
+		Color skeletonColor = skeleton.getColor();
+		Color meshColor = slot.getColor();
+		Color regionColor = color;
+		float a = skeletonColor.a * meshColor.a * regionColor.a * 255;
+		float multiplier = premultipliedAlpha ? a : 255;
+		float color = NumberUtils.intToFloatColor( //
+			((int)a << 24) //
+				| ((int)(skeletonColor.b * meshColor.b * regionColor.b * multiplier) << 16) //
+				| ((int)(skeletonColor.g * meshColor.g * regionColor.g * multiplier) << 8) //
+				| (int)(skeletonColor.r * meshColor.r * regionColor.r * multiplier));
+
+		float[] worldVertices = this.worldVertices;
+		float x = skeleton.getX(), y = skeleton.getY();
+		Object[] skeletonBones = skeleton.getBones().items;
+		float[] weights = this.weights;
+		int[] bones = this.bones;
+
+		FloatArray ffdArray = slot.getAttachmentVertices();
+		if (ffdArray.size == 0) {
+			for (int w = 0, v = 0, b = 0, n = bones.length; v < n; w += 5) {
+				float wx = 0, wy = 0;
+				int nn = bones[v++] + v;
+				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;
+				}
+				worldVertices[w] = wx + x;
+				worldVertices[w + 1] = wy + y;
+				worldVertices[w + 2] = color;
+			}
+		} else {
+			float[] ffd = ffdArray.items;
+			for (int w = 0, v = 0, b = 0, f = 0, n = bones.length; v < n; w += 5) {
+				float wx = 0, wy = 0;
+				int nn = bones[v++] + v;
+				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;
+				}
+				worldVertices[w] = wx + x;
+				worldVertices[w + 1] = wy + y;
+				worldVertices[w + 2] = color;
+			}
+		}
+	}
+
+	public float[] getWorldVertices () {
+		return worldVertices;
+	}
+
+	public short[] getTriangles () {
+		return triangles;
+	}
+
+	public int[] getBones () {
+		return bones;
+	}
+
+	public float[] getWeights () {
+		return weights;
+	}
+
+	public Color getColor () {
+		return color;
+	}
+
+	public String getPath () {
+		return path;
+	}
+
+	public void setPath (String path) {
+		this.path = path;
+	}
+
+	public int getHullLength () {
+		return hullLength;
+	}
+
+	public void setHullLength (int hullLength) {
+		this.hullLength = hullLength;
+	}
+
+	public void setEdges (int[] edges) {
+		this.edges = edges;
+	}
+
+	public int[] getEdges () {
+		return edges;
+	}
+
+	public float getWidth () {
+		return width;
+	}
+
+	public void setWidth (float width) {
+		this.width = width;
+	}
+
+	public float getHeight () {
+		return height;
+	}
+
+	public void setHeight (float height) {
+		this.height = height;
+	}
+
+	/** @param bones For each vertex, the number of bones affecting the vertex followed by that many bone indices. Ie: count,
+	 *           boneIndex, ...
+	 * @param weights For each bone affecting the vertex, the vertex position in the bone's coordinate system and the weight for
+	 *           the bone's influence. Ie: x, y, weight, ...
+	 * @param uvs For each vertex, a texure coordinate pair. Ie: u, v, ...
+	 * @param triangles Vertex number triplets which describe the mesh's triangulation. */
+	public void setMesh (int[] bones, float[] weights, float[] uvs, short[] triangles) {
+		this.bones = bones;
+		this.weights = weights;
+		this.triangles = triangles;
+
+		int uvsLength = uvs.length;
+		int worldVerticesLength = uvsLength / 2 * 5;
+		if (worldVertices == null || worldVertices.length != worldVerticesLength) worldVertices = new float[worldVerticesLength];
+
+		float u, v, w, h;
+		if (region == null) {
+			u = v = 0;
+			w = h = 1;
+		} else {
+			u = region.getU();
+			v = region.getV();
+			w = region.getU2() - u;
+			h = region.getV2() - v;
+		}
+		for (int i = 0, ii = 3; i < uvsLength; i += 2, ii += 5) {
+			worldVertices[ii] = u + uvs[i] * w;
+			worldVertices[ii + 1] = v + uvs[i + 1] * h;
+		}
+	}
+}

+ 1 - 1
spine-libgdx/test/com/esotericsoftware/spine/NormalMapTest.java

@@ -168,7 +168,7 @@ public class NormalMapTest extends ApplicationAdapter {
 
 	public void resize (int width, int height) {
 		batch.getProjectionMatrix().setToOrtho2D(0, 0, width, height);
-		ui.stage.setViewport(width, height);
+		ui.stage.getViewport().update(width, height, true);
 		resolution.set(width, height);
 	}
 

+ 9 - 2
spine-libgdx/test/com/esotericsoftware/spine/SkeletonTest.java

@@ -63,6 +63,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Window;
 import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
 import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
 import com.badlogic.gdx.utils.Array;
+import com.badlogic.gdx.utils.viewport.ScreenViewport;
 
 import java.awt.FileDialog;
 import java.awt.Frame;
@@ -195,6 +196,8 @@ public class SkeletonTest extends ApplicationAdapter {
 			debugRenderer.setBones(ui.debugBonesCheckbox.isChecked());
 			debugRenderer.setRegionAttachments(ui.debugRegionsCheckbox.isChecked());
 			debugRenderer.setBoundingBoxes(ui.debugBoundingBoxesCheckbox.isChecked());
+			debugRenderer.setMeshHull(ui.debugMeshHullCheckbox.isChecked());
+			debugRenderer.setMeshTriangles(ui.debugMeshTrianglesCheckbox.isChecked());
 			debugRenderer.draw(skeleton);
 		}
 
@@ -205,12 +208,12 @@ public class SkeletonTest extends ApplicationAdapter {
 	public void resize (int width, int height) {
 		batch.getProjectionMatrix().setToOrtho2D(0, 0, width, height);
 		debugRenderer.getShapeRenderer().setProjectionMatrix(batch.getProjectionMatrix());
-		ui.stage.setViewport(width, height);
+		ui.stage.getViewport().update(width, height, true);
 		if (!ui.minimizeButton.isChecked()) ui.window.setHeight(height);
 	}
 
 	class UI {
-		Stage stage = new Stage();
+		Stage stage = new Stage(new ScreenViewport());
 		com.badlogic.gdx.scenes.scene2d.ui.Skin skin = new com.badlogic.gdx.scenes.scene2d.ui.Skin(
 			Gdx.files.internal("skin/skin.json"));
 
@@ -231,6 +234,8 @@ public class SkeletonTest extends ApplicationAdapter {
 		CheckBox debugBonesCheckbox = new CheckBox(" Bones", skin);
 		CheckBox debugRegionsCheckbox = new CheckBox(" Regions", skin);
 		CheckBox debugBoundingBoxesCheckbox = new CheckBox(" Bounds", skin);
+		CheckBox debugMeshHullCheckbox = new CheckBox(" Mesh Hull", skin);
+		CheckBox debugMeshTrianglesCheckbox = new CheckBox(" Mesh Triangles", skin);
 		Slider scaleSlider = new Slider(0.1f, 3, 0.01f, false, skin);
 		Label scaleLabel = new Label("1.0", skin);
 		TextButton pauseButton = new TextButton("Pause", skin, "toggle");
@@ -286,6 +291,8 @@ public class SkeletonTest extends ApplicationAdapter {
 			root.add(table(flipXCheckbox, flipYCheckbox)).row();
 			root.add("Debug:");
 			root.add(table(debugBonesCheckbox, debugRegionsCheckbox, debugBoundingBoxesCheckbox)).row();
+			root.add();
+			root.add(table(debugMeshHullCheckbox, debugMeshTrianglesCheckbox)).row();
 			root.add("Alpha:");
 			root.add(premultipliedCheckbox).row();
 			root.add("Skin:");