Przeglądaj źródła

[csharp] Added clipping attachment, loading and subs for clipping code

badlogic 8 lat temu
rodzic
commit
63c3a48014

+ 1 - 0
spine-csharp/spine-csharp.csproj

@@ -62,6 +62,7 @@
     <Compile Include="src\Attachments\AttachmentLoader.cs" />
     <Compile Include="src\Attachments\AttachmentType.cs" />
     <Compile Include="src\Attachments\BoundingBoxAttachment.cs" />
+    <Compile Include="src\Attachments\ClippingAttachment.cs" />
     <Compile Include="src\Attachments\MeshAttachment.cs" />
     <Compile Include="src\Attachments\PathAttachment.cs" />
     <Compile Include="src\Attachments\PointAttachment.cs" />

+ 4 - 0
spine-csharp/src/Attachments/AtlasAttachmentLoader.cs

@@ -90,6 +90,10 @@ namespace Spine {
 			return new PointAttachment(name);
 		}
 
+		public ClippingAttachment NewClippingAttachment(Skin skin, string name) {
+			return new ClippingAttachment(name);
+		}
+
 		public AtlasRegion FindRegion (string name) {
 			AtlasRegion region;
 

+ 2 - 0
spine-csharp/src/Attachments/AttachmentLoader.cs

@@ -43,5 +43,7 @@ namespace Spine {
 		PathAttachment NewPathAttachment (Skin skin, string name);
 
 		PointAttachment NewPointAttachment (Skin skin, string name);
+
+		ClippingAttachment NewClippingAttachment (Skin skin, string name);
 	}
 }

+ 1 - 1
spine-csharp/src/Attachments/AttachmentType.cs

@@ -30,6 +30,6 @@
 
 namespace Spine {
 	public enum AttachmentType {
-		Region, Boundingbox, Mesh, Linkedmesh, Path, Point
+		Region, Boundingbox, Mesh, Linkedmesh, Path, Point, Clipping
 	}
 }

+ 42 - 0
spine-csharp/src/Attachments/ClippingAttachment.cs

@@ -0,0 +1,42 @@
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+    public class ClippingAttachment : VertexAttachment {
+        internal SlotData endSlot;
+
+        public SlotData EndSlot { get { return endSlot; } set { endSlot = value; } }
+
+        public ClippingAttachment(string name) : base(name) {
+        }
+    }
+}

+ 268 - 0
spine-csharp/src/ConvexDecomposer.cs

