فهرست منبع

Updated spine-csharp to 3.1.05: linked meshes. (#543)

* Updated spine-csharp to 3.1.00: linked meshes.

* Updated SkeletonBinary.cs for 3.1.05

based on:
https://github.com/EsotericSoftware/spine-runtimes/commit/3a06b829cc81203ebeec996efad33e4bef6cc8c7
https://github.com/EsotericSoftware/spine-runtimes/commit/8c55aa1f62d5a3238c9f6be7a835a6a9b5995593

* Fixed skeleton flip when rotation and scale are disabled.

Based on: https://github.com/EsotericSoftware/spine-runtimes/commit/b22669711d94005c75c367b8b792a1582a8404b4

* Fixed IK when the parent has nonuniform scale and the child Y != 0.

Based on: https://github.com/EsotericSoftware/spine-runtimes/commit/bf902936aa5a914b2be901cbd142a323e1f43955

* Removed redundant locals.
John 9 سال پیش
والد
کامیت
ba5e4073c3

+ 2 - 1
spine-csharp/src/Animation.cs

@@ -573,7 +573,8 @@ namespace Spine {
 
 		override public void Apply (Skeleton skeleton, float lastTime, float time, ExposedList<Event> firedEvents, float alpha) {
 			Slot slot = skeleton.slots.Items[slotIndex];
-			if (slot.attachment != attachment) return;
+			IFfdAttachment ffdAttachment = slot.attachment as IFfdAttachment; // == null if not FfdAttachment.
+			if (ffdAttachment == null || !ffdAttachment.ApplyFFD(attachment)) return;
 
 			float[] frames = this.frames;
 			if (time < frames[0]) return; // Time is before first frame.

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

@@ -31,6 +31,6 @@
 
 namespace Spine {
 	public enum AttachmentType {
-		region, boundingbox, mesh, weightedmesh
+		region, boundingbox, mesh, weightedmesh, linkedmesh, weightedlinkedmesh
 	}
 }

+ 5 - 0
spine-csharp/src/Attachments/IFfdAttachment.cs

@@ -0,0 +1,5 @@
+namespace Spine {
+	public interface IFfdAttachment {
+		bool ApplyFFD (Attachment sourceAttachment);
+	}
+}

+ 22 - 1
spine-csharp/src/Attachments/MeshAttachment.cs

@@ -33,11 +33,13 @@ using System;
 
 namespace Spine {
 	/// <summary>Attachment that displays a texture region using a mesh.</summary>
-	public class MeshAttachment : Attachment {
+	public class MeshAttachment : Attachment, IFfdAttachment {
 		internal float[] vertices, uvs, regionUVs;
 		internal int[] triangles;
 		internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight;
 		internal float r = 1, g = 1, b = 1, a = 1;
+		internal MeshAttachment parentMesh;
+		internal bool inheritFFD;
 
 		public int HullLength { get; set; }
 		public float[] Vertices { get { return vertices; } set { vertices = value; } }
@@ -64,6 +66,21 @@ namespace Spine {
 		public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } }
 		public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size.
 
+		public bool InheritFFD { get { return inheritFFD; } set { inheritFFD = value; } }
+
+		public MeshAttachment ParentMesh {
+			get { return parentMesh; }
+			set {
+				parentMesh = value;
+				if (value != null) {
+					vertices = value.vertices;
+					regionUVs = value.regionUVs;
+					triangles = value.triangles;
+					HullLength = value.HullLength;
+				}
+			}
+		}
+
 		// Nonessential.
 		public int[] Edges { get; set; }
 		public float Width { get; set; }
@@ -105,5 +122,9 @@ namespace Spine {
 				worldVertices[i + 1] = vx * m10 + vy * m11 + y;
 			}
 		}
+
+		public bool ApplyFFD (Attachment sourceAttachment) {
+			return this == sourceAttachment || (inheritFFD && parentMesh == sourceAttachment);
+		}
 	}
 }

+ 23 - 1
spine-csharp/src/Attachments/WeightedMeshAttachment.cs

@@ -34,12 +34,14 @@ using System.Collections.Generic;
 
 namespace Spine {
 	/// <summary>Attachment that displays a texture region using a mesh which can be deformed by bones.</summary>
-	public class WeightedMeshAttachment : Attachment {
+	public class WeightedMeshAttachment : Attachment, IFfdAttachment {
 		internal int[] bones;
 		internal float[] weights, uvs, regionUVs;
 		internal int[] triangles;
 		internal float regionOffsetX, regionOffsetY, regionWidth, regionHeight, regionOriginalWidth, regionOriginalHeight;
 		internal float r = 1, g = 1, b = 1, a = 1;
+		internal WeightedMeshAttachment parentMesh;
+		internal bool inheritFFD;
 
 		public int HullLength { get; set; }
 		public int[] Bones { get { return bones; } set { bones = value; } }
@@ -67,6 +69,22 @@ namespace Spine {
 		public float RegionOriginalWidth { get { return regionOriginalWidth; } set { regionOriginalWidth = value; } }
 		public float RegionOriginalHeight { get { return regionOriginalHeight; } set { regionOriginalHeight = value; } } // Unrotated, unstripped size.
 
+		public bool InheritFFD { get { return inheritFFD; } set { inheritFFD = value; } }
+
+		public WeightedMeshAttachment ParentMesh {
+			get { return parentMesh; }
+			set {
+				parentMesh = value;
+				if (value != null) {
+					bones = value.bones;
+					weights = value.weights;
+					regionUVs = value.regionUVs;
+					triangles = value.triangles;
+					HullLength = value.HullLength;
+				}
+			}
+		}
+
 		// Nonessential.
 		public int[] Edges { get; set; }
 		public float Width { get; set; }
@@ -129,5 +147,9 @@ namespace Spine {
 				}
 			}
 		}
+
+		public bool ApplyFFD (Attachment sourceAttachment) {
+			return this == sourceAttachment || (inheritFFD && parentMesh == sourceAttachment);
+		}
 	}
 }

+ 65 - 72
spine-csharp/src/Bone.cs

@@ -139,71 +139,70 @@ namespace Spine {
 				b = pa * lb + pb * ld;
 				c = pc * la + pd * lc;
 				d = pc * lb + pd * ld;
-			} else if (data.inheritRotation) { // No scale inheritance.
-				pa = 1;
-				pb = 0;
-				pc = 0;
-				pd = 1;
-				do {
-					cos = MathUtils.CosDeg(parent.appliedRotation);
-					sin = MathUtils.SinDeg(parent.appliedRotation);
-					float temp = pa * cos + pb * sin;
-					pb = pa * -sin + pb * cos;
-					pa = temp;
-					temp = pc * cos + pd * sin;
-					pd = pc * -sin + pd * cos;
-					pc = temp;
-
-					if (!parent.data.inheritRotation) break;
-					parent = parent.parent;
-				} while (parent != null);
-				a = pa * la + pb * lc;
-				b = pa * lb + pb * ld;
-				c = pc * la + pd * lc;
-				d = pc * lb + pd * ld;
-				if (skeleton.flipX) {
-					a = -a;
-					b = -b;
-				}
-				if (skeleton.flipY != yDown) {
-					c = -c;
-					d = -d;
+			} else {
+				if (data.inheritRotation) { // No scale inheritance.
+					pa = 1;
+					pb = 0;
+					pc = 0;
+					pd = 1;
+					do {
+						cos = MathUtils.CosDeg(parent.appliedRotation);
+						sin = MathUtils.SinDeg(parent.appliedRotation);
+						float temp = pa * cos + pb * sin;
+						pb = pa * -sin + pb * cos;
+						pa = temp;
+						temp = pc * cos + pd * sin;
+						pd = pc * -sin + pd * cos;
+						pc = temp;
+
+						if (!parent.data.inheritRotation) break;
+						parent = parent.parent;
+					} while (parent != null);
+					a = pa * la + pb * lc;
+					b = pa * lb + pb * ld;
+					c = pc * la + pd * lc;
+					d = pc * lb + pd * ld;
+				} else if (data.inheritScale) { // No rotation inheritance.
+					pa = 1;
+					pb = 0;
+					pc = 0;
+					pd = 1;
+					do {
+						float r = parent.rotation;
+						cos = MathUtils.CosDeg(r);
+						sin = MathUtils.SinDeg(r);
+						float psx = parent.appliedScaleX, psy = parent.appliedScaleY;
+						float za = cos * psx, zb = -sin * psy, zc = sin * psx, zd = cos * psy;
+						float temp = pa * za + pb * zc;
+						pb = pa * zb + pb * zd;
+						pa = temp;
+						temp = pc * za + pd * zc;
+						pd = pc * zb + pd * zd;
+						pc = temp;
+
+						if (psx < 0) r = -r;
+						cos = MathUtils.CosDeg(-r);
+						sin = MathUtils.SinDeg(-r);
+						temp = pa * cos + pb * sin;
+						pb = pa * -sin + pb * cos;
+						pa = temp;
+						temp = pc * cos + pd * sin;
+						pd = pc * -sin + pd * cos;
+						pc = temp;
+
+						if (!parent.data.inheritScale) break;
+						parent = parent.parent;
+					} while (parent != null);
+					a = pa * la + pb * lc;
+					b = pa * lb + pb * ld;
+					c = pc * la + pd * lc;
+					d = pc * lb + pd * ld;
+				} else {
+					a = la;
+					b = lb;
+					c = lc;
+					d = ld;
 				}
-			} else if (data.inheritScale) { // No rotation inheritance.
-				pa = 1;
-				pb = 0;
-				pc = 0;
-				pd = 1;
-				do {
-					float r = parent.rotation;
-					cos = MathUtils.CosDeg(r);
-					sin = MathUtils.SinDeg(r);
-					float psx = parent.appliedScaleX, psy = parent.appliedScaleY;
-					float za = cos * psx, zb = -sin * psy, zc = sin * psx, zd = cos * psy;
-					float temp = pa * za + pb * zc;
-					pb = pa * zb + pb * zd;
-					pa = temp;
-					temp = pc * za + pd * zc;
-					pd = pc * zb + pd * zd;
-					pc = temp;
-
-					if (psx < 0) r = -r;
-					cos = MathUtils.CosDeg(-r);
-					sin = MathUtils.SinDeg(-r);
-					temp = pa * cos + pb * sin;
-					pb = pa * -sin + pb * cos;
-					pa = temp;
-					temp = pc * cos + pd * sin;
-					pd = pc * -sin + pd * cos;
-					pc = temp;
-
-					if (!parent.data.inheritScale) break;
-					parent = parent.parent;
-				} while (parent != null);
-				a = pa * la + pb * lc;
-				b = pa * lb + pb * ld;
-				c = pc * la + pd * lc;
-				d = pc * lb + pd * ld;
 				if (skeleton.flipX) {
 					a = -a;
 					b = -b;
@@ -212,11 +211,6 @@ namespace Spine {
 					c = -c;
 					d = -d;
 				}
-			} else {
-				a = la;
-				b = lb;
-				c = lc;
-				d = ld;
 			}
 		}
 
@@ -238,9 +232,8 @@ namespace Spine {
 		}
 
 		public void LocalToWorld (float localX, float localY, out float worldX, out float worldY) {
-			float x = localX, y = localY;
-			worldX = x * a + y * b + this.worldX;
-			worldY = x * c + y * d + this.worldY;
+			worldX = localX * a + localY * b + this.worldX;
+			worldY = localX * c + localY * d + this.worldY;
 		}
 
 		override public String ToString () {

+ 14 - 8
spine-csharp/src/IkConstraint.cs

@@ -98,7 +98,7 @@ namespace Spine {
 		/// <param name="child">A direct descendant of the parent bone.</param>
 		static public void Apply (Bone parent, Bone child, float targetX, float targetY, int bendDir, float alpha) {
 			if (alpha == 0) return;
-			float px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY, csx = child.scaleX, cy = child.y;
+			float px = parent.x, py = parent.y, psx = parent.scaleX, psy = parent.scaleY;
 			int offset1, offset2, sign2;
 			if (psx < 0) {
 				psx = -psx;
@@ -112,6 +112,13 @@ namespace Spine {
 				psy = -psy;
 				sign2 = -sign2;
 			}
+			float cx = child.x, cy = child.y, csx = child.scaleX;
+			bool u = Math.Abs(psx - psy) <= 0.0001f;
+			if (!u && cy != 0) {
+				child.worldX = parent.a * cx + parent.worldX;
+				child.worldY = parent.c * cx + parent.worldY;
+				cy = 0;
+			}
 			if (csx < 0) {
 				csx = -csx;
 				offset2 = 180;
@@ -135,7 +142,7 @@ namespace Spine {
 				dy = (y * a - x * c) * invDet - py;
 			}
 			float l1 = (float)Math.Sqrt(dx * dx + dy * dy), l2 = child.data.length * csx, a1, a2;
-			if (Math.Abs(psx - psy) <= 0.0001f) {
+			if (u) {
 				l2 *= psx;
 				float cos = (tx * tx + ty * ty - l1 * l1 - l2 * l2) / (2 * l1 * l2);
 				if (cos < -1) cos = -1;
@@ -144,7 +151,6 @@ namespace Spine {
 				float a = l1 + l2 * cos, o = l2 * MathUtils.Sin(a2);
 				a1 = MathUtils.Atan2(ty * a - tx * o, tx * a + ty * o);
 			} else {
-				cy = 0;
 				float a = psx * l2, b = psy * l2, ta = MathUtils.Atan2(ty, tx);
 				float aa = a * a, bb = b * b, ll = l1 * l1, dd = tx * tx + ty * ty;
 				float c0 = bb * ll + aa * dd - aa * bb, c1 = -2 * bb * l1, c2 = bb - aa;
@@ -202,17 +208,17 @@ namespace Spine {
 				}
 			}
 		outer:
-			float offset = MathUtils.Atan2(cy, child.x) * sign2;
-			a1 = (a1 - offset) * MathUtils.radDeg + offset1;
-			a2 = (a2 + offset) * MathUtils.radDeg * sign2 + offset2;
+			float oo = MathUtils.Atan2(cy, cx) * sign2;
+			a1 = (a1 - oo) * MathUtils.radDeg + offset1;
+			a2 = (a2 + oo) * MathUtils.radDeg * sign2 + offset2;
 			if (a1 > 180) a1 -= 360;
 			else if (a1 < -180) a1 += 360;
 			if (a2 > 180) a2 -= 360;
 			else if (a2 < -180) a2 += 360;
 			float rotation = parent.rotation;
-			parent.UpdateWorldTransform(parent.x, parent.y, rotation + (a1 - rotation) * alpha, parent.scaleX, parent.scaleY);
+			parent.UpdateWorldTransform(px, py, rotation + (a1 - rotation) * alpha, parent.appliedScaleX, parent.appliedScaleY);
 			rotation = child.rotation;
-			child.UpdateWorldTransform(child.x, cy, rotation + (a2 - rotation) * alpha, child.scaleX, child.scaleY);
+			child.UpdateWorldTransform(cx, cy, rotation + (a2 - rotation) * alpha, child.appliedScaleX, child.appliedScaleY);
 		}
 	}
 }

+ 212 - 139
spine-csharp/src/SkeletonBinary.cs

@@ -52,15 +52,17 @@ namespace Spine {
 
 		private AttachmentLoader attachmentLoader;
 		public float Scale { get; set; }
-		private char[] chars = new char[32];
+		private byte[] bytes = new byte[32];
 		private byte[] buffer = new byte[4];
 
+		private List<SkeletonJson.LinkedMesh> linkedMeshes = new List<SkeletonJson.LinkedMesh>();
+
 		public SkeletonBinary (params Atlas[] atlasArray)
 			: this(new AtlasAttachmentLoader(atlasArray)) {
 		}
 
 		public SkeletonBinary (AttachmentLoader attachmentLoader) {
-			if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader cannot be null.");
+			if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader");
 			this.attachmentLoader = attachmentLoader;
 			Scale = 1;
 		}
@@ -97,7 +99,7 @@ namespace Spine {
 		#endif // !(UNITY)
 
 		public SkeletonData ReadSkeletonData (Stream input) {
-			if (input == null) throw new ArgumentNullException("input cannot be null.");
+			if (input == null) throw new ArgumentNullException("input");
 			float scale = Scale;
 
 			var skeletonData = new SkeletonData();
@@ -116,11 +118,9 @@ namespace Spine {
 			}
 
 			// Bones.
-			for (int i = 0, n = ReadInt(input, true); i < n; i++) {
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
 				String name = ReadString(input);
-				BoneData parent = null;
-				int parentIndex = ReadInt(input, true) - 1;
-				if (parentIndex != -1) parent = skeletonData.bones.Items[parentIndex];
+				BoneData parent = i == 0 ? null : skeletonData.bones.Items[ReadVarint(input, true)];
 				BoneData boneData = new BoneData(name, parent);
 				boneData.x = ReadFloat(input) * scale;
 				boneData.y = ReadFloat(input) * scale;
@@ -135,21 +135,21 @@ namespace Spine {
 			}
 
 			// IK constraints.
-			for (int i = 0, n = ReadInt(input, true); i < n; i++) {
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
 				IkConstraintData ikConstraintData = new IkConstraintData(ReadString(input));
-				for (int ii = 0, nn = ReadInt(input, true); ii < nn; ii++)
-					ikConstraintData.bones.Add(skeletonData.bones.Items[ReadInt(input, true)]);
-				ikConstraintData.target = skeletonData.bones.Items[ReadInt(input, true)];
+				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++)
+					ikConstraintData.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]);
+				ikConstraintData.target = skeletonData.bones.Items[ReadVarint(input, true)];
 				ikConstraintData.mix = ReadFloat(input);
 				ikConstraintData.bendDirection = ReadSByte(input);
 				skeletonData.ikConstraints.Add(ikConstraintData);
 			}
 
 			// Transform constraints.
-			for (int i = 0, n = ReadInt(input, true); i < n; i++) {
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
 				TransformConstraintData transformConstraintData = new TransformConstraintData(ReadString(input));
-				transformConstraintData.bone = skeletonData.bones.Items[ReadInt(input, true)];
-				transformConstraintData.target = skeletonData.bones.Items[ReadInt(input, true)];
+				transformConstraintData.bone = skeletonData.bones.Items[ReadVarint(input, true)];
+				transformConstraintData.target = skeletonData.bones.Items[ReadVarint(input, true)];
 				transformConstraintData.translateMix = ReadFloat(input);
 				transformConstraintData.x = ReadFloat(input) * scale;
 				transformConstraintData.y = ReadFloat(input) * scale;
@@ -157,9 +157,9 @@ namespace Spine {
 			}
 
 			// Slots.
-			for (int i = 0, n = ReadInt(input, true); i < n; i++) {
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
 				String slotName = ReadString(input);
-				BoneData boneData = skeletonData.bones.Items[ReadInt(input, true)];
+				BoneData boneData = skeletonData.bones.Items[ReadVarint(input, true)];
 				SlotData slotData = new SlotData(slotName, boneData);
 				int color = ReadInt(input);
 				slotData.r = ((color & 0xff000000) >> 24) / 255f;
@@ -167,7 +167,7 @@ namespace Spine {
 				slotData.b = ((color & 0x0000ff00) >> 8) / 255f;
 				slotData.a = ((color & 0x000000ff)) / 255f;
 				slotData.attachmentName = ReadString(input);
-				slotData.blendMode = (BlendMode)ReadInt(input, true);
+				slotData.blendMode = (BlendMode)ReadVarint(input, true);
 				skeletonData.slots.Add(slotData);
 			}
 
@@ -179,20 +179,39 @@ namespace Spine {
 			}
 
 			// Skins.
-			for (int i = 0, n = ReadInt(input, true); i < n; i++)
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++)
 				skeletonData.skins.Add(ReadSkin(input, ReadString(input), nonessential));
 
+			// Linked meshes.
+			for (int i = 0, n = linkedMeshes.Count; i < n; i++) {
+				SkeletonJson.LinkedMesh linkedMesh = linkedMeshes[i];
+				Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin);
+				if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin);
+				Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent);
+				if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent);
+				if (linkedMesh.mesh is MeshAttachment) {
+					MeshAttachment mesh = (MeshAttachment)linkedMesh.mesh;
+					mesh.ParentMesh = (MeshAttachment)parent;
+					mesh.UpdateUVs();
+				} else {
+					WeightedMeshAttachment mesh = (WeightedMeshAttachment)linkedMesh.mesh;
+					mesh.ParentMesh = (WeightedMeshAttachment)parent;
+					mesh.UpdateUVs();
+				}
+			}
+			linkedMeshes.Clear();
+
 			// Events.
-			for (int i = 0, n = ReadInt(input, true); i < n; i++) {
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
 				EventData eventData = new EventData(ReadString(input));
-				eventData.Int = ReadInt(input, false);
+				eventData.Int = ReadVarint(input, false);
 				eventData.Float = ReadFloat(input);
 				eventData.String = ReadString(input);
 				skeletonData.events.Add(eventData);
 			}
 
 			// Animations.
-			for (int i = 0, n = ReadInt(input, true); i < n; i++)
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++)
 				ReadAnimation(ReadString(input), input, skeletonData);
 
 			skeletonData.bones.TrimExcess();
@@ -206,40 +225,49 @@ namespace Spine {
 
 		/** @return May be null. */
 		private Skin ReadSkin (Stream input, String skinName, bool nonessential) {
-			int slotCount = ReadInt(input, true);
+			int slotCount = ReadVarint(input, true);
 			if (slotCount == 0) return null;
 			Skin skin = new Skin(skinName);
 			for (int i = 0; i < slotCount; i++) {
-				int slotIndex = ReadInt(input, true);
-				for (int ii = 0, nn = ReadInt(input, true); ii < nn; ii++) {
+				int slotIndex = ReadVarint(input, true);
+				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) {
 					String name = ReadString(input);
-					skin.AddAttachment(slotIndex, name, ReadAttachment(input, skin, name, nonessential));
+					skin.AddAttachment(slotIndex, name, ReadAttachment(input, skin, slotIndex, name, nonessential));
 				}
 			}
 			return skin;
 		}
 
-		private Attachment ReadAttachment (Stream input, Skin skin, String attachmentName, bool nonessential) {
+		private Attachment ReadAttachment (Stream input, Skin skin, int slotIndex, String attachmentName, bool nonessential) {
 			float scale = Scale;
 
 			String name = ReadString(input);
 			if (name == null) name = attachmentName;
 
-			switch ((AttachmentType)input.ReadByte()) {
+			AttachmentType type = (AttachmentType)input.ReadByte();
+			switch (type) {
 			case AttachmentType.region: {
 					String path = ReadString(input);
+					float x = ReadFloat(input);
+					float y = ReadFloat(input);
+					float scaleX = ReadFloat(input);
+					float scaleY = ReadFloat(input);
+					float rotation = ReadFloat(input);
+					float width = ReadFloat(input);
+					float height = ReadFloat(input);
+					int color = ReadInt(input);
+
 					if (path == null) path = name;
 					RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path);
 					if (region == null) return null;
 					region.Path = path;
-					region.x = ReadFloat(input) * scale;
-					region.y = ReadFloat(input) * scale;
-					region.scaleX = ReadFloat(input);
-					region.scaleY = ReadFloat(input);
-					region.rotation = ReadFloat(input);
-					region.width = ReadFloat(input) * scale;
-					region.height = ReadFloat(input) * scale;
-					int color = ReadInt(input);
+					region.x = x * scale;
+					region.y = y * scale;
+					region.scaleX = scaleX;
+					region.scaleY = scaleY;
+					region.rotation = rotation;
+					region.width = width * scale;
+					region.height = height * scale;
 					region.r = ((color & 0xff000000) >> 24) / 255f;
 					region.g = ((color & 0x00ff0000) >> 16) / 255f;
 					region.b = ((color & 0x0000ff00) >> 8) / 255f;
@@ -248,80 +276,159 @@ namespace Spine {
 					return region;
 				}
 			case AttachmentType.boundingbox: {
+					float[] vertices = ReadFloatArray(input, ReadVarint(input, true) * 2, scale);
 					BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name);
 					if (box == null) return null;
-					box.vertices = ReadFloatArray(input, scale);
+					box.vertices = vertices;
 					return box;
 				}
 			case AttachmentType.mesh: {
 					String path = ReadString(input);
+					int color = ReadInt(input);
+					int hullLength = 0;
+					int verticesLength = ReadVarint(input, true) * 2;
+					float[] uvs = ReadFloatArray(input, verticesLength, 1);
+					int[] triangles = ReadShortArray(input);
+					float[] vertices = ReadFloatArray(input, verticesLength, scale);
+					hullLength = ReadVarint(input, true);
+					int[] edges = null;
+					float width = 0, height = 0;
+					if (nonessential) {
+						edges = ReadShortArray(input);
+						width = ReadFloat(input);
+						height = ReadFloat(input);
+					}
+
 					if (path == null) path = name;
 					MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path);
 					if (mesh == null) return null;
 					mesh.Path = path;
-					mesh.regionUVs = ReadFloatArray(input, 1);
-					mesh.triangles = ReadShortArray(input);
-					mesh.vertices = ReadFloatArray(input, scale);
-					mesh.UpdateUVs();
-					int color = ReadInt(input);
 					mesh.r = ((color & 0xff000000) >> 24) / 255f;
 					mesh.g = ((color & 0x00ff0000) >> 16) / 255f;
 					mesh.b = ((color & 0x0000ff00) >> 8) / 255f;
 					mesh.a = ((color & 0x000000ff)) / 255f;
-					mesh.HullLength = ReadInt(input, true) * 2;
+					mesh.vertices = vertices;
+					mesh.triangles = triangles;
+					mesh.regionUVs = uvs;
+					mesh.UpdateUVs();
+					mesh.HullLength = hullLength;
 					if (nonessential) {
-						mesh.Edges = ReadIntArray(input);
-						mesh.Width = ReadFloat(input) * scale;
-						mesh.Height = ReadFloat(input) * scale;
+						mesh.Edges = edges;
+						mesh.Width = width * scale;
+						mesh.Height = height * scale;
 					}
 					return mesh;
 				}
-			case AttachmentType.weightedmesh: {
+			case AttachmentType.linkedmesh: {
 					String path = ReadString(input);
+					int color = ReadInt(input);
+					String skinName = ReadString(input);
+					String parent = ReadString(input);
+					bool inheritFFD = ReadBoolean(input);
+					float width = 0, height = 0;
+					if (nonessential) {
+						width = ReadFloat(input);
+						height = ReadFloat(input);
+					}
+
 					if (path == null) path = name;
-					WeightedMeshAttachment mesh = attachmentLoader.NewWeightedMeshAttachment(skin, name, path);
+					MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path);
 					if (mesh == null) return null;
 					mesh.Path = path;
-					float[] uvs = ReadFloatArray(input, 1);
+					mesh.r = ((color & 0xff000000) >> 24) / 255f;
+					mesh.g = ((color & 0x00ff0000) >> 16) / 255f;
+					mesh.b = ((color & 0x0000ff00) >> 8) / 255f;
+					mesh.a = ((color & 0x000000ff)) / 255f;
+					mesh.inheritFFD = inheritFFD;
+					if (nonessential) {
+						mesh.Width = width * scale;
+						mesh.Height = height * scale;
+					}
+					linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent));
+					return mesh;
+				}
+			case AttachmentType.weightedmesh: {
+					String path = ReadString(input);
+					int color = ReadInt(input);
+					int vertexCount = ReadVarint(input, true);		
+					float[] uvs = ReadFloatArray(input, vertexCount * 2, 1);
 					int[] triangles = ReadShortArray(input);
-
-					int vertexCount = ReadInt(input, true);
 					var weights = new List<float>(uvs.Length * 3 * 3);
 					var bones = new List<int>(uvs.Length * 3);
 					for (int i = 0; i < vertexCount; i++) {
 						int boneCount = (int)ReadFloat(input);
 						bones.Add(boneCount);
-						for (int nn = i + boneCount * 4; i < nn; i += 4) {
+						for (int ii = 0; i < boneCount; ii++) {
 							bones.Add((int)ReadFloat(input));
 							weights.Add(ReadFloat(input) * scale);
 							weights.Add(ReadFloat(input) * scale);
 							weights.Add(ReadFloat(input));
 						}
 					}
+					int hullLength = ReadVarint(input, true);
+					int[] edges = null;
+					float width = 0, height = 0;
+					if (nonessential) {
+						edges = ReadShortArray(input);
+						width = ReadFloat(input);
+						height = ReadFloat(input);
+					}
+
+					if (path == null) path = name;
+					WeightedMeshAttachment mesh = attachmentLoader.NewWeightedMeshAttachment(skin, name, path);
+					if (mesh == null) return null;
+					mesh.Path = path;
+					mesh.r = ((color & 0xff000000) >> 24) / 255f;
+					mesh.g = ((color & 0x00ff0000) >> 16) / 255f;
+					mesh.b = ((color & 0x0000ff00) >> 8) / 255f;
+					mesh.a = ((color & 0x000000ff)) / 255f;
 					mesh.bones = bones.ToArray();
 					mesh.weights = weights.ToArray();
 					mesh.triangles = triangles;
 					mesh.regionUVs = uvs;
 					mesh.UpdateUVs();
+					mesh.HullLength = hullLength * 2;
+					if (nonessential) {
+						mesh.Edges = edges;
+						mesh.Width = width * scale;
+						mesh.Height = height * scale;
+					}
+					//
+					return mesh;
+				}
+			case AttachmentType.weightedlinkedmesh: {
+					String path = ReadString(input);
 					int color = ReadInt(input);
+					String skinName = ReadString(input);
+					String parent = ReadString(input);
+					bool inheritFFD = ReadBoolean(input);
+					float width = 0, height = 0;
+					if (nonessential) {
+						width = ReadFloat(input);
+						height = ReadFloat(input);
+					}
+
+					if (path == null) path = name;
+					WeightedMeshAttachment mesh = attachmentLoader.NewWeightedMeshAttachment(skin, name, path);
+					if (mesh == null) return null;
+					mesh.Path = path;
 					mesh.r = ((color & 0xff000000) >> 24) / 255f;
 					mesh.g = ((color & 0x00ff0000) >> 16) / 255f;
 					mesh.b = ((color & 0x0000ff00) >> 8) / 255f;
 					mesh.a = ((color & 0x000000ff)) / 255f;
-					mesh.HullLength = ReadInt(input, true) * 2;
+					mesh.inheritFFD = inheritFFD;
 					if (nonessential) {
-						mesh.Edges = ReadIntArray(input);
-						mesh.Width = ReadFloat(input) * scale;
-						mesh.Height = ReadFloat(input) * scale;
+						mesh.Width = width * scale;
+						mesh.Height = height * scale;
 					}
+					linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent));
 					return mesh;
 				}
 			}
 			return null;
 		}
 
-		private float[] ReadFloatArray (Stream input, float scale) {
-			int n = ReadInt(input, true);
+		private float[] ReadFloatArray (Stream input, int n, float scale) {
 			float[] array = new float[n];
 			if (scale == 1) {
 				for (int i = 0; i < n; i++)
@@ -334,18 +441,10 @@ namespace Spine {
 		}
 
 		private int[] ReadShortArray (Stream input) {
-			int n = ReadInt(input, true);
-			int[] array = new int[n];
-			for (int i = 0; i < n; i++)
-				array[i] = (input.ReadByte() << 8) + input.ReadByte();
-			return array;
-		}
-
-		private int[] ReadIntArray (Stream input) {
-			int n = ReadInt(input, true);
+			int n = ReadVarint(input, true);
 			int[] array = new int[n];
-			for (int i = 0; i < n; i++)
-				array[i] = ReadInt(input, true);
+			for (int i = 0; i < n; i++) 
+				array[i] = (input.ReadByte() << 8) | input.ReadByte();
 			return array;
 		}
 
@@ -355,11 +454,11 @@ namespace Spine {
 			float duration = 0;
 
 			// Slot timelines.
-			for (int i = 0, n = ReadInt(input, true); i < n; i++) {
-				int slotIndex = ReadInt(input, true);
-				for (int ii = 0, nn = ReadInt(input, true); ii < nn; ii++) {
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
+				int slotIndex = ReadVarint(input, true);
+				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) {
 					int timelineType = input.ReadByte();
-					int frameCount = ReadInt(input, true);
+					int frameCount = ReadVarint(input, true);
 					switch (timelineType) {
 					case TIMELINE_COLOR: {
 							ColorTimeline timeline = new ColorTimeline(frameCount);
@@ -392,11 +491,11 @@ namespace Spine {
 			}
 
 			// Bone timelines.
-			for (int i = 0, n = ReadInt(input, true); i < n; i++) {
-				int boneIndex = ReadInt(input, true);
-				for (int ii = 0, nn = ReadInt(input, true); ii < nn; ii++) {
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
+				int boneIndex = ReadVarint(input, true);
+				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) {
 					int timelineType = input.ReadByte();
-					int frameCount = ReadInt(input, true);
+					int frameCount = ReadVarint(input, true);
 					switch (timelineType) {
 					case TIMELINE_ROTATE: {
 							RotateTimeline timeline = new RotateTimeline(frameCount);
@@ -434,9 +533,9 @@ namespace Spine {
 			}
 
 			// IK timelines.
-			for (int i = 0, n = ReadInt(input, true); i < n; i++) {
-				IkConstraintData ikConstraint = skeletonData.ikConstraints.Items[ReadInt(input, true)];
-				int frameCount = ReadInt(input, true);
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
+				IkConstraintData ikConstraint = skeletonData.ikConstraints.Items[ReadVarint(input, true)];
+				int frameCount = ReadVarint(input, true);
 				IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount);
 				timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(ikConstraint);
 				for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
@@ -448,13 +547,13 @@ namespace Spine {
 			}
 
 			// FFD timelines.
-			for (int i = 0, n = ReadInt(input, true); i < n; i++) {
-				Skin skin = skeletonData.skins.Items[ReadInt(input, true)];
-				for (int ii = 0, nn = ReadInt(input, true); ii < nn; ii++) {
-					int slotIndex = ReadInt(input, true);
-					for (int iii = 0, nnn = ReadInt(input, true); iii < nnn; iii++) {
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
+				Skin skin = skeletonData.skins.Items[ReadVarint(input, true)];
+				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) {
+					int slotIndex = ReadVarint(input, true);
+					for (int iii = 0, nnn = ReadVarint(input, true); iii < nnn; iii++) {
 						Attachment attachment = skin.GetAttachment(slotIndex, ReadString(input));
-						int frameCount = ReadInt(input, true);
+						int frameCount = ReadVarint(input, true);
 						FFDTimeline timeline = new FFDTimeline(frameCount);
 						timeline.slotIndex = slotIndex;
 						timeline.attachment = attachment;
@@ -468,7 +567,7 @@ namespace Spine {
 							else
 								vertexCount = ((WeightedMeshAttachment)attachment).weights.Length / 3 * 2;
 
-							int end = ReadInt(input, true);
+							int end = ReadVarint(input, true);
 							if (end == 0) {
 								if (attachment is MeshAttachment)
 									vertices = ((MeshAttachment)attachment).vertices;
@@ -476,7 +575,7 @@ namespace Spine {
 									vertices = new float[vertexCount];
 							} else {
 								vertices = new float[vertexCount];
-								int start = ReadInt(input, true);
+								int start = ReadVarint(input, true);
 								end += start;
 								if (scale == 1) {
 									for (int v = start; v < end; v++)
@@ -502,24 +601,24 @@ namespace Spine {
 			}
 
 			// Draw order timeline.
-			int drawOrderCount = ReadInt(input, true);
+			int drawOrderCount = ReadVarint(input, true);
 			if (drawOrderCount > 0) {
 				DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount);
 				int slotCount = skeletonData.slots.Count;
 				for (int i = 0; i < drawOrderCount; i++) {
-					int offsetCount = ReadInt(input, true);
+					int offsetCount = ReadVarint(input, true);
 					int[] drawOrder = new int[slotCount];
 					for (int ii = slotCount - 1; ii >= 0; ii--)
 						drawOrder[ii] = -1;
 					int[] unchanged = new int[slotCount - offsetCount];
 					int originalIndex = 0, unchangedIndex = 0;
 					for (int ii = 0; ii < offsetCount; ii++) {
-						int slotIndex = ReadInt(input, true);
+						int slotIndex = ReadVarint(input, true);
 						// Collect unchanged items.
 						while (originalIndex != slotIndex)
 							unchanged[unchangedIndex++] = originalIndex++;
 						// Set changed items.
-						drawOrder[originalIndex + ReadInt(input, true)] = originalIndex++;
+						drawOrder[originalIndex + ReadVarint(input, true)] = originalIndex++;
 					}
 					// Collect remaining unchanged items.
 					while (originalIndex < slotCount)
@@ -534,14 +633,14 @@ namespace Spine {
 			}
 
 			// Event timeline.
-			int eventCount = ReadInt(input, true);
+			int eventCount = ReadVarint(input, true);
 			if (eventCount > 0) {
 				EventTimeline timeline = new EventTimeline(eventCount);
 				for (int i = 0; i < eventCount; i++) {
 					float time = ReadFloat(input);
-					EventData eventData = skeletonData.events.Items[ReadInt(input, true)];
+					EventData eventData = skeletonData.events.Items[ReadVarint(input, true)];
 					Event e = new Event(time, eventData);
-					e.Int = ReadInt(input, false);
+					e.Int = ReadVarint(input, false);
 					e.Float = ReadFloat(input);
 					e.String = ReadBoolean(input) ? ReadString(input) : eventData.String;
 					timeline.SetFrame(i, e);
@@ -565,13 +664,13 @@ namespace Spine {
 			}
 		}
 
-		private sbyte ReadSByte (Stream input) {
+		private static sbyte ReadSByte (Stream input) {
 			int value = input.ReadByte();
 			if (value == -1) throw new EndOfStreamException();
 			return (sbyte)value;
 		}
 
-		private bool ReadBoolean (Stream input) {
+		private static bool ReadBoolean (Stream input) {
 			return input.ReadByte() != 0;
 		}
 
@@ -583,11 +682,11 @@ namespace Spine {
 			return BitConverter.ToSingle(buffer, 0);
 		}
 
-		private int ReadInt (Stream input) {
+		private static int ReadInt (Stream input) {
 			return (input.ReadByte() << 24) + (input.ReadByte() << 16) + (input.ReadByte() << 8) + input.ReadByte();
 		}
 
-		private int ReadInt (Stream input, bool optimizePositive) {
+		private static int ReadVarint (Stream input, bool optimizePositive) {
 			int b = input.ReadByte();
 			int result = b & 0x7F;
 			if ((b & 0x80) != 0) {
@@ -600,8 +699,7 @@ namespace Spine {
 						b = input.ReadByte();
 						result |= (b & 0x7F) << 21;
 						if ((b & 0x80) != 0) {
-							b = input.ReadByte();
-							result |= (b & 0x7F) << 28;
+							result |= (input.ReadByte() & 0x7F) << 28;
 						}
 					}
 				}
@@ -610,53 +708,28 @@ namespace Spine {
 		}
 
 		private string ReadString (Stream input) {
-			int charCount = ReadInt(input, true);
-			switch (charCount) {
+			int byteCount = ReadVarint(input, true);
+			switch (byteCount) {
 			case 0:
 				return null;
 			case 1:
 				return "";
 			}
-			charCount--;
-			char[] chars = this.chars;
-			if (chars.Length < charCount) this.chars = chars = new char[charCount];
-			// Try to read 7 bit ASCII chars.
-			int charIndex = 0;
-			int b = 0;
-			while (charIndex < charCount) {
-				b = input.ReadByte();
-				if (b > 127) break;
-				chars[charIndex++] = (char)b;
-			}
-			// If a char was not ASCII, finish with slow path.
-			if (charIndex < charCount) ReadUtf8_slow(input, charCount, charIndex, b);
-			return new String(chars, 0, charCount);
+			byteCount--;
+			byte[] bytes = this.bytes;
+			if (bytes.Length < byteCount) bytes = new byte[byteCount];
+			ReadFully(input, bytes, 0, byteCount);
+			return System.Text.Encoding.UTF8.GetString(bytes, 0, byteCount);
 		}
 
-		private void ReadUtf8_slow (Stream input, int charCount, int charIndex, int b) {
-			char[] chars = this.chars;
-			while (true) {
-				switch (b >> 4) {
-				case 0:
-				case 1:
-				case 2:
-				case 3:
-				case 4:
-				case 5:
-				case 6:
-				case 7:
-					chars[charIndex] = (char)b;
-					break;
-				case 12:
-				case 13:
-					chars[charIndex] = (char)((b & 0x1F) << 6 | input.ReadByte() & 0x3F);
-					break;
-				case 14:
-					chars[charIndex] = (char)((b & 0x0F) << 12 | (input.ReadByte() & 0x3F) << 6 | input.ReadByte() & 0x3F);
-					break;
+		private static void ReadFully (Stream input, byte[] b, int off, int len) {
+			while (len > 0) {
+				int count = input.Read(b, off, len);
+				if (count <= 0) {
+					throw new EndOfStreamException();
 				}
-				if (++charIndex >= charCount) break;
-				b = input.ReadByte() & 0xFF;
+				off += count;
+				len -= count;
 			}
 		}
 	}

+ 83 - 33
spine-csharp/src/SkeletonJson.cs

@@ -46,6 +46,7 @@ namespace Spine {
 	public class SkeletonJson {
 		private AttachmentLoader attachmentLoader;
 		public float Scale { get; set; }
+		private List<LinkedMesh> linkedMeshes = new List<LinkedMesh>();
 
 		public SkeletonJson (params Atlas[] atlasArray)
 			: this(new AtlasAttachmentLoader(atlasArray)) {
@@ -208,7 +209,7 @@ namespace Spine {
 					foreach (KeyValuePair<String, Object> slotEntry in (Dictionary<String, Object>)entry.Value) {
 						int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key);
 						foreach (KeyValuePair<String, Object> attachmentEntry in ((Dictionary<String, Object>)slotEntry.Value)) {
-							Attachment attachment = ReadAttachment(skin, attachmentEntry.Key, (Dictionary<String, Object>)attachmentEntry.Value);
+							Attachment attachment = ReadAttachment(skin, slotIndex, attachmentEntry.Key, (Dictionary<String, Object>)attachmentEntry.Value);
 							if (attachment != null) skin.AddAttachment(slotIndex, attachmentEntry.Key, attachment);
 						}
 					}
@@ -218,6 +219,25 @@ namespace Spine {
 				}
 			}
 
+			// Linked meshes.
+			for (int i = 0, n = linkedMeshes.Count; i < n; i++) {
+				LinkedMesh linkedMesh = linkedMeshes[i];
+				Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin);
+				if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin);
+				Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent);
+				if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent);
+				if (linkedMesh.mesh is MeshAttachment) {
+					MeshAttachment mesh = (MeshAttachment)linkedMesh.mesh;
+					mesh.ParentMesh = (MeshAttachment)parent;
+					mesh.UpdateUVs();
+				} else {
+					WeightedMeshAttachment mesh = (WeightedMeshAttachment)linkedMesh.mesh;
+					mesh.ParentMesh = (WeightedMeshAttachment)parent;
+					mesh.UpdateUVs();
+				}
+			}
+			linkedMeshes.Clear();
+
 			// Events.
 			if (root.ContainsKey("events")) {
 				foreach (KeyValuePair<String, Object> entry in (Dictionary<String, Object>)root["events"]) {
@@ -245,7 +265,7 @@ namespace Spine {
 			return skeletonData;
 		}
 
-		private Attachment ReadAttachment (Skin skin, String name, Dictionary<String, Object> map) {
+		private Attachment ReadAttachment (Skin skin, int slotIndex, String name, Dictionary<String, Object> map) {
 			if (map.ContainsKey("name"))
 				name = (String)map["name"];
 
@@ -285,15 +305,11 @@ namespace Spine {
 				}
 
 				return region;
-			case AttachmentType.mesh: {
+			case AttachmentType.mesh:
+			case AttachmentType.linkedmesh: {
 					MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path);
 					if (mesh == null) return null;
-
 					mesh.Path = path;
-					mesh.vertices = GetFloatArray(map, "vertices", scale);
-					mesh.triangles = GetIntArray(map, "triangles");
-					mesh.regionUVs = GetFloatArray(map, "uvs", 1);
-					mesh.UpdateUVs();
 
 					if (map.ContainsKey("color")) {
 						var color = (String)map["color"];
@@ -303,38 +319,31 @@ namespace Spine {
 						mesh.a = ToColor(color, 3);
 					}
 
-					mesh.HullLength = GetInt(map, "hull", 0) * 2;
-					if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges");
 					mesh.Width = GetInt(map, "width", 0) * scale;
 					mesh.Height = GetInt(map, "height", 0) * scale;
 
+					String parent = GetString(map, "parent", null);
+					if (parent == null) {
+						mesh.vertices = GetFloatArray(map, "vertices", scale);
+						mesh.triangles = GetIntArray(map, "triangles");
+						mesh.regionUVs = GetFloatArray(map, "uvs", 1);
+						mesh.UpdateUVs();
+
+						mesh.HullLength = GetInt(map, "hull", 0) * 2;
+						if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges");
+					} else {
+						mesh.InheritFFD = GetBoolean(map, "ffd", true);
+						linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent));
+					}
+
 					return mesh;
 				}
-			case AttachmentType.weightedmesh: {
+			case AttachmentType.weightedmesh:
+			case AttachmentType.weightedlinkedmesh: {
 					WeightedMeshAttachment mesh = attachmentLoader.NewWeightedMeshAttachment(skin, name, path);
 					if (mesh == null) return null;
 
 					mesh.Path = path;
-					float[] uvs = GetFloatArray(map, "uvs", 1);
-					float[] vertices = GetFloatArray(map, "vertices", 1);
-					var weights = new List<float>(uvs.Length * 3 * 3);
-					var bones = new List<int>(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.bones = bones.ToArray();
-					mesh.weights = weights.ToArray();
-					mesh.triangles = GetIntArray(map, "triangles");
-					mesh.regionUVs = uvs;
-					mesh.UpdateUVs();
 
 					if (map.ContainsKey("color")) {
 						var color = (String)map["color"];
@@ -344,11 +353,39 @@ namespace Spine {
 						mesh.a = ToColor(color, 3);
 					}
 
-					mesh.HullLength = GetInt(map, "hull", 0) * 2;
-					if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges");
 					mesh.Width = GetInt(map, "width", 0) * scale;
 					mesh.Height = GetInt(map, "height", 0) * scale;
 
+					String parent = GetString(map, "parent", null);
+					if (parent == null) {
+						float[] uvs = GetFloatArray(map, "uvs", 1);
+						float[] vertices = GetFloatArray(map, "vertices", 1);
+						var weights = new List<float>(uvs.Length * 3 * 3);
+						var bones = new List<int>(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.bones = bones.ToArray();
+						mesh.weights = weights.ToArray();
+						mesh.triangles = GetIntArray(map, "triangles");
+						mesh.regionUVs = uvs;
+						mesh.UpdateUVs();
+
+						mesh.HullLength = GetInt(map, "hull", 0) * 2;
+						if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges");
+					} else {
+						mesh.InheritFFD = GetBoolean(map, "ffd", true);
+						linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent));
+					}
+
 					return mesh;
 				}
 			case AttachmentType.boundingbox:
@@ -657,5 +694,18 @@ namespace Spine {
 				timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]);
 			}
 		}
+
+		internal class LinkedMesh {
+			internal String parent, skin;
+			internal int slotIndex;
+			internal Attachment mesh;
+
+			public LinkedMesh (Attachment mesh, String skin, int slotIndex, String parent) {
+				this.mesh = mesh;
+				this.skin = skin;
+				this.slotIndex = slotIndex;
+				this.parent = parent;
+			}
+		}
 	}
 }