Browse Source

Merge remote-tracking branch 'origin/3.6-beta' into 3.6-beta

Nathan Sweet 8 years ago
parent
commit
5287642645

+ 9 - 7
spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/ClippingTest.java

@@ -89,12 +89,14 @@ public class ClippingTest extends ApplicationAdapter {
 		// Create a clipping attachment, slot data, and slot.
 		ClippingAttachment clip = new ClippingAttachment("clip");
 		// Rectangle:
-		clip.setVertices(new float[] { //
-			-140, 50, //
-			250, 50, //
-			250, 350, //
-			-140, 350, //
-		});
+		clip.setVertices(
+			new float[] { 87, 288, 217, 371, 456, 361, 539, 175, 304, 194, 392, 290, 193, 214, 123, 15, 14, 137 });	
+//		new float[] { //
+//			-140, 50, //
+//			250, 50, //
+//			250, 350, //
+//			-140, 350, //
+//		});
 		// Self intersection:
 //		clip.setVertices(new float[] { //
 //			-140, -50, //
@@ -115,7 +117,7 @@ public class ClippingTest extends ApplicationAdapter {
 	}
 
 	public void render () {
-		// state.update(Gdx.graphics.getDeltaTime() * 0.3f);
+		 state.update(Gdx.graphics.getDeltaTime() * 0.3f);
 		state.update(0);
 
 		Gdx.gl.glClearColor(0.3f, 0.3f, 0.3f, 1);

+ 57 - 46
spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/ConvexDecomposerTest.java

@@ -24,7 +24,7 @@ import com.badlogic.gdx.math.Vector3;
 import com.badlogic.gdx.utils.Array;
 import com.badlogic.gdx.utils.FloatArray;
 import com.esotericsoftware.spine.utils.ConvexDecomposer;
-import com.esotericsoftware.spine.utils.SutherlandHodgmanClipper;
+import com.esotericsoftware.spine.utils.Clipper;
 
 public class ConvexDecomposerTest extends ApplicationAdapter {
 	OrthographicCamera sceneCamera;
@@ -46,9 +46,11 @@ public class ConvexDecomposerTest extends ApplicationAdapter {
 		polyBatcher = new PolygonSpriteBatch();
 		image = new Texture("skin/skin.png");
 		font = new BitmapFont();
-		
-		// float[] v = { 87, 288, 217, 371, 456, 361, 539, 175, 304, 194, 392, 290, 193, 214, 123, 15, 14, 137 };
-		float[] v = { 336, 153, 207, 184, 364, 333, 529, 326, 584, 130, 438, 224 };
+
+		float[] v = {100, 100, 120, 100, 200, 100, 200, 400, 100, 400};
+
+// float[] v = {87, 288, 217, 371, 456, 361, 539, 175, 304, 194, 392, 290, 193, 214, 123, 15, 14, 137};
+// float[] v = { 336, 153, 207, 184, 364, 333, 529, 326, 584, 130, 438, 224 };
 		polygon.addAll(v);
 		triangulate();
 	}
@@ -92,7 +94,7 @@ public class ConvexDecomposerTest extends ApplicationAdapter {
 				triangulate();
 			}
 		}
-		
+
 		if (Gdx.input.isKeyJustPressed(Keys.R)) {
 			long start = System.nanoTime();
 			generateRandomPolygon();
@@ -103,43 +105,52 @@ public class ConvexDecomposerTest extends ApplicationAdapter {
 				if (i != polygon.size - 1) System.out.print(", ");
 			}
 			System.out.println("};");
-//			triangulate();
+			triangulate();
+		}
+
+		if (Gdx.input.isKeyJustPressed(Keys.T)) {
+			triangulate();
 		}
 	}
 
 	private void generateRandomPolygon () {
 		polygon.clear();
-		
-		int numVertices = 10; // MathUtils.random(3, 3);
+		convexPolygons.clear();
+
+		int numVertices = MathUtils.random(3, 30);
 		for (int i = 0; i < numVertices; i++) {
 			float x = (float)(50 + Math.random() * (Gdx.graphics.getWidth() - 50));
 			float y = (float)(50 + Math.random() * (Gdx.graphics.getHeight() - 50));
 
 			polygon.add(x);
 			polygon.add(y);
-			
+			System.out.println(polygon.toString(","));
 			if (selfIntersects(polygon)) {
 				polygon.size -= 2;
 				i--;
 			}
 		}
 	}
-	
-	private boolean selfIntersects(FloatArray polygon) {
+
+	private boolean selfIntersects (FloatArray polygon) {
 		Vector2 tmp = new Vector2();
-		for(int i = 0, n = polygon.size; i <= n; i+=4) {
+		if (polygon.size == 6) return false;
+		for (int i = 0, n = polygon.size; i <= n; i += 2) {
 			float x1 = polygon.get(i % n);
 			float y1 = polygon.get((i + 1) % n);
 			float x2 = polygon.get((i + 2) % n);
 			float y2 = polygon.get((i + 3) % n);
-			
-			for (int j = 0; j <= n; j+=4) {
-				if (j == i || j == i + 1) continue;
+
+			for (int j = 0; j <= n; j += 2) {
 				float x3 = polygon.get(j % n);
 				float y3 = polygon.get((j + 1) % n);
 				float x4 = polygon.get((j + 2) % n);
 				float y4 = polygon.get((j + 3) % n);
-				if (Intersector.intersectSegments(x1, y1, x2, y2, x3, y3, x4, y4, tmp)) return true;		
+				if (x1 == x3 && y1 == y3) continue;
+				if (x1 == x4 && y1 == y4) continue;
+				if (x2 == x3 && y2 == y3) continue;
+				if (x2 == x4 && y2 == y4) continue;
+				if (Intersector.intersectSegments(x1, y1, x2, y2, x3, y3, x4, y4, tmp)) return true;
 			}
 		}
 		return false;
@@ -154,7 +165,7 @@ public class ConvexDecomposerTest extends ApplicationAdapter {
 		polyBatcher.disableBlending();
 
 		polyBatcher.end();
-		
+
 		// polygon
 		shapes.setColor(Color.RED);
 		shapes.begin(ShapeType.Line);
@@ -185,32 +196,32 @@ public class ConvexDecomposerTest extends ApplicationAdapter {
 		}
 
 		// edge normals
-		shapes.setColor(Color.YELLOW);
-		if (polygon.size > 2) {
-			boolean clockwise = SutherlandHodgmanClipper.isClockwise(polygon);
-			for (int i = 0; i < polygon.size; i += 2) {
-				float x = polygon.get(i);
-				float y = polygon.get(i + 1);
-				float x2 = polygon.get((i + 2) % polygon.size);
-				float y2 = polygon.get((i + 3) % polygon.size);
-
-				float mx = x + (x2 - x) / 2;
-				float my = y + (y2 - y) / 2;
-				float nx = (y2 - y);
-				float ny = -(x2 - x);
-				if (!clockwise) {
-					nx = -nx;
-					ny = -ny;
-				}
-				float l = 1 / (float)Math.sqrt(nx * nx + ny * ny);
-				nx *= l * 20;
-				ny *= l * 20;
+// shapes.setColor(Color.YELLOW);
+// if (polygon.size > 2) {
+// boolean clockwise = Clipper.isClockwise(polygon);
+// for (int i = 0; i < polygon.size; i += 2) {
+// float x = polygon.get(i);
+// float y = polygon.get(i + 1);
+// float x2 = polygon.get((i + 2) % polygon.size);
+// float y2 = polygon.get((i + 3) % polygon.size);
+//
+// float mx = x + (x2 - x) / 2;
+// float my = y + (y2 - y) / 2;
+// float nx = (y2 - y);
+// float ny = -(x2 - x);
+// if (!clockwise) {
+// nx = -nx;
+// ny = -ny;
+// }
+// float l = 1 / (float)Math.sqrt(nx * nx + ny * ny);
+// nx *= l * 20;
+// ny *= l * 20;
+//
+// shapes.line(mx, my, mx + nx, my + ny);
+// }
+// }
 
-//				shapes.line(mx, my, mx + nx, my + ny);
-			}
-		}
-		
-		// decomposition		
+		// decomposition
 		if (convexPolygons != null) {
 			for (int i = 0, n = convexPolygons.size; i < n; i++) {
 				if (colors.size <= i) {
@@ -223,12 +234,12 @@ public class ConvexDecomposerTest extends ApplicationAdapter {
 
 		if (isCreatingPolygon) {
 			polygon.setSize(polygon.size - 2);
-		}	
+		}
 		shapes.end();
-		
+
 		polyBatcher.begin();
 		polyBatcher.enableBlending();
-		for (int i = 0; i < polygon.size; i+=2) {
+		for (int i = 0; i < polygon.size; i += 2) {
 			float x = polygon.get(i);
 			float y = polygon.get(i + 1);
 			font.draw(polyBatcher, "" + (i >> 1), x, y); // + ", " + x + ", " + y, x, y);
@@ -238,7 +249,7 @@ public class ConvexDecomposerTest extends ApplicationAdapter {
 	}
 
 	private void triangulate () {
-		SutherlandHodgmanClipper.makeClockwise(polygon);
+		Clipper.makeClockwise(polygon);
 		convexPolygons = decomposer.decompose(polygon);
 	}
 

+ 113 - 103
spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/SoftwareClippingTest.java

@@ -6,6 +6,7 @@ import org.lwjgl.opengl.GL11;
 import com.badlogic.gdx.ApplicationAdapter;
 import com.badlogic.gdx.Gdx;
 import com.badlogic.gdx.Input.Buttons;
+import com.badlogic.gdx.Input.Keys;
 import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
 import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
 import com.badlogic.gdx.graphics.Color;
@@ -16,9 +17,11 @@ import com.badlogic.gdx.graphics.g2d.PolygonSpriteBatch;
 import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
 import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
 import com.badlogic.gdx.math.Vector3;
+import com.badlogic.gdx.utils.Array;
 import com.badlogic.gdx.utils.FloatArray;
 import com.badlogic.gdx.utils.ShortArray;
-import com.esotericsoftware.spine.utils.SutherlandHodgmanClipper;
+import com.esotericsoftware.spine.utils.Clipper;
+import com.esotericsoftware.spine.utils.ConvexDecomposer;
 
 public class SoftwareClippingTest extends ApplicationAdapter {
 	OrthographicCamera sceneCamera;
@@ -26,29 +29,28 @@ public class SoftwareClippingTest extends ApplicationAdapter {
 	PolygonSpriteBatch polyBatcher;
 	Texture image;
 
-	float[] triangleOutline = { 100, 100, 300, 100, 200, 300 };
-	float[] triangle = { 
-		100, 100, Color.WHITE.toFloatBits(), 0, 1, 
-		300, 100, Color.WHITE.toFloatBits(), 1, 1, 
-		200, 300, Color.WHITE.toFloatBits(), 0.5f, 0
-	};
-	short[] triangleIndices = { 0, 1, 2 };	
+	float[] triangleOutline = {100, 100, 300, 100, 200, 300};
+	float[] triangle = {100, 100, Color.WHITE.toFloatBits(), 0, 1, 300, 100, Color.WHITE.toFloatBits(), 1, 1, 200, 300,
+		Color.WHITE.toFloatBits(), 0.5f, 0};
+	short[] triangleIndices = {0, 1, 2};
 	FloatArray clippingPolygon = new FloatArray();
 	FloatArray clippedPolygon = new FloatArray();
-	
+
 	FloatArray clippedPolygonVertices = new FloatArray();
 	ShortArray clippedPolygonIndices = new ShortArray();
 
 	boolean isCreatingClippingArea = false;
 	Vector3 tmp = new Vector3();
-	SutherlandHodgmanClipper clipper;	
+	Clipper clipper;
+	ConvexDecomposer decomposer;
 
 	@Override
 	public void create () {
 		sceneCamera = new OrthographicCamera();
 		shapes = new ShapeRenderer();
 		polyBatcher = new PolygonSpriteBatch();
-		clipper = new SutherlandHodgmanClipper();
+		clipper = new Clipper();
+		decomposer = new ConvexDecomposer();
 		image = new Texture("skin/skin.png");
 	}
 
@@ -74,40 +76,46 @@ public class SoftwareClippingTest extends ApplicationAdapter {
 			if (!isCreatingClippingArea) {
 				clippingPolygon.clear();
 				isCreatingClippingArea = true;
-			} 
-			
-			clippingPolygon.add(tmp.x);
-			clippingPolygon.add(tmp.y);
-			
+			}
+
+			clippingPolygon.add((int)tmp.x);
+			clippingPolygon.add((int)tmp.y);
+
 			if (Gdx.input.isButtonPressed(Buttons.RIGHT)) {
-				isCreatingClippingArea = false;					
-				clip();			
+				isCreatingClippingArea = false;
+				clip();
 			}
 		}
+
+		if (Gdx.input.isKeyJustPressed(Keys.T)) {
+			clip();
+		}
 	}
 
 	private void renderScene () {
 		sceneCamera.update();
 		shapes.setProjectionMatrix(sceneCamera.combined);
 		polyBatcher.setProjectionMatrix(sceneCamera.combined);
-		
+
 		polyBatcher.begin();
 		polyBatcher.disableBlending();
-		
-		if (clippedPolygon.size == 0) {
+
+		// clipped polygon
+		if (clippedPolygonVertices.size == 0) {
 			polyBatcher.draw(image, triangle, 0, 15, triangleIndices, 0, 3);
 		} else {
-			polyBatcher.draw(image, clippedPolygonVertices.items, 0, clippedPolygonVertices.size, clippedPolygonIndices.items, 0, clippedPolygonIndices.size);
+			polyBatcher.draw(image, clippedPolygonVertices.items, 0, clippedPolygonVertices.size, clippedPolygonIndices.items, 0,
+				clippedPolygonIndices.size);
 		}
 		polyBatcher.end();
-		
+
 		shapes.begin(ShapeType.Line);
 
 		// triangle
 		shapes.setColor(Color.GREEN);
 		shapes.polygon(triangleOutline);
 
-		// clipped polygons
+		// clipping area
 		shapes.setColor(Color.RED);
 		if (isCreatingClippingArea) {
 			tmp.set(Gdx.input.getX(), Gdx.input.getY(), 0);
@@ -115,7 +123,7 @@ public class SoftwareClippingTest extends ApplicationAdapter {
 			clippingPolygon.add(tmp.x);
 			clippingPolygon.add(tmp.y);
 		}
-		
+
 		switch (clippingPolygon.size) {
 		case 0:
 			break;
@@ -134,41 +142,41 @@ public class SoftwareClippingTest extends ApplicationAdapter {
 			shapes.polygon(clippingPolygon.items, 0, clippingPolygon.size);
 		}
 
-		// edge normals
-		shapes.setColor(Color.YELLOW);		
-		if (clippingPolygon.size > 2) {
-			boolean clockwise = SutherlandHodgmanClipper.isClockwise(clippingPolygon);
-			for (int i = 0; i < clippingPolygon.size; i += 2) {
-				float x = clippingPolygon.get(i);
-				float y = clippingPolygon.get(i + 1);
-				float x2 = clippingPolygon.get((i + 2) % clippingPolygon.size);
-				float y2 = clippingPolygon.get((i + 3) % clippingPolygon.size);
-
-				float mx = x + (x2 - x) / 2;
-				float my = y + (y2 - y) / 2;
-				float nx = (y2 - y);
-				float ny = -(x2 - x);
-				if (!clockwise) {
-					nx = -nx;
-					ny = -ny;
-				}
-				float l = 1 / (float)Math.sqrt(nx * nx + ny * ny);
-				nx *= l * 20;
-				ny *= l * 20;
+// // edge normals
+// shapes.setColor(Color.YELLOW);
+// if (clippingPolygon.size > 2) {
+// boolean clockwise = Clipper.isClockwise(clippingPolygon);
+// for (int i = 0; i < clippingPolygon.size; i += 2) {
+// float x = clippingPolygon.get(i);
+// float y = clippingPolygon.get(i + 1);
+// float x2 = clippingPolygon.get((i + 2) % clippingPolygon.size);
+// float y2 = clippingPolygon.get((i + 3) % clippingPolygon.size);
+//
+// float mx = x + (x2 - x) / 2;
+// float my = y + (y2 - y) / 2;
+// float nx = (y2 - y);
+// float ny = -(x2 - x);
+// if (!clockwise) {
+// nx = -nx;
+// ny = -ny;
+// }
+// float l = 1 / (float)Math.sqrt(nx * nx + ny * ny);
+// nx *= l * 20;
+// ny *= l * 20;
+//
+// shapes.line(mx, my, mx + nx, my + ny);
+// }
+// }
 
-				shapes.line(mx, my, mx + nx, my + ny);
-			}
-		}
-		
 		if (isCreatingClippingArea) {
 			clippingPolygon.setSize(clippingPolygon.size - 2);
 		}
 
-		// clipped polygon
-		shapes.setColor(Color.PINK);
-		if (clippedPolygon.size > 0) {
-			shapes.polygon(clippedPolygon.items, 0, clippedPolygon.size);
-		}
+// // clipped polygon
+// shapes.setColor(Color.PINK);
+// if (clippedPolygon.size > 0) {
+// shapes.polygon(clippedPolygon.items, 0, clippedPolygon.size);
+// }
 
 		shapes.end();
 	}
@@ -180,56 +188,58 @@ public class SoftwareClippingTest extends ApplicationAdapter {
 		float y2 = triangle[6];
 		float x3 = triangle[10];
 		float y3 = triangle[11];
-		
-		// must duplicate first vertex at end of polygon
-		// so we can avoid module/branch in clipping code
-		SutherlandHodgmanClipper.makeClockwise(clippingPolygon);
-		clippingPolygon.add(clippingPolygon.get(0));
-		clippingPolygon.add(clippingPolygon.get(1));
-		
-		boolean clipped = clipper.clip(x1, y1, x2, y2, x3, y3, clippingPolygon, clippedPolygon);
-		System.out.println("Clipped: " + clipped);
-		if (clipped) {
-			clippedPolygonVertices.clear();
-			clippedPolygonIndices.clear();
-			
-			float d0 = y2 - y3;
-			float d1 = x3 - x2;
-			float d2 = x1 - x3;
-			float d3 = y1 - y3;
-			float d4 = y3 - y1;
-			
-			float denom = 1 / (d0 * d2 + d1 * d3);
-			
-			// triangulate by creating a triangle fan, duplicate vertices
-			float color = Color.WHITE.toFloatBits();
-			for (int i = 0; i < clippedPolygon.size; i+=2) {				
-				float x = clippedPolygon.get(i);
-				float y = clippedPolygon.get(i + 1);
-					
-				float a = (d0 * (x - x3) + d1 * (y - y3)) * denom;
-				float b = (d4 * (x - x3) + d2 * (y - y3)) * denom;
-				float c = 1.0f - a - b;
-				
-				float u = triangle[3] * a + triangle[8] * b + triangle[13] * c;
-				float v = triangle[4] * a + triangle[9] * b + triangle[14] * c;
-				clippedPolygonVertices.add(x);
-				clippedPolygonVertices.add(y);
-				clippedPolygonVertices.add(color);
-				clippedPolygonVertices.add(u);
-				clippedPolygonVertices.add(v);
-			}
-			
-			for (int i = 1; i < (clippedPolygon.size >> 1) - 1; i++) {
-				clippedPolygonIndices.add(0);
-				clippedPolygonIndices.add(i);
-				clippedPolygonIndices.add(i + 1);
+
+		Array<FloatArray> clippingPolygons = decomposer.decompose(clippingPolygon);
+		clippedPolygonVertices.clear();
+		clippedPolygonIndices.clear();
+
+		for (FloatArray poly : clippingPolygons) {
+			Clipper.makeClockwise(poly);
+			poly.add(poly.get(0));
+			poly.add(poly.get(1));
+
+			boolean clipped = clipper.clip(x1, y1, x2, y2, x3, y3, poly, clippedPolygon);
+			System.out.println("Clipped: " + clipped);
+			if (clipped) {
+				float d0 = y2 - y3;
+				float d1 = x3 - x2;
+				float d2 = x1 - x3;
+				float d3 = y1 - y3;
+				float d4 = y3 - y1;
+
+				float denom = 1 / (d0 * d2 + d1 * d3);
+
+				// triangulate by creating a triangle fan, duplicate vertices
+				int o = clippedPolygonVertices.size / 5;
+				float color = Color.WHITE.toFloatBits();
+				for (int i = 0; i < clippedPolygon.size; i += 2) {
+					float x = clippedPolygon.get(i);
+					float y = clippedPolygon.get(i + 1);
+
+					float a = (d0 * (x - x3) + d1 * (y - y3)) * denom;
+					float b = (d4 * (x - x3) + d2 * (y - y3)) * denom;
+					float c = 1.0f - a - b;
+
+					float u = triangle[3] * a + triangle[8] * b + triangle[13] * c;
+					float v = triangle[4] * a + triangle[9] * b + triangle[14] * c;
+					clippedPolygonVertices.add(x);
+					clippedPolygonVertices.add(y);
+					clippedPolygonVertices.add(color);
+					clippedPolygonVertices.add(u);
+					clippedPolygonVertices.add(v);
+				}
+
+				for (int i = 1; i < (clippedPolygon.size >> 1) - 1; i++) {
+					clippedPolygonIndices.add(o);
+					clippedPolygonIndices.add(o + i);
+					clippedPolygonIndices.add(o + i + 1);
+				}
+			} else {
+				clippedPolygon.clear();
 			}
-		} else {
-			clippedPolygon.clear();
+
+			poly.setSize(poly.size - 2);
 		}
-		
-		clippingPolygon.setSize(clippingPolygon.size - 2);
 	}
 
 	public static void main (String[] args) {

+ 4 - 4
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java

@@ -48,7 +48,7 @@ import com.esotericsoftware.spine.attachments.ClippingAttachment;
 import com.esotericsoftware.spine.attachments.MeshAttachment;
 import com.esotericsoftware.spine.attachments.RegionAttachment;
 import com.esotericsoftware.spine.attachments.SkeletonAttachment;
-import com.esotericsoftware.spine.utils.SutherlandHodgmanClipper;
+import com.esotericsoftware.spine.utils.Clipper;
 import com.esotericsoftware.spine.utils.TwoColorPolygonBatch;
 
 public class SkeletonRenderer {
@@ -58,7 +58,7 @@ public class SkeletonRenderer {
 	private boolean premultipliedAlpha;
 	private final FloatArray vertices = new FloatArray(32);
 
-	private SutherlandHodgmanClipper clipper = new SutherlandHodgmanClipper();
+	private Clipper clipper = new Clipper();
 	private ClippingAttachment clipAttachment;
 	private Slot clipEnd;
 	private FloatArray clippingArea = new FloatArray();
@@ -406,9 +406,9 @@ public class SkeletonRenderer {
 			int n = clip.getWorldVerticesLength();
 			float[] vertices = this.clippingArea.setSize(n);
 			clip.computeWorldVertices(slot, 0, n, vertices, 0, 2);
-			clippingAreaClockwise = SutherlandHodgmanClipper.isClockwise(this.clippingArea);
+			clippingAreaClockwise = Clipper.isClockwise(this.clippingArea);
 			if (!clippingAreaClockwise) {
-				SutherlandHodgmanClipper.makeClockwise(clippingArea);
+				Clipper.makeClockwise(clippingArea);
 			}
 			clippingArea.add(clippingArea.items[0]);
 			clippingArea.add(clippingArea.items[1]);

+ 30 - 1
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/SutherlandHodgmanClipper.java → spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/Clipper.java

@@ -1,9 +1,38 @@
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 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 develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) 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.utils;
 
 import com.badlogic.gdx.utils.FloatArray;
 
-public class SutherlandHodgmanClipper {
+public class Clipper {
 	final FloatArray scratch = new FloatArray();
 
 	/** Clips the input triangle against the convex clipping area, which needs to be clockwise. If the triangle lies entirely within the clipping area, false is

+ 153 - 80
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/ConvexDecomposer.java

@@ -1,16 +1,64 @@
+/******************************************************************************
+ * Spine Runtimes Software License v2.5
+ *
+ * Copyright (c) 2013-2016, 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 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 develop new applications using the Spine Runtimes or otherwise
+ * create derivative works or improvements of the Spine Runtimes 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, BUSINESS INTERRUPTION, OR LOSS OF
+ * USE, DATA, OR PROFITS) 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.utils;
 
+import java.util.Iterator;
+
 import com.badlogic.gdx.utils.Array;
 import com.badlogic.gdx.utils.FloatArray;
 import com.badlogic.gdx.utils.IntArray;
+import com.badlogic.gdx.utils.Pool;
 import com.badlogic.gdx.utils.ShortArray;
 
 public class ConvexDecomposer {
 	static private final int CONCAVE = -1;
-	static private final int TANGENTIAL = 0;
 	static private final int CONVEX = 1;
 
+	private Pool<FloatArray> polygonPool = new Pool<FloatArray>() {
+		@Override
+		protected FloatArray newObject () {
+			return new FloatArray(16);
+		}
+	};
+
+	private Pool<ShortArray> polygonIndicesPool = new Pool<ShortArray>() {
+		@Override
+		protected ShortArray newObject () {
+			return new ShortArray(16);
+		}
+	};
+
+	private Array<FloatArray> convexPolygons = new Array<FloatArray>();
+	private Array<ShortArray> convexPolygonsIndices = new Array<ShortArray>();
+
 	private final ShortArray indicesArray = new ShortArray();
 	private short[] indices;
 	private float[] vertices;
@@ -36,17 +84,15 @@ public class ConvexDecomposer {
 		for (int i = 0, n = vertexCount; i < n; ++i)
 			vertexTypes.add(classifyVertex(i));
 
-		// A polygon with n vertices has a triangulation of n-2 triangles.
 		ShortArray triangles = this.triangles;
 		triangles.clear();
 		triangles.ensureCapacity(Math.max(0, vertexCount - 2) * 4);
 
+		// Triangulate
 		while (this.vertexCount > 3) {
 			int earTipIndex = findEarTip();
-			System.out.println("tip index: " + earTipIndex);
 			cutEarTip(earTipIndex);
 
-			// The type of the two vertices adjacent to the clipped vertex may have changed.
 			int previousIndex = previousIndex(earTipIndex);
 			int nextIndex = earTipIndex == vertexCount ? 0 : earTipIndex;
 			vertexTypes.set(previousIndex, classifyVertex(previousIndex));
@@ -59,57 +105,37 @@ public class ConvexDecomposer {
 			triangles.add(indicesArray.get(1));
 		}
 
-		Array<FloatArray> polyResult = new Array<FloatArray>();
-		Array<ShortArray> polyIndicesResult = new Array<ShortArray>();
-
-		ShortArray polyIndices = new ShortArray();
-		FloatArray poly = new FloatArray();
-		int idx1 = triangles.get(0);
-		polyIndices.add(idx1);
-		idx1 <<= 1;
-		int idx2 = triangles.get(1);
-		polyIndices.add(idx2);
-		idx2 <<= 1;
-		int idx3 = triangles.get(2);
-		polyIndices.add(idx3);
-		idx3 <<= 1;
-		System.out.println("Triangle: " + idx1 / 2 + ", " + idx2 / 2 + ", " + idx3 / 2);
-		
-		float x1 = polygon.get(idx1);
-		float y1 = polygon.get(idx1 + 1);
-		float x2 = polygon.get(idx2);
-		float y2 = polygon.get(idx2 + 1);
-		float x3 = polygon.get(idx3);
-		float y3 = polygon.get(idx3 + 1);
-		
-		poly.add(x1);
-		poly.add(y1);
-		poly.add(x2);
-		poly.add(y2);
-		poly.add(x3);
-		poly.add(y3);
-		int lastWinding = winding(x1, y1, x2, y2, x3, y3);
-		int fanBaseIndex = idx1 >> 1;		
-
-		for (int i = 3, n = triangles.size; i < n; i += 3) {
-			idx1 = triangles.get(i);
-			idx2 = triangles.get(i + 1);
-			idx3 = triangles.get(i + 2);
-			System.out.println("Triangle: " + idx1 + ", " + idx2 + ", " + idx3);
-
-			x1 = polygon.get(idx1 * 2);
-			y1 = polygon.get(idx1 * 2 + 1);
-			x2 = polygon.get(idx2 * 2);
-			y2 = polygon.get(idx2 * 2 + 1);
-			x3 = polygon.get(idx3 * 2);
-			y3 = polygon.get(idx3 * 2 + 1);
+		polygonPool.freeAll(convexPolygons);
+		convexPolygons.clear();
+		polygonIndicesPool.freeAll(convexPolygonsIndices);
+		convexPolygonsIndices.clear();
+
+		ShortArray polyIndices = polygonIndicesPool.obtain();
+		polyIndices.clear();
+		FloatArray poly = polygonPool.obtain();
+		poly.clear();
+		int fanBaseIndex = -1;
+		int lastWinding = 0;
+
+		// Merge subsequent triangles if they form a triangle fan
+		for (int i = 0, n = triangles.size; i < n; i += 3) {
+			int idx1 = triangles.get(i) << 1;
+			int idx2 = triangles.get(i + 1) << 1;
+			int idx3 = triangles.get(i + 2) << 1;
+
+			float x1 = polygon.get(idx1);
+			float y1 = polygon.get(idx1 + 1);
+			float x2 = polygon.get(idx2);
+			float y2 = polygon.get(idx2 + 1);
+			float x3 = polygon.get(idx3);
+			float y3 = polygon.get(idx3 + 1);
 
 			// if the base of the last triangle
 			// is the same as this triangle's base
 			// check if they form a convex polygon (triangle fan)
 			boolean merged = false;
-			if (fanBaseIndex == idx1) {				
-				int o = poly.size - 4;				
+			if (fanBaseIndex == idx1) {
+				int o = poly.size - 4;
 				int winding1 = winding(poly.get(o), poly.get(o + 1), poly.get(o + 2), poly.get(o + 3), x3, y3);
 				int winding2 = winding(x3, y3, poly.get(0), poly.get(1), poly.get(2), poly.get(3));
 				if (winding1 == lastWinding && winding2 == lastWinding) {
@@ -123,16 +149,20 @@ public class ConvexDecomposer {
 			// otherwise make this triangle
 			// the new base
 			if (!merged) {
-				polyResult.add(poly);
-				polyIndicesResult.add(polyIndices);
-				poly = new FloatArray();
+				if (poly.size > 0) {
+					convexPolygons.add(poly);
+					convexPolygonsIndices.add(polyIndices);
+				}
+				poly = polygonPool.obtain();
+				poly.clear();
 				poly.add(x1);
 				poly.add(y1);
 				poly.add(x2);
 				poly.add(y2);
 				poly.add(x3);
 				poly.add(y3);
-				polyIndices = new ShortArray();
+				polyIndices = polygonIndicesPool.obtain();
+				polyIndices.clear();
 				polyIndices.add(idx1);
 				polyIndices.add(idx2);
 				polyIndices.add(idx3);
@@ -142,24 +172,75 @@ public class ConvexDecomposer {
 		}
 
 		if (poly.size > 0) {
-			polyResult.add(poly);
-			polyIndicesResult.add(polyIndices);
+			convexPolygons.add(poly);
+			convexPolygonsIndices.add(polyIndices);
 		}
 
-		for (ShortArray pIndices : polyIndicesResult) {
-			System.out.println("Poly: " + pIndices.toString(","));
+		// go through the list of polygons and try
+		// to merge the remaining triangles with
+		// the found triangle fans
+		for (int i = 0, n = convexPolygons.size; i < n; i++) {
+			polyIndices = convexPolygonsIndices.get(i);
+			if (polyIndices.size == 0) continue;
+			int firstIndex = polyIndices.get(0);
+			int lastIndex = polyIndices.get(polyIndices.size - 1);
+
+			poly = convexPolygons.get(i);
+			int o = poly.size - 4;
+			float prevPrevX = poly.get(o);
+			float prevPrevY = poly.get(o + 1);
+			float prevX = poly.get(o + 2);
+			float prevY = poly.get(o + 3);
+			float firstX = poly.get(0);
+			float firstY = poly.get(1);
+			float secondX = poly.get(2);
+			float secondY = poly.get(3);
+			int winding = winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY);
+
+			for (int j = 0; j < n; j++) {
+				if (j == i) continue;
+				ShortArray otherIndices = convexPolygonsIndices.get(j);
+				if (otherIndices.size != 3) continue;
+				int otherFirstIndex = otherIndices.get(0);
+				int otherSecondIndex = otherIndices.get(1);
+				int otherLastIndex = otherIndices.get(2);
+
+				FloatArray otherPoly = convexPolygons.get(j);
+				float x3 = otherPoly.get(otherPoly.size - 2);
+				float y3 = otherPoly.get(otherPoly.size - 1);
+
+				if (otherFirstIndex != firstIndex || otherSecondIndex != lastIndex) continue;
+				int winding1 = winding(prevPrevX, prevPrevY, prevX, prevY, x3, y3);
+				int winding2 = winding(x3, y3, firstX, firstY, secondX, secondY);
+				if (winding1 == winding && winding2 == winding) {
+					otherPoly.clear();
+					otherIndices.clear();
+					poly.add(x3);
+					poly.add(y3);
+					polyIndices.add(otherLastIndex);
+					prevPrevX = prevX;
+					prevPrevY = prevY;
+					prevX = x3;
+					prevY = y3;
+					j = 0;
+				}
+			}
 		}
 
-		return polyResult;
-	}
+		// Remove empty polygons that resulted from the
+		// merge step above
+		Iterator<FloatArray> polyIter = convexPolygons.iterator();
+		while (polyIter.hasNext()) {
+			poly = polyIter.next();
+			if (poly.size == 0) {
+				polyIter.remove();
+				polygonPool.free(poly);
+			}
+		}
 
-	public static int winding (float v1x, float v1y, float v2x, float v2y, float v3x, float v3y) {
-		float vx = v2x - v1x;
-		float vy = v2y - v1y;
-		return v3x * vy - v3y * vx + vx * v1y - v1x * vy >= 0 ? 1 : -1;
+		return convexPolygons;
 	}
 
-	/** @return {@link #CONCAVE}, {@link #TANGENTIAL} or {@link #CONVEX} */
 	private int classifyVertex (int index) {
 		short[] indices = this.indices;
 		int previous = indices[previousIndex(index)] * 2;
@@ -175,17 +256,10 @@ public class ConvexDecomposer {
 		for (int i = 0; i < vertexCount; i++)
 			if (isEarTip(i)) return i;
 
-		// Desperate mode: if no vertex is an ear tip, we are dealing with a degenerate polygon (e.g. nearly collinear).
-		// Note that the input was not necessarily degenerate, but we could have made it so by clipping some valid ears.
-
-		// Idea taken from Martin Held, "FIST: Fast industrial-strength triangulation of polygons", Algorithmica (1998),
-		// http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.115.291
-
-		// Return a convex or tangential vertex if one exists.
 		int[] vertexTypes = this.vertexTypes.items;
 		for (int i = 0; i < vertexCount; i++)
 			if (vertexTypes[i] != CONCAVE) return i;
-		return 0; // If all vertices are concave, just return the first one.
+		return 0;
 	}
 
 	private boolean isEarTip (int earTipIndex) {
@@ -203,18 +277,11 @@ public class ConvexDecomposer {
 		float p2x = vertices[p2], p2y = vertices[p2 + 1];
 		float p3x = vertices[p3], p3y = vertices[p3 + 1];
 
-		// Check if any point is inside the triangle formed by previous, current and next vertices.
-		// Only consider vertices that are not part of this triangle, or else we'll always find one inside.
 		for (int i = nextIndex(nextIndex); i != previousIndex; i = nextIndex(i)) {
-			// Concave vertices can obviously be inside the candidate ear, but so can tangential vertices
-			// if they coincide with one of the triangle's vertices.
 			if (vertexTypes[i] != CONVEX) {
 				int v = indices[i] * 2;
 				float vx = vertices[v];
 				float vy = vertices[v + 1];
-				// Because the polygon has clockwise winding order, the area sign will be positive if the point is strictly inside.
-				// It will be 0 on the edge, which we want to include as well.
-				// note: check the edge defined by p1->p3 first since this fails _far_ more then the other 2 checks.
 				if (computeSpannedAreaSign(p3x, p3y, p1x, p1y, vx, vy) >= 0) {
 					if (computeSpannedAreaSign(p1x, p1y, p2x, p2y, vx, vy) >= 0) {
 						if (computeSpannedAreaSign(p2x, p2y, p3x, p3y, vx, vy) >= 0) return false;
@@ -255,4 +322,10 @@ public class ConvexDecomposer {
 		area += p3x * (p2y - p1y);
 		return (int)Math.signum(area);
 	}
+
+	public static int winding (float v1x, float v1y, float v2x, float v2y, float v3x, float v3y) {
+		float vx = v2x - v1x;
+		float vy = v2y - v1y;
+		return v3x * vy - v3y * vx + vx * v1y - v1x * vy >= 0 ? 1 : -1;
+	}
 }

+ 10 - 1
spine-unity/Assets/spine-unity/Modules/AttachmentTools/AttachmentTools.cs

@@ -305,9 +305,11 @@ namespace Spine.Unity.Modules.AttachmentTools {
 			var skinAttachments = o.Attachments;
 			var newSkin = new Skin(newName);
 
+			// Use these to detect and use shared regions.
 			var existingRegions = new Dictionary<AtlasRegion, int>();
 			var regionIndexes = new List<int>();
 
+			// Collect all textures from the attachments of the original skin.
 			var repackedAttachments = new List<Attachment>();
 			var texturesToPack = new List<Texture2D>();
 			var originalRegions = new List<AtlasRegion>();
@@ -334,11 +336,13 @@ namespace Spine.Unity.Modules.AttachmentTools {
 				newSkin.AddAttachment(key.slotIndex, key.name, newAttachment);
 			}
 
+			// Fill a new texture with the collected attachment textures.
 			var newTexture = new Texture2D(maxAtlasSize, maxAtlasSize, textureFormat, mipmaps);
 			newTexture.anisoLevel = texturesToPack[0].anisoLevel;
 			newTexture.name = newName;
 			var rects = newTexture.PackTextures(texturesToPack.ToArray(), padding, maxAtlasSize);
 
+			// Rehydrate the repacked textures as a Material, Spine atlas and Spine.AtlasAttachments
 			var newMaterial = new Material(shader);
 			newMaterial.name = newName;
 			newMaterial.mainTexture = newTexture;
@@ -352,11 +356,16 @@ namespace Spine.Unity.Modules.AttachmentTools {
 				repackedRegions.Add(newRegion);
 			}
 
+			// 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]]);
 			}
 
+			// Clean up
+			foreach (var ttp in texturesToPack)
+				UnityEngine.Object.Destroy(ttp);
+
 			t = newTexture;
 			m = newMaterial;
 			return newSkin;
@@ -706,7 +715,7 @@ namespace Spine.Unity.Modules.AttachmentTools {
 				ma.hulllength = o.hulllength;
 
 				// Nonessential.
-				ma.Edges = o.Edges.Clone() as int[];
+				ma.Edges = (o.Edges == null) ? null : o.Edges.Clone() as int[]; // Allow absence of Edges array when nonessential data is not exported.
 				ma.Width = o.Width;
 				ma.Height = o.Height;
 			}