@@ -0,0 +1,268 @@
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+namespace Spine {
+	internal class ConvexDecomposer {
+	//	private final Array<FloatArray> convexPolygons = new Array();
+	//	private final Array<ShortArray> convexPolygonsIndices = new Array();
+
+	//	private final ShortArray indicesArray = new ShortArray();
+	//	private final BooleanArray isConcaveArray = new BooleanArray();
+	//	private final ShortArray triangles = new ShortArray();
+
+	//	private final Pool<FloatArray> polygonPool = new Pool() {
+	//		protected FloatArray newObject() {
+	//		return new FloatArray(16);
+	//	}
+	//};
+
+	//private final Pool<ShortArray> polygonIndicesPool = new Pool() {
+	//		protected ShortArray newObject() {
+	//	return new ShortArray(16);
+	//}
+	//	};
+
+	//	public Array<FloatArray> decompose(FloatArray input) {
+	//	float[] vertices = input.items;
+	//	int vertexCount = input.size >> 1;
+
+	//	ShortArray indicesArray = this.indicesArray;
+	//	indicesArray.clear();
+	//	short[] indices = indicesArray.setSize(vertexCount);
+	//	for (short i = 0; i < vertexCount; i++)
+	//		indices[i] = i;
+
+	//	BooleanArray isConcaveArray = this.isConcaveArray;
+	//	boolean[] isConcave = isConcaveArray.setSize(vertexCount);
+	//	for (int i = 0, n = vertexCount; i < n; ++i)
+	//		isConcave[i] = isConcave(i, vertexCount, vertices, indices);
+
+	//	ShortArray triangles = this.triangles;
+	//	triangles.clear();
+	//	triangles.ensureCapacity(Math.max(0, vertexCount - 2) << 2);
+
+	//	while (vertexCount > 3) {
+	//		// Find ear tip.
+	//		int previous = vertexCount - 1, i = 0, next = 1;
+	//		while (true) {
+	//		outer:
+	//			if (!isConcave[i]) {
+	//				int p1 = indices[previous] << 1, p2 = indices[i] << 1, p3 = indices[next] << 1;
+	//				float p1x = vertices[p1], p1y = vertices[p1 + 1];
+	//				float p2x = vertices[p2], p2y = vertices[p2 + 1];
+	//				float p3x = vertices[p3], p3y = vertices[p3 + 1];
+	//				for (int ii = (next + 1) % vertexCount; ii != previous; ii = (ii + 1) % vertexCount) {
+	//					if (!isConcave[ii]) continue;
+	//					int v = indices[ii] << 1;
+	//					float vx = vertices[v], vy = vertices[v + 1];
+	//					if (positiveArea(p3x, p3y, p1x, p1y, vx, vy)) {
+	//						if (positiveArea(p1x, p1y, p2x, p2y, vx, vy)) {
+	//							if (positiveArea(p2x, p2y, p3x, p3y, vx, vy)) break outer;
+	//						}
+	//					}
+	//				}
+	//				break;
+	//			}
+
+	//			if (next == 0) {
+	//				do {
+	//					if (!isConcave[i]) break;
+	//					i--;
+	//				} while (i > 0);
+	//				break;
+	//			}
+
+	//			previous = i;
+	//			i = next;
+	//			next = (next + 1) % vertexCount;
+	//		}
+
+	//		// Cut ear tip.
+	//		triangles.add(indices[(vertexCount + i - 1) % vertexCount]);
+	//		triangles.add(indices[i]);
+	//		triangles.add(indices[(i + 1) % vertexCount]);
+	//		indicesArray.removeIndex(i);
+	//		isConcaveArray.removeIndex(i);
+	//		vertexCount--;
+
+	//		int previousIndex = (vertexCount + i - 1) % vertexCount;
+	//		int nextIndex = i == vertexCount ? 0 : i;
+	//		isConcave[previousIndex] = isConcave(previousIndex, vertexCount, vertices, indices);
+	//		isConcave[nextIndex] = isConcave(nextIndex, vertexCount, vertices, indices);
+	//	}
+
+	//	if (vertexCount == 3) {
+	//		triangles.add(indices[2]);
+	//		triangles.add(indices[0]);
+	//		triangles.add(indices[1]);
+	//	}
+
+	//	Array<FloatArray> convexPolygons = this.convexPolygons;
+	//	polygonPool.freeAll(convexPolygons);
+	//	convexPolygons.clear();
+
+	//	Array<ShortArray> convexPolygonsIndices = this.convexPolygonsIndices;
+	//	polygonIndicesPool.freeAll(convexPolygonsIndices);
+	//	convexPolygonsIndices.clear();
+
+	//	ShortArray polygonIndices = polygonIndicesPool.obtain();
+	//	polygonIndices.clear();
+
+	//	FloatArray polygon = polygonPool.obtain();
+	//	polygon.clear();
+
+	//	// Merge subsequent triangles if they form a triangle fan.
+	//	int fanBaseIndex = -1, lastWinding = 0;
+	//	short[] trianglesItems = triangles.items;
+	//	for (int i = 0, n = triangles.size; i < n; i += 3) {
+	//		int t1 = trianglesItems[i] << 1, t2 = trianglesItems[i + 1] << 1, t3 = trianglesItems[i + 2] << 1;
+	//		float x1 = vertices[t1], y1 = vertices[t1 + 1];
+	//		float x2 = vertices[t2], y2 = vertices[t2 + 1];
+	//		float x3 = vertices[t3], y3 = vertices[t3 + 1];
+
+	//		// If the base of the last triangle is the same as this triangle, check if they form a convex polygon (triangle fan).
+	//		boolean merged = false;
+	//		if (fanBaseIndex == t1) {
+	//			int o = polygon.size - 4;
+	//			float[] p = polygon.items;
+	//			int winding1 = winding(p[o], p[o + 1], p[o + 2], p[o + 3], x3, y3);
+	//			int winding2 = winding(x3, y3, p[0], p[1], p[2], p[3]);
+	//			if (winding1 == lastWinding && winding2 == lastWinding) {
+	//				polygon.add(x3);
+	//				polygon.add(y3);
+	//				polygonIndices.add(t3);
+	//				merged = true;
+	//			}
+	//		}
+
+	//		// Otherwise make this triangle the new base.
+	//		if (!merged) {
+	//			if (polygon.size > 0) {
+	//				convexPolygons.add(polygon);
+	//				convexPolygonsIndices.add(polygonIndices);
+	//			}
+	//			polygon = polygonPool.obtain();
+	//			polygon.clear();
+	//			polygon.add(x1);
+	//			polygon.add(y1);
+	//			polygon.add(x2);
+	//			polygon.add(y2);
+	//			polygon.add(x3);
+	//			polygon.add(y3);
+	//			polygonIndices = polygonIndicesPool.obtain();
+	//			polygonIndices.clear();
+	//			polygonIndices.add(t1);
+	//			polygonIndices.add(t2);
+	//			polygonIndices.add(t3);
+	//			lastWinding = winding(x1, y1, x2, y2, x3, y3);
+	//			fanBaseIndex = t1;
+	//		}
+	//	}
+
+	//	if (polygon.size > 0) {
+	//		convexPolygons.add(polygon);
+	//		convexPolygonsIndices.add(polygonIndices);
+	//	}
+
+	//	// 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++) {
+	//		polygonIndices = convexPolygonsIndices.get(i);
+	//		if (polygonIndices.size == 0) continue;
+	//		int firstIndex = polygonIndices.get(0);
+	//		int lastIndex = polygonIndices.get(polygonIndices.size - 1);
+
+	//		polygon = convexPolygons.get(i);
+	//		int o = polygon.size - 4;
+	//		float[] p = polygon.items;
+	//		float prevPrevX = p[o], prevPrevY = p[o + 1];
+	//		float prevX = p[o + 2], prevY = p[o + 3];
+	//		float firstX = p[0], firstY = p[1];
+	//		float secondX = p[2], secondY = p[3];
+	//		int winding = winding(prevPrevX, prevPrevY, prevX, prevY, firstX, firstY);
+
+	//		for (int ii = 0; ii < n; ii++) {
+	//			if (ii == i) continue;
+	//			ShortArray otherIndices = convexPolygonsIndices.get(ii);
+	//			if (otherIndices.size != 3) continue;
+	//			int otherFirstIndex = otherIndices.get(0);
+	//			int otherSecondIndex = otherIndices.get(1);
+	//			int otherLastIndex = otherIndices.get(2);
+
+	//			FloatArray otherPoly = convexPolygons.get(ii);
+	//			float x3 = otherPoly.get(otherPoly.size - 2), 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();
+	//				polygon.add(x3);
+	//				polygon.add(y3);
+	//				polygonIndices.add(otherLastIndex);
+	//				prevPrevX = prevX;
+	//				prevPrevY = prevY;
+	//				prevX = x3;
+	//				prevY = y3;
+	//				ii = 0;
+	//			}
+	//		}
+	//	}
+
+	//	// Remove empty polygons that resulted from the merge step above.
+	//	for (int i = convexPolygons.size - 1; i >= 0; i--) {
+	//		polygon = convexPolygons.get(i);
+	//		if (polygon.size == 0) {
+	//			convexPolygons.removeIndex(i);
+	//			polygonPool.free(polygon);
+	//		}
+	//	}
+
+	//	return convexPolygons;
+	//}
+
+	//static private boolean isConcave(int index, int vertexCount, float[] vertices, short[] indices) {
+	//	int previous = indices[(vertexCount + index - 1) % vertexCount] << 1;
+	//	int current = indices[index] << 1;
+	//	int next = indices[(index + 1) % vertexCount] << 1;
+	//	return !positiveArea(vertices[previous], vertices[previous + 1], vertices[current], vertices[current + 1], vertices[next],
+	//		vertices[next + 1]);
+	//}
+
+	//static private boolean positiveArea(float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) {
+	//	return p1x * (p3y - p2y) + p2x * (p1y - p3y) + p3x * (p2y - p1y) >= 0;
+	//}
+
+	//static private int winding(float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) {
+	//	float px = p2x - p1x, py = p2y - p1y;
+	//	return p3x * py - p3y * px + px * p1y - p1x * py >= 0 ? 1 : -1;
+	//}
+	}
+}

