Browse Source

spine-c updated to v3.1.

NathanSweet 9 years ago
parent
commit
421789e6f7

+ 1 - 1
spine-c/README.md

@@ -12,7 +12,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f
 
 ## Spine version
 
-spine-c works with data exported from the latest version of Spine, except linked meshes are [not yet supported](https://trello.com/c/bERJAFEq/73-update-runtimes-to-support-v3-1-linked-meshes).
+spine-c works with data exported from the latest version of Spine.
 
 spine-c supports all Spine features.
 

+ 8 - 1
spine-c/include/spine/Attachment.h

@@ -39,7 +39,12 @@ extern "C" {
 struct spAttachmentLoader;
 
 typedef enum {
-	SP_ATTACHMENT_REGION, SP_ATTACHMENT_BOUNDING_BOX, SP_ATTACHMENT_MESH, SP_ATTACHMENT_WEIGHTED_MESH
+	SP_ATTACHMENT_REGION,
+	SP_ATTACHMENT_BOUNDING_BOX,
+	SP_ATTACHMENT_MESH,
+	SP_ATTACHMENT_WEIGHTED_MESH,
+	SP_ATTACHMENT_LINKED_MESH,
+	SP_ATTACHMENT_WEIGHTED_LINKED_MESH
 } spAttachmentType;
 
 typedef struct spAttachment {
@@ -65,6 +70,8 @@ typedef spAttachmentType AttachmentType;
 #define ATTACHMENT_BOUNDING_BOX SP_ATTACHMENT_BOUNDING_BOX
 #define ATTACHMENT_MESH SP_ATTACHMENT_MESH
 #define ATTACHMENT_WEIGHTED_MESH SP_ATTACHMENT_WEIGHTED_MESH
+#define ATTACHMENT_LINKED_MESH SP_ATTACHMENT_LINKED_MESH
+#define ATTACHMENT_WEIGHTED_LINKED_MESH SP_ATTACHMENT_WEIGHTED_LINKED_MESH
 typedef spAttachment Attachment;
 #define Attachment_dispose(...) spAttachment_dispose(__VA_ARGS__)
 #endif

+ 8 - 2
spine-c/include/spine/MeshAttachment.h

@@ -40,7 +40,8 @@
 extern "C" {
 #endif
 
-typedef struct spMeshAttachment {
+typedef struct spMeshAttachment spMeshAttachment;
+struct spMeshAttachment {
 	spAttachment super;
 	const char* path;
 
@@ -54,6 +55,9 @@ typedef struct spMeshAttachment {
 	int trianglesCount;
 	unsigned short* triangles;
 
+	spMeshAttachment* const parentMesh;
+	int/*bool*/inheritFFD;
+
 	float r, g, b, a;
 
 	void* rendererObject;
@@ -67,17 +71,19 @@ typedef struct spMeshAttachment {
 	int edgesCount;
 	int* edges;
 	float width, height;
-} spMeshAttachment;
+};
 
 spMeshAttachment* spMeshAttachment_create (const char* name);
 void spMeshAttachment_updateUVs (spMeshAttachment* self);
 void spMeshAttachment_computeWorldVertices (spMeshAttachment* self, spSlot* slot, float* worldVertices);
+void spMeshAttachment_setParentMesh (spMeshAttachment* self, spMeshAttachment* parentMesh);
 
 #ifdef SPINE_SHORT_NAMES
 typedef spMeshAttachment MeshAttachment;
 #define MeshAttachment_create(...) spMeshAttachment_create(__VA_ARGS__)
 #define MeshAttachment_updateUVs(...) spMeshAttachment_updateUVs(__VA_ARGS__)
 #define MeshAttachment_computeWorldVertices(...) spMeshAttachment_computeWorldVertices(__VA_ARGS__)
+#define MeshAttachment_setParentMesh(...) spMeshAttachment_setParentMesh(__VA_ARGS__)
 #endif
 
 #ifdef __cplusplus

+ 8 - 2
spine-c/include/spine/WeightedMeshAttachment.h

@@ -39,7 +39,8 @@
 extern "C" {
 #endif
 
-typedef struct spWeightedMeshAttachment {
+typedef struct spWeightedMeshAttachment spWeightedMeshAttachment;
+struct spWeightedMeshAttachment {
 	spAttachment super;
 	const char* path;
 
@@ -57,6 +58,9 @@ typedef struct spWeightedMeshAttachment {
 	float* uvs;
 	int hullLength;
 
+	spWeightedMeshAttachment* const parentMesh;
+	int/*bool*/inheritFFD;
+
 	float r, g, b, a;
 
 	void* rendererObject;
@@ -70,17 +74,19 @@ typedef struct spWeightedMeshAttachment {
 	int edgesCount;
 	int* edges;
 	float width, height;
-} spWeightedMeshAttachment;
+};
 
 spWeightedMeshAttachment* spWeightedMeshAttachment_create (const char* name);
 void spWeightedMeshAttachment_updateUVs (spWeightedMeshAttachment* self);
 void spWeightedMeshAttachment_computeWorldVertices (spWeightedMeshAttachment* self, spSlot* slot, float* worldVertices);
+void spWeightedMeshAttachment_setParentMesh (spWeightedMeshAttachment* self, spWeightedMeshAttachment* parentMesh);
 
 #ifdef SPINE_SHORT_NAMES
 typedef spWeightedMeshAttachment WeightedMeshAttachment;
 #define WeightedMeshAttachment_create(...) spWeightedMeshAttachment_create(__VA_ARGS__)
 #define WeightedMeshAttachment_updateUVs(...) spWeightedMeshAttachment_updateUVs(__VA_ARGS__)
 #define WeightedMeshAttachment_computeWorldVertices(...) spWeightedMeshAttachment_computeWorldVertices(__VA_ARGS__)
+#define WeightedMeshAttachment_setParentMesh(...) spWeightedMeshAttachment_setParentMesh(__VA_ARGS__)
 #endif
 
 #ifdef __cplusplus

+ 18 - 1
spine-c/src/spine/Animation.c

@@ -683,7 +683,24 @@ void _spFFDTimeline_apply (const spTimeline* timeline, spSkeleton* skeleton, flo
 	spFFDTimeline* self = (spFFDTimeline*)timeline;
 
 	spSlot *slot = skeleton->slots[self->slotIndex];
-	if (slot->attachment != self->attachment) return;
+
+	if (slot->attachment != self->attachment) {
+		if (!slot->attachment) return;
+		switch (slot->attachment->type) {
+		case SP_ATTACHMENT_MESH: {
+			spMeshAttachment* mesh = SUB_CAST(spMeshAttachment, slot->attachment);
+			if (!mesh->inheritFFD || mesh->parentMesh != (void*)self->attachment) return;
+			break;
+		}
+		case SP_ATTACHMENT_WEIGHTED_MESH: {
+			spWeightedMeshAttachment* mesh = SUB_CAST(spWeightedMeshAttachment, slot->attachment);
+			if (!mesh->inheritFFD || mesh->parentMesh != (void*)self->attachment) return;
+			break;
+		}
+		default:
+			return;
+		}
+	}
 
 	if (time < self->frames[0]) return; /* Time is before first frame. */
 

+ 4 - 2
spine-c/src/spine/AtlasAttachmentLoader.c

@@ -54,7 +54,8 @@ spAttachment* _spAtlasAttachmentLoader_createAttachment (spAttachmentLoader* loa
 		attachment->regionOriginalHeight = region->originalHeight;
 		return SUPER(attachment);
 	}
-	case SP_ATTACHMENT_MESH: {
+	case SP_ATTACHMENT_MESH:
+	case SP_ATTACHMENT_LINKED_MESH: {
 		spMeshAttachment* attachment;
 		spAtlasRegion* region = spAtlas_findRegion(self->atlas, path);
 		if (!region) {
@@ -76,7 +77,8 @@ spAttachment* _spAtlasAttachmentLoader_createAttachment (spAttachmentLoader* loa
 		attachment->regionOriginalHeight = region->originalHeight;
 		return SUPER(attachment);
 	}
-	case SP_ATTACHMENT_WEIGHTED_MESH: {
+	case SP_ATTACHMENT_WEIGHTED_MESH:
+	case SP_ATTACHMENT_WEIGHTED_LINKED_MESH: {
 		spWeightedMeshAttachment* attachment;
 		spAtlasRegion* region = spAtlas_findRegion(self->atlas, path);
 		if (!region) {

+ 67 - 73
spine-c/src/spine/Bone.c

@@ -108,75 +108,74 @@ void spBone_updateWorldTransformWith (spBone* self, float x, float y, float rota
 		CONST_CAST(float, self->b) = pa * lb + pb * ld;
 		CONST_CAST(float, self->c) = pc * la + pd * lc;
 		CONST_CAST(float, self->d) = pc * lb + pd * ld;
-	} else if (self->data->inheritRotation) { /* No scale inheritance. */
-		pa = 1;
-		pb = 0;
-		pc = 0;
-		pd = 1;
-		do {
-			cosine = COS(parent->appliedRotation * DEG_RAD);
-			sine = SIN(parent->appliedRotation * DEG_RAD);
-			temp = pa * cosine + pb * sine;
-			pb = pa * -sine + pb * cosine;
-			pa = temp;
-			temp = pc * cosine + pd * sine;
-			pd = pc * -sine + pd * cosine;
-			pc = temp;
-
-			if (!parent->data->inheritRotation) break;
-			parent = parent->parent;
-		} while (parent);
-		CONST_CAST(float, self->a) = pa * la + pb * lc;
-		CONST_CAST(float, self->b) = pa * lb + pb * ld;
-		CONST_CAST(float, self->c) = pc * la + pd * lc;
-		CONST_CAST(float, self->d) = pc * lb + pd * ld;
-		if (self->skeleton->flipX) {
-			CONST_CAST(float, self->a) = -self->a;
-			CONST_CAST(float, self->b) = -self->b;
-		}
-		if (self->skeleton->flipY != yDown) {
-			CONST_CAST(float, self->c) = -self->c;
-			CONST_CAST(float, self->d) = -self->d;
+	} else {
+		if (self->data->inheritRotation) { /* No scale inheritance. */
+			pa = 1;
+			pb = 0;
+			pc = 0;
+			pd = 1;
+			do {
+				cosine = COS(parent->appliedRotation * DEG_RAD);
+				sine = SIN(parent->appliedRotation * DEG_RAD);
+				temp = pa * cosine + pb * sine;
+				pb = pa * -sine + pb * cosine;
+				pa = temp;
+				temp = pc * cosine + pd * sine;
+				pd = pc * -sine + pd * cosine;
+				pc = temp;
+
+				if (!parent->data->inheritRotation) break;
+				parent = parent->parent;
+			} while (parent);
+			CONST_CAST(float, self->a) = pa * la + pb * lc;
+			CONST_CAST(float, self->b) = pa * lb + pb * ld;
+			CONST_CAST(float, self->c) = pc * la + pd * lc;
+			CONST_CAST(float, self->d) = pc * lb + pd * ld;
+		} else if (self->data->inheritScale) { /* No rotation inheritance. */
+			pa = 1;
+			pb = 0;
+			pc = 0;
+			pd = 1;
+			do {
+				float za, zb, zc, zd;
+				float r = parent->rotation;
+				float psx = parent->appliedScaleX, psy = parent->appliedScaleY;
+				cosine = COS(r * DEG_RAD);
+				sine = SIN(r * DEG_RAD);
+				za = cosine * psx;
+				zb = -sine * psy;
+				zc = sine * psx;
+				zd = cosine * psy;
+				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;
+				cosine = COS(-r * DEG_RAD);
+				sine = SIN(-r * DEG_RAD);
+				temp = pa * cosine + pb * sine;
+				pb = pa * -sine + pb * cosine;
+				pa = temp;
+				temp = pc * cosine + pd * sine;
+				pd = pc * -sine + pd * cosine;
+				pc = temp;
+
+				if (!parent->data->inheritScale) break;
+				parent = parent->parent;
+			} while (parent);
+			CONST_CAST(float, self->a) = pa * la + pb * lc;
+			CONST_CAST(float, self->b) = pa * lb + pb * ld;
+			CONST_CAST(float, self->c) = pc * la + pd * lc;
+			CONST_CAST(float, self->d) = pc * lb + pd * ld;
+		} else {
+			CONST_CAST(float, self->a) = la;
+			CONST_CAST(float, self->b) = lb;
+			CONST_CAST(float, self->c) = lc;
+			CONST_CAST(float, self->d) = ld;
 		}
-	} else if (self->data->inheritScale) { /* No rotation inheritance. */
-		pa = 1;
-		pb = 0;
-		pc = 0;
-		pd = 1;
-		do {
-			float za, zb, zc, zd;
-			float r = parent->rotation;
-			float psx = parent->appliedScaleX, psy = parent->appliedScaleY;
-			cosine = COS(r * DEG_RAD);
-			sine = SIN(r * DEG_RAD);
-			za = cosine * psx;
-			zb = -sine * psy;
-			zc = sine * psx;
-			zd = cosine * psy;
-			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;
-			cosine = COS(-r * DEG_RAD);
-			sine = SIN(-r * DEG_RAD);
-			temp = pa * cosine + pb * sine;
-			pb = pa * -sine + pb * cosine;
-			pa = temp;
-			temp = pc * cosine + pd * sine;
-			pd = pc * -sine + pd * cosine;
-			pc = temp;
-
-			if (!parent->data->inheritScale) break;
-			parent = parent->parent;
-		} while (parent);
-		CONST_CAST(float, self->a) = pa * la + pb * lc;
-		CONST_CAST(float, self->b) = pa * lb + pb * ld;
-		CONST_CAST(float, self->c) = pc * la + pd * lc;
-		CONST_CAST(float, self->d) = pc * lb + pd * ld;
 		if (self->skeleton->flipX) {
 			CONST_CAST(float, self->a) = -self->a;
 			CONST_CAST(float, self->b) = -self->b;
@@ -185,11 +184,6 @@ void spBone_updateWorldTransformWith (spBone* self, float x, float y, float rota
 			CONST_CAST(float, self->c) = -self->c;
 			CONST_CAST(float, self->d) = -self->d;
 		}
-	} else {
-		CONST_CAST(float, self->a) = la;
-		CONST_CAST(float, self->b) = lb;
-		CONST_CAST(float, self->c) = lc;
-		CONST_CAST(float, self->d) = ld;
 	}
 }
 

+ 28 - 22
spine-c/src/spine/IkConstraint.c

@@ -80,47 +80,54 @@ void spIkConstraint_apply1 (spBone* bone, float targetX, float targetY, float al
 }
 
 void spIkConstraint_apply2 (spBone* parent, spBone* child, float targetX, float targetY, int bendDir, float alpha) {
-	float px = parent->x, py = parent->y, psx = parent->scaleX, psy = parent->scaleY, csx = child->scaleX, cy = child->y;
-	int offset1, offset2, sign2;
+	float px = parent->x, py = parent->y, psx = parent->scaleX, psy = parent->scaleY;
+	float cx = child->x, cy = child->y, csx = child->appliedScaleX, cwx = child->worldX, cwy = child->worldY;
+	int o1, o2, s2, u;
 	spBone* pp = parent->parent;
-	float tx, ty, dx, dy, l1, l2, a1, a2, psd, r;
+	float tx, ty, dx, dy, l1, l2, a1, a2, r;
 	if (alpha == 0) return;
 	if (psx < 0) {
 		psx = -psx;
-		offset1 = 180;
-		sign2 = -1;
+		o1 = 180;
+		s2 = -1;
 	} else {
-		offset1 = 0;
-		sign2 = 1;
+		o1 = 0;
+		s2 = 1;
 	}
 	if (psy < 0) {
 		psy = -psy;
-		sign2 = -sign2;
+		s2 = -s2;
+	}
+	u = psx - psy;
+	u = u < 0 ? -u : u <= 0.0001f;
+	if (!u && cy != 0) {
+		cwx = parent->a * cx + parent->worldX;
+		cwy = parent->c * cx + parent->worldY;
+		cy = 0;
 	}
 	if (csx < 0) {
 		csx = -csx;
-		offset2 = 180;
+		o2 = 180;
 	} else
-		offset2 = 0;
+		o2 = 0;
 	if (!pp) {
 		tx = targetX - px;
 		ty = targetY - py;
-		dx = child->worldX - px;
-		dy = child->worldY - py;
+		dx = cwx - px;
+		dy = cwy - py;
 	} else {
 		float a = pp->a, b = pp->b, c = pp->c, d = pp->d, invDet = 1 / (a * d - b * c);
 		float wx = pp->worldX, wy = pp->worldY, x = targetX - wx, y = targetY - wy;
 		tx = (x * d - y * b) * invDet - px;
 		ty = (y * a - x * c) * invDet - py;
-		x = child->worldX - wx;
-		y = child->worldY - wy;
+		x = cwx - wx;
+		y = cwy - wy;
 		dx = (x * d - y * b) * invDet - px;
 		dy = (y * a - x * c) * invDet - py;
 	}
 	l1 = SQRT(dx * dx + dy * dy);
 	l2 = child->data->length * csx;
-	psd = psx - psy;
-	if (psd < 0 ? -psd : psd <= 0.0001f) {
+	if (u) {
 		float cos, a, o;
 		l2 *= psx;
 		cos = (tx * tx + ty * ty - l1 * l1 - l2 * l2) / (2 * l1 * l2);
@@ -138,7 +145,6 @@ void spIkConstraint_apply2 (spBone* parent, spBone* child, float targetX, float
 		float minAngle = 0, minDist = FLT_MAX, minX = 0, minY = 0;
 		float maxAngle = 0, maxDist = 0, maxX = 0, maxY = 0;
 		float x = l1 + a, dist = x * x, angle, y;
-		cy = 0;
 		if (d >= 0) {
 			float q = SQRT(d), r0, r1, ar0, ar1;;
 			if (c1 < 0) q = -q;
@@ -192,16 +198,16 @@ void spIkConstraint_apply2 (spBone* parent, spBone* child, float targetX, float
 		}
 	}
 	outer: {
-		float offset = ATAN2(cy, child->x) * sign2;
-		a1 = (a1 - offset) * RAD_DEG + offset1;
-		a2 = (a2 + offset) * RAD_DEG * sign2 + offset2;
+		float offset = ATAN2(cy, child->x) * s2;
+		a1 = (a1 - offset) * RAD_DEG + o1;
+		a2 = (a2 + offset) * RAD_DEG * s2 + o2;
 		if (a1 > 180) a1 -= 360;
 		else if (a1 < -180) a1 += 360;
 		if (a2 > 180) a2 -= 360;
 		else if (a2 < -180) a2 += 360;
 		r = parent->rotation;
-		spBone_updateWorldTransformWith(parent, parent->x, parent->y, r + (a1 - r) * alpha, parent->scaleX, parent->scaleY);
+		spBone_updateWorldTransformWith(parent, px, py, r + (a1 - r) * alpha, parent->appliedScaleX, parent->appliedScaleY);
 		r = child->rotation;
-		spBone_updateWorldTransformWith(child, child->x, cy, r + (a2 - r) * alpha, child->scaleX, child->scaleY);
+		spBone_updateWorldTransformWith(child, cx, cy, r + (a2 - r) * alpha, child->appliedScaleX, child->appliedScaleY);
 	}
 }

+ 26 - 4
spine-c/src/spine/MeshAttachment.c

@@ -36,11 +36,13 @@ void _spMeshAttachment_dispose (spAttachment* attachment) {
 	spMeshAttachment* self = SUB_CAST(spMeshAttachment, attachment);
 	_spAttachment_deinit(attachment);
 	FREE(self->path);
-	FREE(self->vertices);
-	FREE(self->regionUVs);
 	FREE(self->uvs);
-	FREE(self->triangles);
-	FREE(self->edges);
+	if (!self->parentMesh) {
+		FREE(self->vertices);
+		FREE(self->regionUVs);
+		FREE(self->triangles);
+		FREE(self->edges);
+	}
 	FREE(self);
 }
 
@@ -84,3 +86,23 @@ void spMeshAttachment_computeWorldVertices (spMeshAttachment* self, spSlot* slot
 		worldVertices[i + 1] = vx * bone->c + vy * bone->d + y;
 	}
 }
+
+void spMeshAttachment_setParentMesh (spMeshAttachment* self, spMeshAttachment* parentMesh) {
+	CONST_CAST(spMeshAttachment*, self->parentMesh) = parentMesh;
+	if (parentMesh) {
+		self->vertices = parentMesh->vertices;
+		self->regionUVs = parentMesh->regionUVs;
+		self->verticesCount = parentMesh->verticesCount;
+
+		self->triangles = parentMesh->triangles;
+		self->trianglesCount = parentMesh->trianglesCount;
+
+		self->hullLength = parentMesh->hullLength;
+
+		self->edges = parentMesh->edges;
+		self->edgesCount = parentMesh->edgesCount;
+
+		self->width = parentMesh->width;
+		self->height = parentMesh->height;
+	}
+}

+ 26 - 24
spine-c/src/spine/Skeleton.c

@@ -37,12 +37,16 @@ typedef enum {
 	SP_UPDATE_BONE, SP_UPDATE_IK_CONSTRAINT, SP_UPDATE_TRANSFORM_CONSTRAINT
 } _spUpdateType;
 
+typedef struct {
+	_spUpdateType type;
+	void* object;
+} _spUpdate;
+
 typedef struct {
 	spSkeleton super;
 
 	int updateCacheCount;
-	void** updateCache;
-	_spUpdateType* updateCacheType;
+	_spUpdate* updateCache;
 } _spSkeleton;
 
 spSkeleton* spSkeleton_create (spSkeletonData* data) {
@@ -115,7 +119,6 @@ void spSkeleton_dispose (spSkeleton* self) {
 	_spSkeleton* internal = SUB_CAST(_spSkeleton, self);
 
 	FREE(internal->updateCache);
-	FREE(internal->updateCacheType);
 
 	for (i = 0; i < self->bonesCount; ++i)
 		spBone_dispose(self->bones[i]);
@@ -139,24 +142,25 @@ void spSkeleton_dispose (spSkeleton* self) {
 
 void spSkeleton_updateCache (const spSkeleton* self) {
 	int i, ii;
+	_spUpdate* update;
 	_spSkeleton* internal = SUB_CAST(_spSkeleton, self);
+	int capacity = self->bonesCount + self->transformConstraintsCount + self->ikConstraintsCount;
 
 	FREE(internal->updateCache);
-	FREE(internal->updateCacheType);
-	int capacity = self->bonesCount + self->transformConstraintsCount + self->ikConstraintsCount;
-	internal->updateCache = MALLOC(void*, capacity);
-	internal->updateCacheType = MALLOC(_spUpdateType, capacity);
+	internal->updateCache = MALLOC(_spUpdate, capacity);
 	internal->updateCacheCount = 0;
 
 	for (i = 0; i < self->bonesCount; ++i) {
 		spBone* bone = self->bones[i];
-		internal->updateCache[internal->updateCacheCount] = bone;
-		internal->updateCacheType[internal->updateCacheCount++] = SP_UPDATE_BONE;
+		update = internal->updateCache + internal->updateCacheCount++;
+		update->type = SP_UPDATE_BONE;
+		update->object = bone;
 		for (ii = 0; ii < self->ikConstraintsCount; ++ii) {
 			spIkConstraint* ikConstraint = self->ikConstraints[ii];
 			if (bone == ikConstraint->bones[ikConstraint->bonesCount - 1]) {
-				internal->updateCache[internal->updateCacheCount] = ikConstraint;
-				internal->updateCacheType[internal->updateCacheCount++] = SP_UPDATE_IK_CONSTRAINT;
+				update = internal->updateCache + internal->updateCacheCount++;
+				update->type = SP_UPDATE_IK_CONSTRAINT;
+				update->object = ikConstraint;
 				break;
 			}
 		}
@@ -165,17 +169,14 @@ void spSkeleton_updateCache (const spSkeleton* self) {
 	for (i = 0; i < self->transformConstraintsCount; ++i) {
 		spTransformConstraint* transformConstraint = self->transformConstraints[i];
 		for (ii = internal->updateCacheCount - 1; ii >= 0; --ii) {
-			void* updatable = internal->updateCache[ii];
-			if (updatable == transformConstraint->bone || updatable == transformConstraint->target) {
+			void* object = internal->updateCache[ii].object;
+			if (object == transformConstraint->bone || object == transformConstraint->target) {
 				int insertIndex = ii + 1;
-				int moveCount = (capacity-2) - insertIndex;
-				if (moveCount > 0) {
-					memmove(internal->updateCache + (insertIndex + 1), internal->updateCache + insertIndex, moveCount * sizeof(void*));
-					memmove(internal->updateCacheType + (insertIndex + 1), internal->updateCacheType + insertIndex, moveCount * sizeof(_spUpdateType));
-				}
+				update = internal->updateCache + insertIndex;
+				memmove(update + 1, update, (internal->updateCacheCount - insertIndex) * sizeof(_spUpdate));
+				update->type = SP_UPDATE_TRANSFORM_CONSTRAINT;
+				update->object = transformConstraint;
 				internal->updateCacheCount++;
-				internal->updateCache[insertIndex] = transformConstraint;
-				internal->updateCacheType[insertIndex] = SP_UPDATE_TRANSFORM_CONSTRAINT;
 				break;
 			}
 		}
@@ -187,15 +188,16 @@ void spSkeleton_updateWorldTransform (const spSkeleton* self) {
 	_spSkeleton* internal = SUB_CAST(_spSkeleton, self);
 
 	for (i = 0; i < internal->updateCacheCount; ++i) {
-		switch (internal->updateCacheType[i]) {
+		_spUpdate* update = internal->updateCache + i;
+		switch (update->type) {
 		case SP_UPDATE_BONE:
-			spBone_updateWorldTransform((spBone*)internal->updateCache[i]);
+			spBone_updateWorldTransform((spBone*)update->object);
 			break;
 		case SP_UPDATE_IK_CONSTRAINT:
-			spIkConstraint_apply((spIkConstraint*)internal->updateCache[i]);
+			spIkConstraint_apply((spIkConstraint*)update->object);
 			break;
 		case SP_UPDATE_TRANSFORM_CONSTRAINT:
-			spTransformConstraint_apply((spTransformConstraint*)internal->updateCache[i]);
+			spTransformConstraint_apply((spTransformConstraint*)update->object);
 			break;
 		}
 	}

+ 175 - 87
spine-c/src/spine/SkeletonJson.c

@@ -36,9 +36,20 @@
 #include <spine/extension.h>
 #include <spine/AtlasAttachmentLoader.h>
 
+typedef struct {
+	const char* parent;
+	const char* skin;
+	int slotIndex;
+	spAttachment* mesh;
+} _spLinkedMesh;
+
 typedef struct {
 	spSkeletonJson super;
 	int ownsLoader;
+
+	int linkedMeshCount;
+	int linkedMeshCapacity;
+	_spLinkedMesh* linkedMeshes;
 } _spSkeletonJson;
 
 spSkeletonJson* spSkeletonJson_createWithLoader (spAttachmentLoader* attachmentLoader) {
@@ -56,7 +67,9 @@ spSkeletonJson* spSkeletonJson_create (spAtlas* atlas) {
 }
 
 void spSkeletonJson_dispose (spSkeletonJson* self) {
-	if (SUB_CAST(_spSkeletonJson, self)->ownsLoader) spAttachmentLoader_dispose(self->attachmentLoader);
+	_spSkeletonJson* internal = SUB_CAST(_spSkeletonJson, self);
+	if (internal->ownsLoader) spAttachmentLoader_dispose(self->attachmentLoader);
+	FREE(internal->linkedMeshes);
 	FREE(self->error);
 	FREE(self);
 }
@@ -103,6 +116,28 @@ static void readCurve (spCurveTimeline* timeline, int frameIndex, Json* frame) {
 	}
 }
 
+static void _spSkeletonJson_addLinkedMesh (spSkeletonJson* self, spAttachment* mesh, const char* skin, int slotIndex,
+		const char* parent) {
+	_spLinkedMesh* linkedMesh;
+	_spSkeletonJson* internal = SUB_CAST(_spSkeletonJson, self);
+
+	if (internal->linkedMeshCount == internal->linkedMeshCapacity) {
+		_spLinkedMesh* linkedMeshes;
+		internal->linkedMeshCapacity *= 2;
+		if (internal->linkedMeshCapacity < 8) internal->linkedMeshCapacity = 8;
+		linkedMeshes = MALLOC(_spLinkedMesh, internal->linkedMeshCapacity);
+		memcpy(linkedMeshes, internal->linkedMeshes, internal->linkedMeshCount);
+		FREE(internal->linkedMeshes);
+		internal->linkedMeshes = linkedMeshes;
+	}
+
+	linkedMesh = internal->linkedMeshes + internal->linkedMeshCount++;
+	linkedMesh->mesh = mesh;
+	linkedMesh->skin = skin;
+	linkedMesh->slotIndex = slotIndex;
+	linkedMesh->parent = parent;
+}
+
 static spAnimation* _spSkeletonJson_readAnimation (spSkeletonJson* self, Json* root, spSkeletonData *skeletonData) {
 	int i;
 	spAnimation* animation;
@@ -132,7 +167,6 @@ static spAnimation* _spSkeletonJson_readAnimation (spSkeletonJson* self, Json* r
 
 	animation = spAnimation_create(root->name, timelinesCount);
 	animation->timelinesCount = 0;
-	skeletonData->animations[skeletonData->animationsCount++] = animation;
 
 	/* Slot timelines. */
 	for (slotMap = slots ? slots->child : 0; slotMap; slotMap = slotMap->next) {
@@ -409,9 +443,11 @@ spSkeletonData* spSkeletonJson_readSkeletonData (spSkeletonJson* self, const cha
 	spSkeletonData* skeletonData;
 	Json *root, *skeleton, *bones, *boneMap, *ik, *transform, *slots, *skins, *animations, *events;
 	char* oldLocale;
+	_spSkeletonJson* internal = SUB_CAST(_spSkeletonJson, self);
 
 	FREE(self->error);
 	CONST_CAST(char*, self->error) = 0;
+	internal->linkedMeshCount = 0;
 
 	oldLocale = setlocale(LC_NUMERIC, "C");
 	root = Json_create(json);
@@ -584,13 +620,12 @@ spSkeletonData* spSkeletonJson_readSkeletonData (spSkeletonJson* self, const cha
 	skins = Json_getItem(root, "skins");
 	if (skins) {
 		Json *slotMap;
-		skeletonData->skinsCount = skins->size;
 		skeletonData->skins = MALLOC(spSkin*, skins->size);
 		for (slotMap = skins->child, i = 0; slotMap; slotMap = slotMap->next, ++i) {
 			Json *attachmentsMap;
 			spSkin *skin = spSkin_create(slotMap->name);
 
-			skeletonData->skins[i] = skin;
+			skeletonData->skins[skeletonData->skinsCount++] = skin;
 			if (strcmp(slotMap->name, "default") == 0) skeletonData->defaultSkin = skin;
 
 			for (attachmentsMap = slotMap->child; attachmentsMap; attachmentsMap = attachmentsMap->next) {
@@ -614,6 +649,10 @@ spSkeletonData* spSkeletonJson_readSkeletonData (spSkeletonJson* self, const cha
 						type = SP_ATTACHMENT_MESH;
 					else if (strcmp(typeString, "weightedmesh") == 0 || strcmp(typeString, "skinnedmesh") == 0)
 						type = SP_ATTACHMENT_WEIGHTED_MESH;
+					else if (strcmp(typeString, "linkedmesh") == 0)
+						type = SP_ATTACHMENT_LINKED_MESH;
+					else if (strcmp(typeString, "weightedlinkedmesh") == 0)
+						type = SP_ATTACHMENT_WEIGHTED_LINKED_MESH;
 					else if (strcmp(typeString, "boundingbox") == 0)
 						type = SP_ATTACHMENT_BOUNDING_BOX;
 					else {
@@ -655,30 +694,12 @@ spSkeletonData* spSkeletonJson_readSkeletonData (spSkeletonJson* self, const cha
 						spRegionAttachment_updateOffset(region);
 						break;
 					}
-					case SP_ATTACHMENT_MESH: {
+					case SP_ATTACHMENT_MESH:
+					case SP_ATTACHMENT_LINKED_MESH: {
 						spMeshAttachment* mesh = SUB_CAST(spMeshAttachment, attachment);
 
 						MALLOC_STR(mesh->path, path);
 
-						entry = Json_getItem(attachmentMap, "vertices");
-						mesh->verticesCount = entry->size;
-						mesh->vertices = MALLOC(float, entry->size);
-						for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
-							mesh->vertices[i] = entry->valueFloat * self->scale;
-
-						entry = Json_getItem(attachmentMap, "triangles");
-						mesh->trianglesCount = entry->size;
-						mesh->triangles = MALLOC(unsigned short, entry->size);
-						for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
-							mesh->triangles[i] = (unsigned short)entry->valueInt;
-
-						entry = Json_getItem(attachmentMap, "uvs");
-						mesh->regionUVs = MALLOC(float, entry->size);
-						for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
-							mesh->regionUVs[i] = entry->valueFloat;
-
-						spMeshAttachment_updateUVs(mesh);
-
 						color = Json_getString(attachmentMap, "color", 0);
 						if (color) {
 							mesh->r = toColor(color, 0);
@@ -687,68 +708,53 @@ spSkeletonData* spSkeletonJson_readSkeletonData (spSkeletonJson* self, const cha
 							mesh->a = toColor(color, 3);
 						}
 
-						mesh->hullLength = Json_getInt(attachmentMap, "hull", 0);
+						mesh->width = Json_getFloat(attachmentMap, "width", 32) * self->scale;
+						mesh->height = Json_getFloat(attachmentMap, "height", 32) * self->scale;
 
-						entry = Json_getItem(attachmentMap, "edges");
-						if (entry) {
-							mesh->edgesCount = entry->size;
-							mesh->edges = MALLOC(int, entry->size);
+						entry = Json_getItem(attachmentMap, "parent");
+						if (!entry) {
+							entry = Json_getItem(attachmentMap, "vertices");
+							mesh->verticesCount = entry->size;
+							mesh->vertices = MALLOC(float, entry->size);
 							for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
-								mesh->edges[i] = entry->valueInt;
-						}
+								mesh->vertices[i] = entry->valueFloat * self->scale;
 
-						mesh->width = Json_getFloat(attachmentMap, "width", 32) * self->scale;
-						mesh->height = Json_getFloat(attachmentMap, "height", 32) * self->scale;
-						break;
-					}
-					case SP_ATTACHMENT_WEIGHTED_MESH: {
-						spWeightedMeshAttachment* mesh = SUB_CAST(spWeightedMeshAttachment, attachment);
-						int verticesCount, b, w, nn;
-						float* vertices;
+							entry = Json_getItem(attachmentMap, "triangles");
+							mesh->trianglesCount = entry->size;
+							mesh->triangles = MALLOC(unsigned short, entry->size);
+							for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
+								mesh->triangles[i] = (unsigned short)entry->valueInt;
 
-						MALLOC_STR(mesh->path, path);
+							entry = Json_getItem(attachmentMap, "uvs");
+							mesh->regionUVs = MALLOC(float, entry->size);
+							for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
+								mesh->regionUVs[i] = entry->valueFloat;
 
-						entry = Json_getItem(attachmentMap, "uvs");
-						mesh->uvsCount = entry->size;
-						mesh->regionUVs = MALLOC(float, entry->size);
-						for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
-							mesh->regionUVs[i] = entry->valueFloat;
+							spMeshAttachment_updateUVs(mesh);
 
-						entry = Json_getItem(attachmentMap, "vertices");
-						verticesCount = entry->size;
-						vertices = MALLOC(float, entry->size);
-						for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
-							vertices[i] = entry->valueFloat;
+							mesh->hullLength = Json_getInt(attachmentMap, "hull", 0);
 
-						for (i = 0; i < verticesCount;) {
-							int bonesCount = (int)vertices[i];
-							mesh->bonesCount += bonesCount + 1;
-							mesh->weightsCount += bonesCount * 3;
-							i += 1 + bonesCount * 4;
-						}
-						mesh->bones = MALLOC(int, mesh->bonesCount);
-						mesh->weights = MALLOC(float, mesh->weightsCount);
-
-						for (i = 0, b = 0, w = 0; i < verticesCount;) {
-							int bonesCount = (int)vertices[i++];
-							mesh->bones[b++] = bonesCount;
-							for (nn = i + bonesCount * 4; i < nn; i += 4, ++b, w += 3) {
-								mesh->bones[b] = (int)vertices[i];
-								mesh->weights[w] = vertices[i + 1] * self->scale;
-								mesh->weights[w + 1] = vertices[i + 2] * self->scale;
-								mesh->weights[w + 2] = vertices[i + 3];
+							entry = Json_getItem(attachmentMap, "edges");
+							if (entry) {
+								mesh->edgesCount = entry->size;
+								mesh->edges = MALLOC(int, entry->size);
+								for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
+									mesh->edges[i] = entry->valueInt;
 							}
+						} else {
+							mesh->inheritFFD = Json_getInt(attachmentMap, "ffd", 1);
+							_spSkeletonJson_addLinkedMesh(self, attachment, Json_getString(attachmentMap, "skin", 0), slotIndex,
+									entry->valueString);
 						}
+						break;
+					}
+					case SP_ATTACHMENT_WEIGHTED_MESH:
+					case SP_ATTACHMENT_WEIGHTED_LINKED_MESH: {
+						spWeightedMeshAttachment* mesh = SUB_CAST(spWeightedMeshAttachment, attachment);
+						int verticesCount, b, w, nn;
+						float* vertices;
 
-						FREE(vertices);
-
-						entry = Json_getItem(attachmentMap, "triangles");
-						mesh->trianglesCount = entry->size;
-						mesh->triangles = MALLOC(unsigned short, entry->size);
-						for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
-							mesh->triangles[i] = (unsigned short)entry->valueInt;
-
-						spWeightedMeshAttachment_updateUVs(mesh);
+						MALLOC_STR(mesh->path, path);
 
 						color = Json_getString(attachmentMap, "color", 0);
 						if (color) {
@@ -758,18 +764,67 @@ spSkeletonData* spSkeletonJson_readSkeletonData (spSkeletonJson* self, const cha
 							mesh->a = toColor(color, 3);
 						}
 
-						mesh->hullLength = Json_getInt(attachmentMap, "hull", 0);
+						mesh->width = Json_getFloat(attachmentMap, "width", 32) * self->scale;
+						mesh->height = Json_getFloat(attachmentMap, "height", 32) * self->scale;
 
-						entry = Json_getItem(attachmentMap, "edges");
-						if (entry) {
-							mesh->edgesCount = entry->size;
-							mesh->edges = MALLOC(int, entry->size);
+						entry = Json_getItem(attachmentMap, "parent");
+						if (!entry) {
+							entry = Json_getItem(attachmentMap, "uvs");
+							mesh->uvsCount = entry->size;
+							mesh->regionUVs = MALLOC(float, entry->size);
 							for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
-								mesh->edges[i] = entry->valueInt;
-						}
+								mesh->regionUVs[i] = entry->valueFloat;
 
-						mesh->width = Json_getFloat(attachmentMap, "width", 32) * self->scale;
-						mesh->height = Json_getFloat(attachmentMap, "height", 32) * self->scale;
+							entry = Json_getItem(attachmentMap, "vertices");
+							verticesCount = entry->size;
+							vertices = MALLOC(float, entry->size);
+							for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
+								vertices[i] = entry->valueFloat;
+
+							for (i = 0; i < verticesCount;) {
+								int bonesCount = (int)vertices[i];
+								mesh->bonesCount += bonesCount + 1;
+								mesh->weightsCount += bonesCount * 3;
+								i += 1 + bonesCount * 4;
+							}
+							mesh->bones = MALLOC(int, mesh->bonesCount);
+							mesh->weights = MALLOC(float, mesh->weightsCount);
+
+							for (i = 0, b = 0, w = 0; i < verticesCount;) {
+								int bonesCount = (int)vertices[i++];
+								mesh->bones[b++] = bonesCount;
+								for (nn = i + bonesCount * 4; i < nn; i += 4, ++b, w += 3) {
+									mesh->bones[b] = (int)vertices[i];
+									mesh->weights[w] = vertices[i + 1] * self->scale;
+									mesh->weights[w + 1] = vertices[i + 2] * self->scale;
+									mesh->weights[w + 2] = vertices[i + 3];
+								}
+							}
+
+							FREE(vertices);
+
+							entry = Json_getItem(attachmentMap, "triangles");
+							mesh->trianglesCount = entry->size;
+							mesh->triangles = MALLOC(unsigned short, entry->size);
+							for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
+								mesh->triangles[i] = (unsigned short)entry->valueInt;
+
+							spWeightedMeshAttachment_updateUVs(mesh);
+
+							mesh->hullLength = Json_getInt(attachmentMap, "hull", 0);
+
+							entry = Json_getItem(attachmentMap, "edges");
+							if (entry) {
+								mesh->edgesCount = entry->size;
+								mesh->edges = MALLOC(int, entry->size);
+								for (entry = entry->child, i = 0; entry; entry = entry->next, ++i)
+									mesh->edges[i] = entry->valueInt;
+							}
+						} else {
+							mesh->inheritFFD = Json_getInt(attachmentMap, "ffd", 1);
+							_spSkeletonJson_addLinkedMesh(self, attachment, Json_getString(attachmentMap, "skin", 0), slotIndex,
+								entry->valueString);
+						}
 						break;
 					}
 					case SP_ATTACHMENT_BOUNDING_BOX: {
@@ -791,6 +846,33 @@ spSkeletonData* spSkeletonJson_readSkeletonData (spSkeletonJson* self, const cha
 		}
 	}
 
+	/* Linked meshes. */
+	for (i = 0; i < internal->linkedMeshCount; i++) {
+		spAttachment* parent;
+		_spLinkedMesh* linkedMesh = internal->linkedMeshes + i;
+		spSkin* skin = !linkedMesh->skin ? skeletonData->defaultSkin : spSkeletonData_findSkin(skeletonData, linkedMesh->skin);
+		if (!skin) {
+			spSkeletonData_dispose(skeletonData);
+			_spSkeletonJson_setError(self, 0, "Skin not found: ", linkedMesh->skin);
+			return 0;
+		}
+		parent = spSkin_getAttachment(skin, linkedMesh->slotIndex, linkedMesh->parent);
+		if (!skin) {
+			spSkeletonData_dispose(skeletonData);
+			_spSkeletonJson_setError(self, 0, "Parent mesh not found: ", linkedMesh->parent);
+			return 0;
+		}
+		if (linkedMesh->mesh->type == SP_ATTACHMENT_MESH) {
+			spMeshAttachment* mesh = SUB_CAST(spMeshAttachment, linkedMesh->mesh);
+			spMeshAttachment_setParentMesh(mesh, SUB_CAST(spMeshAttachment, parent));
+			spMeshAttachment_updateUVs(mesh);
+		} else {
+			spWeightedMeshAttachment* mesh = SUB_CAST(spWeightedMeshAttachment, linkedMesh->mesh);
+			spWeightedMeshAttachment_setParentMesh(mesh, SUB_CAST(spWeightedMeshAttachment, parent));
+			spWeightedMeshAttachment_updateUVs(mesh);
+		}
+	}
+
 	/* Events. */
 	events = Json_getItem(root, "events");
 	if (events) {
@@ -813,8 +895,14 @@ spSkeletonData* spSkeletonJson_readSkeletonData (spSkeletonJson* self, const cha
 	if (animations) {
 		Json *animationMap;
 		skeletonData->animations = MALLOC(spAnimation*, animations->size);
-		for (animationMap = animations->child; animationMap; animationMap = animationMap->next)
-			_spSkeletonJson_readAnimation(self, animationMap, skeletonData);
+		for (animationMap = animations->child; animationMap; animationMap = animationMap->next) {
+			spAnimation* animation = _spSkeletonJson_readAnimation(self, animationMap, skeletonData);
+			if (!animation) {
+				spSkeletonData_dispose(skeletonData);
+				return 0;
+			}
+			skeletonData->animations[skeletonData->animationsCount++] = animation;
+		}
 	}
 
 	Json_dispose(root);

+ 32 - 5
spine-c/src/spine/WeightedMeshAttachment.c

@@ -36,12 +36,14 @@ void _spWeightedMeshAttachment_dispose (spAttachment* attachment) {
 	spWeightedMeshAttachment* self = SUB_CAST(spWeightedMeshAttachment, attachment);
 	_spAttachment_deinit(attachment);
 	FREE(self->path);
-	FREE(self->bones);
-	FREE(self->weights);
-	FREE(self->regionUVs);
 	FREE(self->uvs);
-	FREE(self->triangles);
-	FREE(self->edges);
+	if (!self->parentMesh) {
+		FREE(self->regionUVs);
+		FREE(self->bones);
+		FREE(self->weights);
+		FREE(self->triangles);
+		FREE(self->edges);
+	}
 	FREE(self);
 }
 
@@ -108,3 +110,28 @@ void spWeightedMeshAttachment_computeWorldVertices (spWeightedMeshAttachment* se
 		}
 	}
 }
+
+void spWeightedMeshAttachment_setParentMesh (spWeightedMeshAttachment* self, spWeightedMeshAttachment* parentMesh) {
+	CONST_CAST(spWeightedMeshAttachment*, self->parentMesh) = parentMesh;
+	if (parentMesh) {
+		self->bones = parentMesh->bones;
+		self->bonesCount = parentMesh->bonesCount;
+
+		self->weights = parentMesh->weights;
+		self->weightsCount = parentMesh->weightsCount;
+
+		self->regionUVs = parentMesh->regionUVs;
+		self->uvsCount = parentMesh->uvsCount;
+
+		self->triangles = parentMesh->triangles;
+		self->trianglesCount = parentMesh->trianglesCount;
+
+		self->hullLength = parentMesh->hullLength;
+
+		self->edges = parentMesh->edges;
+		self->edgesCount = parentMesh->edgesCount;
+
+		self->width = parentMesh->width;
+		self->height = parentMesh->height;
+	}
+}

+ 1 - 1
spine-cocos2d-iphone/2/README.md

@@ -10,7 +10,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f
 
 ## Spine version
 
-spine-cocos2d-iphone v2 works with data exported from the latest version of Spine, except linked meshes are [not yet supported](https://trello.com/c/bERJAFEq/73-update-runtimes-to-support-v3-1-linked-meshes).
+spine-cocos2d-iphone v2 works with data exported from the latest version of Spine.
 
 spine-cocos2d-iphone v2 supports all Spine features.
 

+ 1 - 1
spine-cocos2d-iphone/3/README.md

@@ -10,7 +10,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f
 
 ## Spine version
 
-spine-cocos2d-iphone v3 works with data exported from the latest version of Spine, except linked meshes are [not yet supported](https://trello.com/c/bERJAFEq/73-update-runtimes-to-support-v3-1-linked-meshes).
+spine-cocos2d-iphone v3 works with data exported from the latest version of Spine.
 
 spine-cocos2d-iphone v3 supports all Spine features.
 

+ 1 - 1
spine-cocos2dx/2/README.md

@@ -10,7 +10,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f
 
 ## Spine version
 
-spine-cocos2dx v2 works with data exported from the latest version of Spine, except linked meshes are [not yet supported](https://trello.com/c/bERJAFEq/73-update-runtimes-to-support-v3-1-linked-meshes).
+spine-cocos2dx v2 works with data exported from the latest version of Spine.
 
 spine-cocos2dx v2 supports all Spine features.
 

+ 1 - 1
spine-cocos2dx/3/README.md

@@ -10,7 +10,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f
 
 ## Spine version
 
-spine-cocos2dx v3 works with data exported from the latest version of Spine, except linked meshes are [not yet supported](https://trello.com/c/bERJAFEq/73-update-runtimes-to-support-v3-1-linked-meshes).
+spine-cocos2dx v3 works with data exported from the latest version of Spine.
 
 spine-cocos2dx v3 supports all Spine features.
 

+ 1 - 1
spine-sfml/README.md

@@ -10,7 +10,7 @@ The Spine Runtimes are developed with the intent to be used with data exported f
 
 ## Spine version
 
-spine-sfml works with data exported from the latest version of Spine, except linked meshes are [not yet supported](https://trello.com/c/bERJAFEq/73-update-runtimes-to-support-v3-1-linked-meshes).
+spine-sfml works with data exported from the latest version of Spine.
 
 spine-sfml supports all Spine features.