+ 19 - 5
spine-csharp/src/SkeletonBinary.cs

@@ -252,7 +252,7 @@ namespace Spine {
 			}
 
 			// Default skin.
-			Skin defaultSkin = ReadSkin(input, "default", nonessential);
+			Skin defaultSkin = ReadSkin(input, skeletonData, "default", nonessential);
 			if (defaultSkin != null) {
 				skeletonData.defaultSkin = defaultSkin;
 				skeletonData.skins.Add(defaultSkin);
@@ -260,7 +260,7 @@ namespace Spine {
 
 			// Skins.
 			for (int i = 0, n = ReadVarint(input, true); i < n; i++)
-				skeletonData.skins.Add(ReadSkin(input, ReadString(input), nonessential));
+				skeletonData.skins.Add(ReadSkin(input, skeletonData, ReadString(input), nonessential));
 
 			// Linked meshes.
 			for (int i = 0, n = linkedMeshes.Count; i < n; i++) {
@@ -299,7 +299,7 @@ namespace Spine {
 
 
 		/// <returns>May be null.</returns>
-		private Skin ReadSkin (Stream input, String skinName, bool nonessential) {
+		private Skin ReadSkin (Stream input, SkeletonData skeletonData, String skinName, bool nonessential) {
 			int slotCount = ReadVarint(input, true);
 			if (slotCount == 0) return null;
 			Skin skin = new Skin(skinName);
@@ -307,14 +307,14 @@ namespace Spine {
 				int slotIndex = ReadVarint(input, true);
 				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) {
 					String name = ReadString(input);
-					Attachment attachment = ReadAttachment(input, skin, slotIndex, name, nonessential);
+					Attachment attachment = ReadAttachment(input, skeletonData, skin, slotIndex, name, nonessential);
 					if (attachment != null) skin.AddAttachment(slotIndex, name, attachment);
 				}
 			}
 			return skin;
 		}
 
-		private Attachment ReadAttachment (Stream input, Skin skin, int slotIndex, String attachmentName, bool nonessential) {
+		private Attachment ReadAttachment (Stream input, SkeletonData skeletonData, Skin skin, int slotIndex, String attachmentName, bool nonessential) {
 			float scale = Scale;
 
 			String name = ReadString(input);
@@ -463,6 +463,20 @@ namespace Spine {
 					//if (nonessential) point.color = color;
 					return point;
 				}
+			case AttachmentType.Clipping: {
+					int endSlotIndex = ReadVarint(input, true);
+					int vertexCount = ReadVarint(input, true);
+					Vertices vertices = ReadVertices(input, vertexCount);
+					if (nonessential) ReadInt(input);
+
+					ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name);
+					if (clip == null) return null;
+					clip.EndSlot = skeletonData.slots.Items[endSlotIndex];
+					clip.worldVerticesLength = vertexCount << 1;
+					clip.vertices = vertices.vertices;
+					clip.bones = vertices.bones;
+					return clip;
+				}
 			}
 			return null;
 		}

+ 319 - 0
spine-csharp/src/SkeletonClipping.cs

@@ -0,0 +1,319 @@
+/******************************************************************************
+ * 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.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+	public class SkeletonClipping {
+	//	private final ConvexDecomposer decomposer = new ConvexDecomposer();
+	//	private final FloatArray clippingPolygon = new FloatArray();
+	//	private final FloatArray clipOutput = new FloatArray(128);
+	//	private final FloatArray clippedVertices = new FloatArray(128);
+	//	private final ShortArray clippedTriangles = new ShortArray(128);
+	//	private final FloatArray scratch = new FloatArray();
+
+	//	private ClippingAttachment clipAttachment;
+	//	private Array<FloatArray> clippingPolygons;
+
+	//	public void clipStart(Slot slot, ClippingAttachment clip) {
+	//		if (clipAttachment != null) return;
+	//		clipAttachment = clip;
+
+	//		int n = clip.getWorldVerticesLength();
+	//		float[] vertices = clippingPolygon.setSize(n);
+	//		clip.computeWorldVertices(slot, 0, n, vertices, 0, 2);
+	//		makeClockwise(clippingPolygon);
+	//		clippingPolygons = decomposer.decompose(clippingPolygon);
+	//		for (FloatArray polygon : clippingPolygons) {
+	//			makeClockwise(polygon);
+	//			polygon.add(polygon.items[0]);
+	//			polygon.add(polygon.items[1]);
+	//		}
+	//	}
+
+	//	public void clipEnd(Slot slot) {
+	//		if (clipAttachment != null && clipAttachment.getEndSlot() == slot.getData()) clipEnd();
+	//	}
+
+	//	public void clipEnd() {
+	//		if (clipAttachment == null) return;
+	//		clipAttachment = null;
+	//		clippingPolygons = null;
+	//		clippedVertices.clear();
+	//		clippedTriangles.clear();
+	//		clippingPolygon.clear();
+	//	}
+
+	//	public boolean isClipping() {
+	//		return clipAttachment != null;
+	//	}
+
+	//	public void clipTriangles(float[] vertices, int verticesLength, short[] triangles, int trianglesLength, float[] uvs,
+	//		float light, float dark, boolean twoColor) {
+
+	//		FloatArray clipOutput = this.clipOutput, clippedVertices = this.clippedVertices;
+	//		ShortArray clippedTriangles = this.clippedTriangles;
+	//		Object[] polygons = clippingPolygons.items;
+	//		int polygonsCount = clippingPolygons.size;
+	//		int vertexSize = twoColor ? 6 : 5;
+
+	//		short index = 0;
+	//		clippedVertices.clear();
+	//		clippedTriangles.clear();
+	//	outer:
+	//		for (int i = 0; i < trianglesLength; i += 3) {
+	//			int vertexOffset = triangles[i] << 1;
+	//			float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1];
+	//			float u1 = uvs[vertexOffset], v1 = uvs[vertexOffset + 1];
+
+	//			vertexOffset = triangles[i + 1] << 1;
+	//			float x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1];
+	//			float u2 = uvs[vertexOffset], v2 = uvs[vertexOffset + 1];
+
+	//			vertexOffset = triangles[i + 2] << 1;
+	//			float x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1];
+	//			float u3 = uvs[vertexOffset], v3 = uvs[vertexOffset + 1];
+
+	//			for (int p = 0; p < polygonsCount; p++) {
+	//				int s = clippedVertices.size;
+	//				if (clip(x1, y1, x2, y2, x3, y3, (FloatArray)polygons[p], clipOutput)) {
+	//					int clipOutputLength = clipOutput.size;
+	//					if (clipOutputLength == 0) continue;
+	//					float d0 = y2 - y3, d1 = x3 - x2, d2 = x1 - x3, d4 = y3 - y1;
+	//					float d = 1 / (d0 * d2 + d1 * (y1 - y3));
+
+	//					int clipOutputCount = clipOutputLength >> 1;
+	//					float[] clipOutputItems = clipOutput.items;
+	//					float[] clippedVerticesItems = clippedVertices.setSize(s + clipOutputCount * vertexSize);
+	//					for (int ii = 0; ii < clipOutputLength; ii += 2) {
+	//						float x = clipOutputItems[ii], y = clipOutputItems[ii + 1];
+	//						clippedVerticesItems[s] = x;
+	//						clippedVerticesItems[s + 1] = y;
+	//						clippedVerticesItems[s + 2] = light;
+	//						if (twoColor) {
+	//							clippedVerticesItems[s + 3] = dark;
+	//							s += 4;
+	//						}
+	//						else
+	//							s += 3;
+	//						float c0 = x - x3, c1 = y - y3;
+	//						float a = (d0 * c0 + d1 * c1) * d;
+	//						float b = (d4 * c0 + d2 * c1) * d;
+	//						float c = 1 - a - b;
+	//						clippedVerticesItems[s] = u1 * a + u2 * b + u3 * c;
+	//						clippedVerticesItems[s + 1] = v1 * a + v2 * b + v3 * c;
+	//						s += 2;
+	//					}
+
+	//					s = clippedTriangles.size;
+	//					short[] clippedTrianglesItems = clippedTriangles.setSize(s + 3 * (clipOutputCount - 2));
+	//					clipOutputCount--;
+	//					for (int ii = 1; ii < clipOutputCount; ii++) {
+	//						clippedTrianglesItems[s] = index;
+	//						clippedTrianglesItems[s + 1] = (short)(index + ii);
+	//						clippedTrianglesItems[s + 2] = (short)(index + ii + 1);
+	//						s += 3;
+	//					}
+	//					index += clipOutputCount + 1;
+
+	//				}
+	//				else {
+	//					float[] clippedVerticesItems = clippedVertices.setSize(s + 3 * vertexSize);
+	//					clippedVerticesItems[s] = x1;
+	//					clippedVerticesItems[s + 1] = y1;
+	//					clippedVerticesItems[s + 2] = light;
+	//					if (!twoColor) {
+	//						clippedVerticesItems[s + 3] = u1;
+	//						clippedVerticesItems[s + 4] = v1;
+
+	//						clippedVerticesItems[s + 5] = x2;
+	//						clippedVerticesItems[s + 6] = y2;
+	//						clippedVerticesItems[s + 7] = light;
+	//						clippedVerticesItems[s + 8] = u2;
+	//						clippedVerticesItems[s + 9] = v2;
+
+	//						clippedVerticesItems[s + 10] = x3;
+	//						clippedVerticesItems[s + 11] = y3;
+	//						clippedVerticesItems[s + 12] = light;
+	//						clippedVerticesItems[s + 13] = u3;
+	//						clippedVerticesItems[s + 14] = v3;
+	//					}
+	//					else {
+	//						clippedVerticesItems[s + 3] = dark;
+	//						clippedVerticesItems[s + 4] = u1;
+	//						clippedVerticesItems[s + 5] = v1;
+
+	//						clippedVerticesItems[s + 6] = x2;
+	//						clippedVerticesItems[s + 7] = y2;
+	//						clippedVerticesItems[s + 8] = light;
+	//						clippedVerticesItems[s + 9] = dark;
+	//						clippedVerticesItems[s + 10] = u2;
+	//						clippedVerticesItems[s + 11] = v2;
+
+	//						clippedVerticesItems[s + 12] = x3;
+	//						clippedVerticesItems[s + 13] = y3;
+	//						clippedVerticesItems[s + 14] = light;
+	//						clippedVerticesItems[s + 15] = dark;
+	//						clippedVerticesItems[s + 16] = u3;
+	//						clippedVerticesItems[s + 17] = v3;
+	//					}
+
+	//					s = clippedTriangles.size;
+	//					short[] clippedTrianglesItems = clippedTriangles.setSize(s + 3);
+	//					clippedTrianglesItems[s] = index;
+	//					clippedTrianglesItems[s + 1] = (short)(index + 1);
+	//					clippedTrianglesItems[s + 2] = (short)(index + 2);
+	//					index += 3;
+	//					continue outer;
+	//				}
+	//			}
+	//		}
+	//	}
+
+	//	/** Clips the input triangle against the convex, clockwise clipping area. If the triangle lies entirely within the clipping
+	//	 * area, false is returned. The clipping area must duplicate the first vertex at the end of the vertices list. */
+	//	boolean clip(float x1, float y1, float x2, float y2, float x3, float y3, FloatArray clippingArea, FloatArray output) {
+	//		FloatArray originalOutput = output;
+	//		boolean clipped = false;
+
+	//		// Avoid copy at the end.
+	//		FloatArray input = null;
+	//		if (clippingArea.size % 4 >= 2) {
+	//			input = output;
+	//			output = scratch;
+	//		}
+	//		else
+	//			input = scratch;
+
+	//		input.clear();
+	//		input.add(x1);
+	//		input.add(y1);
+	//		input.add(x2);
+	//		input.add(y2);
+	//		input.add(x3);
+	//		input.add(y3);
+	//		input.add(x1);
+	//		input.add(y1);
+	//		output.clear();
+
+	//		float[] clippingVertices = clippingArea.items;
+	//		int clippingVerticesLast = clippingArea.size - 4;
+	//		for (int i = 0; ; i += 2) {
+	//			float edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1];
+	//			float edgeX2 = clippingVertices[i + 2], edgeY2 = clippingVertices[i + 3];
+	//			float deltaX = edgeX - edgeX2, deltaY = edgeY - edgeY2;
+
+	//			float[] inputVertices = input.items;
+	//			int inputVerticesLength = input.size - 2, outputStart = output.size;
+	//			for (int ii = 0; ii < inputVerticesLength; ii += 2) {
+	//				float inputX = inputVertices[ii], inputY = inputVertices[ii + 1];
+	//				float inputX2 = inputVertices[ii + 2], inputY2 = inputVertices[ii + 3];
+	//				boolean side2 = deltaX * (inputY2 - edgeY2) - deltaY * (inputX2 - edgeX2) > 0;
+	//				if (deltaX * (inputY - edgeY2) - deltaY * (inputX - edgeX2) > 0) {
+	//					if (side2) { // v1 inside, v2 inside
+	//						output.add(inputX2);
+	//						output.add(inputY2);
+	//						continue;
+	//					}
+	//					// v1 inside, v2 outside
+	//					float c0 = inputY2 - inputY, c2 = inputX2 - inputX;
+	//					float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / (c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY));
+	//					output.add(edgeX + (edgeX2 - edgeX) * ua);
+	//					output.add(edgeY + (edgeY2 - edgeY) * ua);
+	//				}
+	//				else if (side2) { // v1 outside, v2 inside
+	//					float c0 = inputY2 - inputY, c2 = inputX2 - inputX;
+	//					float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / (c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY));
+	//					output.add(edgeX + (edgeX2 - edgeX) * ua);
+	//					output.add(edgeY + (edgeY2 - edgeY) * ua);
+	//					output.add(inputX2);
+	//					output.add(inputY2);
+	//				}
+	//				clipped = true;
+	//			}
+
+	//			if (outputStart == output.size) { // All edges outside.
+	//				originalOutput.clear();
+	//				return true;
+	//			}
+
+	//			output.add(output.items[0]);
+	//			output.add(output.items[1]);
+
+	//			if (i == clippingVerticesLast) break;
+	//			FloatArray temp = output;
+	//			output = input;
+	//			output.clear();
+	//			input = temp;
+	//		}
+
+	//		if (originalOutput != output) {
+	//			originalOutput.clear();
+	//			originalOutput.addAll(output.items, 0, output.size - 2);
+	//		}
+	//		else
+	//			originalOutput.setSize(originalOutput.size - 2);
+
+	//		return clipped;
+	//	}
+
+	//	public FloatArray getClippedVertices() {
+	//		return clippedVertices;
+	//	}
+
+	//	public ShortArray getClippedTriangles() {
+	//		return clippedTriangles;
+	//	}
+
+	//	static void makeClockwise(FloatArray polygon) {
+	//		float[] vertices = polygon.items;
+	//		int verticeslength = polygon.size;
+
+	//		float area = vertices[verticeslength - 2] * vertices[1] - vertices[0] * vertices[verticeslength - 1], p1x, p1y, p2x, p2y;
+	//		for (int i = 0, n = verticeslength - 3; i < n; i += 2) {
+	//			p1x = vertices[i];
+	//			p1y = vertices[i + 1];
+	//			p2x = vertices[i + 2];
+	//			p2y = vertices[i + 3];
+	//			area += p1x * p2y - p2x * p1y;
+	//		}
+	//		if (area < 0) return;
+
+	//		for (int i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) {
+	//			float x = vertices[i], y = vertices[i + 1];
+	//			int other = lastX - i;
+	//			vertices[i] = vertices[other];
+	//			vertices[i + 1] = vertices[other + 1];
+	//			vertices[other] = x;
+	//			vertices[other + 1] = y;
+	//		}
+	//	}
+	}
+}

+ 12 - 2
spine-csharp/src/SkeletonJson.cs

@@ -261,7 +261,7 @@ namespace Spine {
 						int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key);
 						foreach (KeyValuePair<String, Object> entry in ((Dictionary<String, Object>)slotEntry.Value)) {
 							try {
-								Attachment attachment = ReadAttachment((Dictionary<String, Object>)entry.Value, skin, slotIndex, entry.Key);
+								Attachment attachment = ReadAttachment((Dictionary<String, Object>)entry.Value, skin, slotIndex, entry.Key, skeletonData);
 								if (attachment != null) skin.AddAttachment(slotIndex, entry.Key, attachment);
 							} catch (Exception e) {
 								throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e);
@@ -317,7 +317,7 @@ namespace Spine {
 			return skeletonData;
 		}
 
-		private Attachment ReadAttachment (Dictionary<String, Object> map, Skin skin, int slotIndex, String name) {
+		private Attachment ReadAttachment (Dictionary<String, Object> map, Skin skin, int slotIndex, String name, SkeletonData skeletonData) {
 			var scale = this.Scale;
 			name = GetString(map, "name", name);
 
@@ -416,6 +416,16 @@ namespace Spine {
 					//if (color != null) point.color = color;
 					return point;
 				}
+			case AttachmentType.Clipping: {
+					ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name);
+					if (clip == null) return null;
+
+					SlotData slot = skeletonData.FindSlot(GetString(map, "end", null));
+					if (slot == null) throw new Exception("Clipping end slot not found: " + GetString(map, "end", null));
+					clip.endSlot = slot;
+					ReadVertices(map, clip, GetInt(map, "vertexCount", 0) << 1);
+					return clip;
+				}
 			}
 			return null;
 		}